Building Inputs for AI Agents: Claude Code, Codex, and Perplexity-Style Composers
Somewhere in the last two years, the humble text box quietly became the most important surface in software. We type into it to write code, run shell commands, search the web, and delegate multi-step work to autonomous agents. And yet most teams still build that surface the way they built a contact form in 2015: a <textarea>, a submit button, and a hope that nothing weird happens. For an AI agent, that is nowhere near enough.
Agents need a composer, not an input. The difference is everything that surrounds the cursor: which mode the agent is allowed to run in, which model and reasoning effort it should use, what context it already has (the repo, the working directory, the open file), and how much of your plan or quota you have left. This article walks through what an agent input actually requires, and why the pattern is now stable enough to copy.
An agent input is a cockpit, not a chat box
When you open a serious AI agent UI (Claude Code, OpenAI's Codex, Perplexity's answer composer), you are not looking at a message field. You are looking at a small control panel that happens to contain a message field. Break any of them down and the same anatomy appears:
- Context above the input. The current repository, branch, environment, or selected files. The agent's world is shown before you type a single character.
- A mode or permission selector. Plan vs. execute, read-only vs. auto-edit, "ask before running commands." This is the single most consequential control in any agentic tool, and it lives right next to the prompt.
- Model and reasoning-effort selectors. Pick the model; pick how hard it should think. These belong in the composer because they change per message, not per session.
- A usage or plan meter. Tokens, requests, or plan budget remaining, shown where the cost is incurred.
- A status-aware send/stop button. While the agent streams, "send" must become "stop." Anything else feels broken.
None of that is exotic. But all of it is fiddly, and none of it is served by a plain textarea. This is the gap a purpose-built composer fills.
Why "prompt-style" input is its own category
Here is a claim worth dwelling on: most rich text editors are full document editors shoehorned into chat inputs. Tiptap, Lexical, Slate, and ProseMirror are document frameworks. They are magnificent at building Notion or Google Docs. They are overkill, and frankly awkward, when all you want is a single growing field that supports mentions, slash commands, and inline emphasis, then collapses politely when blurred.
This is exactly the niche Prompt Area was built for. It is a production-grade contentEditable input purpose-built for prompt-style and chat-composer use, with zero extra dependencies: no ProseMirror, no Slate, no Lexical. You get one component, PromptArea, and one hook, usePromptAreaState(). It ships as an npm package with self-contained CSS, or via the shadcn registry so you can copy the source into your repo and own it outright. For agent UIs, that small surface area is a feature: you are not bending a document framework into a cockpit; you are starting from something already shaped like one.
The Styles: copy-paste agent compositions
The part that makes this concrete is Prompt Area's Styles. These are real, copy-paste compositions of the input plus its companion Action Bar and Status Bar components, each mirroring a UI you already recognize. There are five worth naming:
- The ChatGPT style: a single-line rounded pill with an inline model selector, dictation, and a voice-to-send arrow.
- The Claude style: a rounded card with the input sitting over an inline control row, a model menu with descriptions, a coral send button, a dismissible notice, and suggested-prompt chips.
- The Claude Code style: env and repo context above the input, a permission-mode menu, model and reasoning-effort selectors, and a plan-usage meter, all built from Prompt Area plus the Action Bar and Status Bar.
- The OpenAI Codex style: a Codex-like coding composer.
- The Perplexity style: a search-and-answer composer.
The Claude Code style is the clearest illustration of the cockpit idea. Look at what is stacked around the cursor: context (which repo and environment you're in) on top, the prompt in the middle, then a row of controls (permission mode, model, reasoning effort), and a usage meter to keep you honest. That is three distinct UI regions cooperating, and the split into Prompt Area (the input), Action Bar (the controls), and Status Bar (the context and meters) maps cleanly onto how you'll actually reason about the layout when you build it.
Composing the input with an Action Bar
The mental model is simple: the input owns text; the action bar owns controls; you wire them together with shared state. The hook gives you a controlled Segment[] value, and the action bar reads from and writes to the same place, including which model and mode are currently selected.
import { PromptArea, usePromptAreaState, segmentsToPlainText } from 'prompt-area' import { useState } from 'react' function AgentComposer({ onSubmit }) { const { segments, setSegments } = usePromptAreaState() const [mode, setMode] = useState('plan') // permission mode const [model, setModel] = useState('opus') // model selector const [effort, setEffort] = useState('high') // reasoning effort return ( <div className="composer"> <StatusBar repo="acme/web" branch="main" env="staging" /> <PromptArea value={segments} onChange={setSegments} placeholder="Describe a task for the agent..." onSubmit={() => onSubmit({ text: segmentsToPlainText(segments), mode, model, effort, }) } /> <ActionBar mode={mode} onModeChange={setMode} model={model} onModelChange={setModel} effort={effort} onEffortChange={setEffort} /> </div> ) }
Notice that mode, model, and effort ride alongside the prompt rather than being smuggled into the prompt string. That separation matters enormously once a real backend is involved, which brings us to the part where agent inputs earn their keep.
Wiring it to the Vercel AI SDK
The cleanest way to stream agent responses today is the Vercel AI SDK, and Prompt Area is built to meet it at a single seam. The idea: Prompt Area owns the input; the AI SDK's useChat owns the stream. They touch at one line, sendMessage({ text: segmentsToPlainText(segments) }), and everything else falls out from there.
Two integration details make this feel native rather than bolted-on:
- Status-aware send/stop. The SDK exposes a status of ready, submitted, streaming, or error. You feed that straight into the action bar so the send button flips to a stop button (wired to stop()) the moment a response begins streaming. The user always has a way out.
- Structured chip context in the request body. Because mentions and commands are real chips, not parsed substrings, you can read them with getChipsByTrigger() and send them as structured fields in the request body, with no fragile string parsing on the server.
import { useChat } from '@ai-sdk/react' import { getChipsByTrigger, segmentsToPlainText } from 'prompt-area' function ChatComposer() { const { sendMessage, status, stop } = useChat() const { segments } = usePromptAreaState() const submit = () => { sendMessage( { text: segmentsToPlainText(segments) }, { body: { mentions: getChipsByTrigger(segments, '@').map(c => c.value), command: getChipsByTrigger(segments, '/')[0]?.value, model: 'opus', }, }, ) } const streaming = status === 'streaming' || status === 'submitted' return <SendButton streaming={streaming} onSend={submit} onStop={stop} /> }
On the server you validate that body with Zod (safeParse, allowlist your model IDs and commands), then run convertToModelMessages() into streamText({ model }) and return toUIMessageStreamResponse(). Because the SDK is provider-agnostic, swapping @ai-sdk/anthropic for @ai-sdk/openai or @ai-sdk/google is a one-line change. The composer doesn't care which model is on the other end.
The discipline that pays off: keep intent (the prompt text) and configuration (model, mode, effort, mentions) in separate channels. Agents are sensitive to both, and conflating them into one string is where flaky behavior is born.Designing the surrounding controls well
A few principles that hold up across Claude Code, Codex, and Perplexity-style tools:
- Make the dangerous mode visible. If an agent can run shell commands or edit files, the permission mode should be the most legible control on screen, not buried in settings.
- Put context above, controls below. Users read the repo/env line before typing, and reach for model and mode after. Match the eye's path.
- Let the send button carry state. Idle, submitting, streaming, error: one button, four meanings. The stop affordance is non-negotiable.
- Show the meter where the cost is. A plan-usage indicator in the composer beats a number on a billing page nobody visits.
Start from something shaped right
You can build all of this from scratch. People do, and they spend weeks on chip handling, IME edge cases, undo/redo, and the auto-grow-on-focus animation before they ever get to the agent logic that actually matters. The faster path is to begin with an input already built for prompts and assemble the cockpit around it.
That is the case for Prompt Area in agent UIs: a tiny, dependency-free input, a set of Action Bar and Status Bar companions, ready-made Styles that mirror Claude Code, Codex, ChatGPT, and Perplexity, and a clean one-line handshake with the Vercel AI SDK. The text box stopped being a text box a while ago. Build it like the cockpit it has become.






