Chat agents·续4
export class ChatAgent extends AIChatAgent { async onChatMessage() { const model = wrapLanguageModel({ model: baseModel, middleware: [guardrailMiddleware], });
const result = streamText({ model, messages: await convertToModelMessages(this.messages), });
return result.toUIMessageStreamResponse(); } }
The AI SDK includes built-in middlewares:
- `extractReasoningMiddleware` — surface chain-of-thought from models like DeepSeek R1
- `defaultSettingsMiddleware` — apply default temperature, max tokens, etc.
- `simulateStreamingMiddleware` — add streaming to non-streaming models
Multiple middlewares compose in order: `middleware: [first, second]` applies as `first(second(model))`.
### Structured output
Use [`generateObject` ↗︎](https://ai-sdk.dev/docs/ai-sdk-core/generating-structured-data) inside tools for structured data extraction:
```js
import {
streamText,
generateObject,
convertToModelMessages,
tool,
stepCountIs,
} from "ai";
import { z } from "zod";
export class ChatAgent extends AIChatAgent {
async onChatMessage() {
const result = streamText({
model: myModel,
messages: await convertToModelMessages(this.messages),
tools: {
extractContactInfo: tool({
description:
"Extract structured contact information from the conversation",
inputSchema: z.object({
text: z.string().describe("The text to extract contact info from"),
}),
execute: async ({ text }) => {
const { object } = await generateObject({
model: myModel,
schema: z.object({
name: z.string(),
email: z.string().email(),
phone: z.string().optional(),
}),
prompt: `Extract contact information from: ${text}`,
});
return object;
},
}),
},
stopWhen: stepCountIs(5),
});
return result.toUIMessageStreamResponse();
}
}
import {
streamText,
generateObject,
convertToModelMessages,
tool,
stepCountIs,
} from "ai";
import { z } from "zod";
export class ChatAgent extends AIChatAgent {
async onChatMessage() {
const result = streamText({
model: myModel,
messages: await convertToModelMessages(this.messages),
tools: {
extractContactInfo: tool({
description:
"Extract structured contact information from the conversation",
inputSchema: z.object({
text: z.string().describe("The text to extract contact info from"),
}),
execute: async ({ text }) => {
const { object } = await generateObject({
model: myModel,
schema: z.object({
name: z.string(),
email: z.string().email(),
phone: z.string().optional(),
}),
prompt: `Extract contact information from: ${text}`,
});
return object;
},
}),
},
stopWhen: stepCountIs(5),
});
return result.toUIMessageStreamResponse();
}
}
In-process subagent delegation
Note
This section covers in-process subagents using the AI SDK's ToolLoopAgent. For Durable Object sub-agents with their own isolated storage and typed RPC, refer to Sub-agents. To run Think or AIChatAgent sub-agents as retained, streaming tools, refer to Agents as tools.
Tools can delegate work to focused sub-calls with their own context. Use ToolLoopAgent ↗︎ to define a reusable agent, then call it from a tool's execute:
import {
ToolLoopAgent,
streamText,
convertToModelMessages,
tool,
stepCountIs,
} from "ai";
import { z } from "zod";
// Define a reusable research agent with its own tools and instructions
const researchAgent = new ToolLoopAgent({
model: researchModel,
instructions: "You are a research assistant. Be thorough and cite sources.",
tools: { webSearch: webSearchTool },
stopWhen: stepCountIs(10),
});
export class ChatAgent extends AIChatAgent {
async onChatMessage() {
const result = streamText({
model: orchestratorModel,
messages: await convertToModelMessages(this.messages),
tools: {
deepResearch: tool({
description: "Research a topic in depth",
inputSchema: z.object({
topic: z.string().describe("The topic to research"),
}),
execute: async ({ topic }) => {
const { text } = await researchAgent.generate({
prompt: topic,
});
return { summary: text };
},
}),
},
stopWhen: stepCountIs(5),
});
return result.toUIMessageStreamResponse();
}
}
import {
ToolLoopAgent,
streamText,
convertToModelMessages,
tool,
stepCountIs,
} from "ai";
import { z } from "zod";
// Define a reusable research agent with its own tools and instructions
const researchAgent = new ToolLoopAgent({
model: researchModel,
instructions: "You are a research assistant. Be thorough and cite sources.",
tools: { webSearch: webSearchTool },
stopWhen: stepCountIs(10),
});
export class ChatAgent extends AIChatAgent {
async onChatMessage() {
const result = streamText({
model: orchestratorModel,
messages: await convertToModelMessages(this.messages),
tools: {
deepResearch: tool({
description: "Research a topic in depth",
inputSchema: z.object({
topic: z.string().describe("The topic to research"),
}),
execute: async ({ topic }) => {
const { text } = await researchAgent.generate({
prompt: topic,
});
return { summary: text };
},
}),
},
stopWhen: stepCountIs(5),
});
return result.toUIMessageStreamResponse();
}
}
The research agent runs in its own context — its token budget is separate from the orchestrator's. Only the summary goes back to the parent model.
Note
ToolLoopAgent is best suited for in-process subagents, not as a replacement for streamText in onChatMessage itself. The main onChatMessage benefits from direct access to this.env, this.messages, and options.body — things that a pre-configured ToolLoopAgent instance cannot reference.
Streaming progress with preliminary results
By default, a tool part appears as loading until execute returns. Use an async generator (async function*) to stream progress updates to the client while the tool is still working:
deepResearch: tool({
description: "Research a topic in depth",
inputSchema: z.object({
topic: z.string().describe("The topic to research"),
}),
async *execute({ topic }) {
// Preliminary result — the client sees "searching" immediately
yield { status: "searching", topic, summary: undefined };
const { text } = await researchAgent.generate({ prompt: topic });
// Final result — sent to the model for its next step
yield { status: "done", topic, summary: text };
},
});
deepResearch: tool({
description: "Research a topic in depth",
inputSchema: z.object({
topic: z.string().describe("The topic to research"),
}),
async *execute({ topic }) {
// Preliminary result — the client sees "searching" immediately
yield { status: "searching", topic, summary: undefined };
const { text } = await researchAgent.generate({ prompt: topic });
// Final result — sent to the model for its next step
yield { status: "done", topic, summary: text };
},
});
Each yield updates the tool part on the client in real-time (with preliminary: true). The last yielded value becomes the final output that the model sees.
This pattern is useful when:
- A task requires exploring large amounts of information that would bloat the main context
- You want to show real-time progress for long-running tools
- You want to parallelize independent research (multiple tool calls run concurrently)
- You need different models or system prompts for different subtasks
For more, refer to the AI SDK Agents docs ↗︎, Subagents ↗︎, and Preliminary Tool Results ↗︎.
Multi-client sync
When multiple clients connect to the same agent instance, messages are automatically broadcast to all connections. If one client sends a message, all other connected clients receive the updated message list.
Client A ──── sendMessage("Hello") ────▶ AIChatAgent
│
persist + stream
│
Client A ◀── CF_AGENT_USE_CHAT_RESPONSE ──────┤
Client B ◀── CF_AGENT_CHAT_MESSAGES ──────────┘
The originating client receives the streaming response. All other clients receive the final messages via a CF_AGENT_CHAT_MESSAGES broadcast.
API reference
Exports
| Import path | Exports |
|---|---|
@cloudflare/ai-chat |
AIChatAgent, createToolsFromClientSchemas, ClientToolSchema, ChatRecoveryContext, ChatRecoveryOptions, ChatRecoveryConfig, ChatRecoveryExhaustedContext, ResolvedChatRecoveryConfig, lifecycle types |
@cloudflare/ai-chat/react |
useAgentChat, extractClientToolSchemas, getToolPartState, getToolCallId, getToolInput, getToolOutput, getToolApproval |
@cloudflare/ai-chat/types |
MessageType, OutgoingMessage, IncomingMessage |
agents/chat |
Shared advanced chat primitives such as SaveMessagesResult, SaveMessagesOptions, CHAT_MESSAGE_TYPES, ROW_MAX_BYTES, and isReplayChunk() |
agents/chat/transport |
WebSocketChatTransport and its AgentConnection connection types, for non-React clients |
WebSocket protocol
The chat protocol uses typed JSON messages over WebSocket:
| Message | Direction | Purpose |
|---|---|---|
CF_AGENT_USE_CHAT_REQUEST |
Client → Server | Send a chat message |
CF_AGENT_USE_CHAT_RESPONSE |
Server → Client | Stream response chunks |
CF_AGENT_CHAT_MESSAGES |
Server → Client | Broadcast updated messages |
CF_AGENT_CHAT_CLEAR |
Bidirectional | Clear conversation |
CF_AGENT_CHAT_REQUEST_CANCEL |
Client → Server | Cancel active stream |
CF_AGENT_TOOL_RESULT |
Client → Server | Provide tool output |
CF_AGENT_TOOL_APPROVAL |
Client → Server | Approve or reject a tool |
CF_AGENT_MESSAGE_UPDATED |
Server → Client | Notify of message update |
CF_AGENT_STREAM_RESUMING |
Server → Client | Notify of stream resumption |
CF_AGENT_STREAM_RESUME_REQUEST |
Client → Server | Request stream resume check |
CF_AGENT_STREAM_RESUME_ACK |
Server → Client | Resume stream from a cursor |
CF_AGENT_STREAM_RESUME_NONE |
Server → Client | No resumable stream exists |
Deprecated APIs
The following APIs are deprecated and will emit a console warning when used. They will be removed in a future release.
| Deprecated | Replacement | Notes |
|---|---|---|
addToolResult({ toolCallId, result }) |
addToolOutput({ toolCallId, output }) |
Renamed for consistency with AI SDK terminology |
detectToolsRequiringConfirmation() |
Use needsApproval on the tool definition |
Approval is now per-tool, not a global filter |
toolsRequiringConfirmation option |
Use needsApproval on individual tools |
Per-tool approval replaces global list |
If you are upgrading from an earlier version, replace deprecated calls with their replacements. The deprecated APIs still work but will be removed in a future major version.
createToolsFromClientSchemas(), extractClientToolSchemas(), and the tools option on useAgentChat are still supported for dynamic client-side tools. They are advanced APIs, not deprecated APIs.
Next steps
Client SDK
useAgent hook and AgentClient class.
Human-in-the-loop
Approval flows and manual intervention patterns.
Build a chat agent
Step-by-step tutorial for building your first chat agent.
Durable execution
runFiber(), stash(), and crash recovery for long-running work.
Long-running agents
Lifecycle, recovery patterns, and provider-specific strategies.
Was this helpful?
YesNo
On this page
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/agents/communication-channels/chat/chat-agents/#page","headline":"Chat agents","description":"Build AI chat interfaces with AIChatAgent and useAgentChat, including message persistence, streaming, and tool support.","url":"https://developers.cloudflare.com/agents/communication-channels/chat/chat-agents/","inLanguage":"en","image":"https://developers.cloudflare.com/agents/communication-channels/chat/chat-agents/og.png?v=7e7ad14729f7160e","dateModified":"2026-10-22","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
description: Connect to Cloudflare Agents from browsers or server runtimes using useAgent, AgentClient, and agentFetch. title: Client SDK image: https://developers.cloudflare.com/agents/communication-channels/chat/client-sdk/og.png?v=13b590d29d3efa8a
Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/agents/llms.txt
Use this file to discover all available pages before exploring further.
Docs