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 HttpAgent client, or a plain curl. Pick the agent with the model field, 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.

BUILT WITH ❤️ FROM CANADA AND SPAIN