Files
2026-03-02 11:30:16 -05:00

142 lines
6.6 KiB
Plaintext

CopilotKit is built on the [AG-UI protocol](https://ag-ui.com) — a lightweight, event-based standard that defines how AI agents communicate with user-facing applications over Server-Sent Events (SSE).
Everything in CopilotKit — messages, state updates, tool calls, and more — flows through AG-UI events. Understanding this layer helps you debug, extend, and build on top of CopilotKit more effectively.
## Accessing Your Agent with `useAgent`
The `useAgent` hook is your primary interface to the AG-UI agent powering your copilot. It returns an [`AbstractAgent`](https://github.com/ag-ui-protocol/ag-ui/blob/main/typescript/packages/client/src/agents/abstract-agent.ts) from the AG-UI client library — the same base type that all AG-UI agents implement.
```tsx
import { useAgent } from "@copilotkit/react-core";
function MyComponent() {
const { agent } = useAgent();
// agent.messages - conversation history
// agent.state - current agent state
// agent.isRunning - whether the agent is currently running
}
```
If you have multiple agents, pass the `agentId` to select one:
```tsx
const { agent } = useAgent({ agentId: "research-agent" });
```
The returned `agent` is a standard AG-UI `AbstractAgent`. You can subscribe to its events, read its state, and interact with it using the same interface defined by the [AG-UI specification](https://docs.ag-ui.com).
### Subscribing to AG-UI Events
Every agent exposes a `subscribe` method that lets you listen for specific AG-UI events as they stream in. Each callback receives the event and the current agent state:
```tsx
import { useAgent } from "@copilotkit/react-core";
import { useEffect } from "react";
function MyComponent() {
const { agent } = useAgent();
useEffect(() => {
const subscription = agent.subscribe({
// Called on every event
onEvent({ event, agent }) {
console.log("Event:", event.type, event);
},
// Text message streaming
onTextMessageContentEvent({ event, textMessageBuffer, agent }) {
console.log("Streaming text:", textMessageBuffer);
},
// Tool calls
onToolCallEndEvent({ event, toolCallName, toolCallArgs, agent }) {
console.log("Tool called:", toolCallName, toolCallArgs);
},
// State updates
onStateSnapshotEvent({ event, agent }) {
console.log("State snapshot:", agent.state);
},
// High-level lifecycle
onMessagesChanged({ agent }) {
console.log("Messages updated:", agent.messages);
},
onStateChanged({ agent }) {
console.log("State changed:", agent.state);
},
});
return () => subscription.unsubscribe();
}, [agent]);
}
```
The full list of subscribable events maps directly to the [AG-UI event types](https://docs.ag-ui.com/concepts/events):
| Event | Callback | Description |
| --- | --- | --- |
| Run lifecycle | `onRunStartedEvent`, `onRunFinishedEvent`, `onRunErrorEvent` | Agent run start, completion, and errors |
| Steps | `onStepStartedEvent`, `onStepFinishedEvent` | Individual step boundaries within a run |
| Text messages | `onTextMessageStartEvent`, `onTextMessageContentEvent`, `onTextMessageEndEvent` | Streaming text content from the agent |
| Tool calls | `onToolCallStartEvent`, `onToolCallArgsEvent`, `onToolCallEndEvent`, `onToolCallResultEvent` | Tool invocation lifecycle |
| State | `onStateSnapshotEvent`, `onStateDeltaEvent` | Full state snapshots and incremental deltas |
| Messages | `onMessagesSnapshotEvent` | Full message list snapshots |
| Custom | `onCustomEvent`, `onRawEvent` | Custom and raw events for extensibility |
| High-level | `onMessagesChanged`, `onStateChanged` | Aggregate notifications after any message or state mutation |
## The Proxy Pattern
When you use CopilotKit with a runtime, your frontend never talks directly to your agent. Instead, CopilotKit creates a **proxy agent** on the frontend that forwards requests through the Copilot Runtime.
On startup, CopilotKit calls the runtime's `/info` endpoint to discover which agents are available. Each agent is wrapped in a `ProxiedCopilotRuntimeAgent` — a thin client that extends AG-UI's [`HttpAgent`](https://github.com/ag-ui-protocol/ag-ui/blob/main/typescript/packages/client/src/agents/http-agent.ts). From your component's perspective, this proxy behaves identically to a local AG-UI agent: same `AbstractAgent` interface, same subscribe API, same properties. But under the hood, every `run` call is an HTTP request to your server, and every response is an SSE stream of AG-UI events flowing back.
```tsx title="What your component sees"
const { agent } = useAgent(); // Returns an AbstractAgent
agent.messages; // Read messages
agent.state; // Read state
agent.subscribe({ ... }); // Subscribe to events
```
```tsx title="What actually happens"
// useAgent() → AgentRegistry checks /info → wraps each agent in ProxiedCopilotRuntimeAgent
// agent.runAgent() → HTTP POST to runtime → runtime routes to your agent → SSE stream back
```
This indirection is what enables the runtime to provide authentication, middleware, agent routing, and ecosystem features like [threads](/premium/threads) and [observability](/premium/observability) — without changing how you interact with agents on the frontend.
## How Agents Slot into the Runtime
On the server side, the `CopilotRuntime` accepts a map of AG-UI `AbstractAgent` instances. Each agent framework provides its own implementation, but they all extend the same base type:
```ts title="app/api/copilotkit/route.ts"
import { CopilotRuntime, copilotRuntimeNextJSAppRouterEndpoint } from "@copilotkit/runtime";
import { HttpAgent } from "@ag-ui/client";
const runtime = new CopilotRuntime({
agents: {
"my-agent": new HttpAgent({
url: "https://my-agent-server.example.com",
}),
},
});
export const POST = async (req: NextRequest) => {
const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({
runtime,
endpoint: "/api/copilotkit",
});
return handleRequest(req);
};
```
When a request comes in:
1. The runtime resolves the target agent by ID
2. It clones the agent (for thread safety) and sets messages, state, and thread context from the request
3. The `AgentRunner` executes the agent, which produces a stream of AG-UI `BaseEvent`s
4. Events are encoded as SSE and streamed back to the frontend proxy
Because every agent is an `AbstractAgent`, you can register any AG-UI-compatible agent — whether it's an `HttpAgent` pointing at a remote server, a framework-specific adapter, or a custom implementation — and the runtime handles routing, middleware, and delivery uniformly.