Skip to main content
AG-UI is CopilotKit’s open protocol for connecting AI agents to user interfaces over Server-Sent Events. AGUIAdapter wraps any Vibes agent and serves it as an AG-UI-compatible HTTP endpoint. CopilotKit frontends using useCoAgent connect directly to this endpoint.
Migrating from older docs? Two API bugs existed in previous versions of this documentation:
  1. The depsFactory option does not exist on AGUIAdapterOptions. Use deps instead.
  2. handleRequest() does not accept a Request object. It accepts an AGUIRunInput object. For Deno HTTP servers, use adapter.handler() which parses the request body for you.
See the corrected API below.

Architecture

Setup

Install @vibesjs/sdk and import AGUIAdapter:
import { Agent, AGUIAdapter } from "npm:@vibesjs/sdk";
import { anthropic } from "npm:@ai-sdk/anthropic";

const model = anthropic("claude-opus-4-5");
const agent = new Agent({ model, systemPrompt: "You are a helpful assistant." });

Constructor

const adapter = new AGUIAdapter(agent, {
  deps: myDeps,                         // static deps passed to every agent run
  getState: () => ({ key: "value" }),   // optional state snapshot callback
});
AGUIAdapterOptions fields:
OptionTypeDescription
depsTDepsStatic dependencies injected into every agent run. Not a factory - computed once at construction.
getState() => Record<string, unknown> | Promise<Record<string, unknown>>Called at run start and end to capture a state snapshot for the frontend

Deno HTTP server

One line connects your adapter to a Deno HTTP server:
import { Agent, AGUIAdapter } from "npm:@vibesjs/sdk";
import { anthropic } from "npm:@ai-sdk/anthropic";

const model = anthropic("claude-opus-4-5");
const agent = new Agent({ model, systemPrompt: "You are a helpful assistant." });

const adapter = new AGUIAdapter(agent, {
  deps: { db: getDb(), userId: "system" },
  getState: () => ({ sessionActive: true }),
});

// adapter.handler() returns a (req: Request) => Promise<Response> function
Deno.serve(adapter.handler());
adapter.handler() returns a standard Deno HTTP handler. It parses the JSON body from POST requests into AGUIRunInput and calls handleRequest().

AGUIRunInput

The shape of the input object that handleRequest() receives (after handler() parses the HTTP body):
interface AGUIRunInput {
  threadId: string;                  // identifies the conversation thread
  runId?: string;                    // optional run identifier
  messages: AGUIMessage[];           // conversation history
  state?: Record<string, unknown>;   // frontend state snapshot to pass to the agent
}
FieldRequiredDescription
threadIdYesStable identifier for the conversation. Used in RUN_STARTED and RUN_FINISHED events.
runIdNoPer-run identifier. Generated automatically if omitted.
messagesYesFull conversation history in AG-UI message format.
stateNoState snapshot from the frontend, available to the agent via dependencies.

Direct handleRequest() usage

Call handleRequest() directly when you parse the request body yourself:
import type { AGUIRunInput } from "npm:@vibesjs/sdk";

// In your own HTTP handler:
async function myHandler(req: Request): Promise<Response> {
  const body = await req.json() as AGUIRunInput;

  // Validate or transform body here...

  return adapter.handleRequest(body);  // takes AGUIRunInput, not Request
}

Deno.serve(myHandler);

SSE event sequence

Every agent run emits a fixed sequence of SSE events. Understanding the sequence helps you build reliable frontend state machines. On error, RUN_ERROR is emitted instead of RUN_FINISHED.

Multi-turn conversations

Pass the message history from a previous result to continue a conversation across turns:
// First turn
const input1: AGUIRunInput = {
  threadId: "thread-123",
  messages: [{ role: "user", content: "What is the capital of France?" }],
};
const response1 = adapter.handleRequest(input1);

// Second turn - include full message history
const input2: AGUIRunInput = {
  threadId: "thread-123",
  messages: [
    { role: "user", content: "What is the capital of France?" },
    { role: "assistant", content: "The capital of France is Paris." },
    { role: "user", content: "What is its population?" },
  ],
};
const response2 = adapter.handleRequest(input2);

State management

Use getState to expose a snapshot of server-side state to the frontend on every run. The adapter calls getState at the start and end of each run and emits STATE_SNAPSHOT events.
interface SessionState {
  userId: string;
  credits: number;
  tier: "free" | "pro";
}

const adapter = new AGUIAdapter(agent, {
  deps: { db },
  getState: async (): Promise<SessionState> => {
    const user = await db.getUser(currentUserId);
    return {
      userId: user.id,
      credits: user.credits,
      tier: user.tier,
    };
  },
});
The frontend receives the state in STATE_SNAPSHOT events and can render it via CopilotKit’s useCoAgentStateRender hook.

Event types reference

All 15 event types emitted by AGUIAdapter:
Event TypeWhen Emitted
RUN_STARTEDAt the beginning of every run (threadId, runId)
RUN_FINISHEDAfter the agent completes all turns
RUN_ERRORIf the agent throws an unhandled error
STATE_SNAPSHOTAt run start (if state provided), and at run end
STATE_DELTAIncremental state update (emitted by frontend protocol)
MESSAGES_SNAPSHOTFull messages snapshot (emitted by frontend protocol)
STEP_STARTEDBefore each agent turn (stepName: "turn-N")
STEP_FINISHEDAfter each agent turn completes
TEXT_MESSAGE_STARTBefore streaming a text response
TEXT_MESSAGE_CONTENTEach text chunk (delta)
TEXT_MESSAGE_ENDAfter the text response is complete
TOOL_CALL_STARTBefore a tool call
TOOL_CALL_ARGSTool arguments (emitted as a single JSON chunk)
TOOL_CALL_ENDAfter tool call completes
RAWUsage metadata (token counts) after each step

API reference

AGUIAdapterOptions

interface AGUIAdapterOptions<TDeps> {
  deps?: TDeps;
  getState?: () => Record<string, unknown> | Promise<Record<string, unknown>>;
}
OptionTypeDescription
depsTDepsStatic dependencies for every agent run
getState() => Record | Promise<Record>State snapshot callback, called at run start and end

AGUIAdapter

MemberSignatureDescription
Constructornew AGUIAdapter(agent, options?)Create the adapter
handleRequest(input)(AGUIRunInput) => ResponseRun the agent and return an SSE Response. Input is AGUIRunInput, not Request.
handler()() => (req: Request) => Promise<Response>Returns a Deno-compatible HTTP handler that parses the request body and calls handleRequest

AGUIRunInput

FieldTypeRequiredDescription
threadIdstringYesConversation thread identifier
runIdstringNoPer-run identifier (auto-generated if omitted)
messagesAGUIMessage[]YesConversation history
stateRecord<string, unknown>NoFrontend state snapshot