Conversing with an agent
An agent speaks three standard wire formats directly, so any client built for OpenAI Chat Completions, Anthropic Messages, or AG-UI works against it with nothing but a base URL and a key: LibreChat, Open WebUI, the Vercel AI SDK, LangChain, the AG-UI
HttpAgentclient, or a plaincurl. Pick the agent with themodelfield, or with its name in the URL for AG-UI. There is no proprietary session protocol to learn.
A workspace can hold many agents. For OpenAI and Anthropic, the request's model names
the one that should answer, and GET /models lists the agent names so a
client can populate a dropdown (chat itself never routes through /models,
only through the POST endpoints below). AG-UI carries no model field: you
pick the agent by putting its name in the URL, POST /agui/v1/agents/:name.
The OpenAI and Anthropic wires are stateless by default: you send the
full messages[] array on every call, the same as calling OpenAI or
Anthropic directly. There is no session id or continuation token to carry
forward unless you opt in with a header (see
Session keys). AG-UI works the other
way around: its threadId is a durable session key, always on, so you
send only the newest turn once a thread exists. See AG-UI below.
OpenAI Chat Completions
POST /openai/v1/chat/completions
Authorization: Bearer
Content-Type: application/json
{
"model": "support",
"messages": [{ "role": "user", "content": "What plans do you offer?" }],
"stream": false
} {
"id": "chatcmpl-…",
"object": "chat.completion",
"created": 1735689600,
"model": "support",
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "We offer Free, Pro and Team plans." },
"finish_reason": "stop",
},
],
"usage": { "prompt_tokens": 42, "completion_tokens": 9, "total_tokens": 51 },
}Set "stream": true to get chat.completion.chunk server-sent events
instead, ending with data: [DONE], the same as the OpenAI API. A turn
that calls a tool sets finish_reason: "tool_calls" and lists the calls
under choices[0].message.tool_calls.
Anthropic Messages
POST /anthropic/v1/messages
x-api-key:
Content-Type: application/json
{
"model": "support",
"max_tokens": 1024,
"messages": [{ "role": "user", "content": "What plans do you offer?" }]
} {
"id": "msg_…",
"type": "message",
"role": "assistant",
"model": "support",
"content": [{ "type": "text", "text": "We offer Free, Pro and Team plans." }],
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 42,
"output_tokens": 9,
"cache_read_input_tokens": 0,
"cache_creation_input_tokens": 0,
},
}This is the richer of the two wires: it carries first-class thinking
blocks (the agent's reasoning, when the model produces any) and tool_use
blocks with structured input. Its usage reports cache reads and writes
alongside input/output tokens, on the response above and on the streaming
message_delta. input_tokens excludes the cached counts, so the three
numbers add up instead of double-counting. Set
"stream": true for the same message_start / content_block_* /
message_delta / message_stop events Anthropic's own API emits.
AG-UI
POST /agui/v1/agents/support
Authorization: Bearer
Content-Type: application/json
{
"threadId": "thread-1",
"runId": "run-1",
"messages": [{ "id": "msg-1", "role": "user", "content": "What plans do you offer?" }],
"tools": [],
"context": [],
"state": null
} The response is always a Server-Sent-Events stream, even for a one-line reply: AG-UI has no non-streaming mode. Each frame is one AG-UI event, encoded with the protocol's own event encoder:
data: {"type":"RUN_STARTED","threadId":"thread-1","runId":"run-1"}
data: {"type":"TEXT_MESSAGE_START","messageId":"msg_1","role":"assistant"}
data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"msg_1","delta":"We offer Free, Pro and Team plans."}
data: {"type":"TEXT_MESSAGE_END","messageId":"msg_1"}
data: {"type":"CUSTOM","name":"usage","value":{"inputTokens":42,"outputTokens":9,"cacheReadTokens":0,"cacheWriteTokens":0}}
data: {"type":"RUN_FINISHED","threadId":"thread-1","runId":"run-1","outcome":{"type":"success"}}threadId and runId are both required: you mint them, the agent echoes
them back verbatim. There is no model field. You choose the agent with the
:name in the URL, so GET /models has no AG-UI equivalent either.
Every reasoning block the model produces streams as its own
REASONING_START / REASONING_MESSAGE_* / REASONING_END pair, and every
tool call as TOOL_CALL_START / TOOL_CALL_ARGS / TOOL_CALL_END, plus a
TOOL_CALL_RESULT once a server-run tool returns. AG-UI is the only one of
the three wires with a dedicated event for a tool's result.
tools declares tools your frontend can execute (a modal, a browser
API, anything that only makes sense client-side). See
Frontend-executed tools.
state seeds the agent's shared state before the run starts. See
Shared state and generative UI.
Anything the protocol has no event for rides a CUSTOM frame, named by its
name field. Besides usage above, a turn emits browser_live_view (the
live-view URL of a browsing session, as soon as one opens) and guardrail
(the decision a guardrail took on this turn).
Listing agents
GET /openai/v1/models{
"object": "list",
"data": [{ "id": "support", "object": "model", "created": 1735689600, "owned_by": "stackbone" }],
}GET /anthropic/v1/models returns the same names in Anthropic's model-list
shape: { data: [{ type, id, display_name, created_at }], first_id, last_id, has_more }. Both are catalogs only: a client uses them to populate a model
picker, never to route a chat call.