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

Chat agents·续4 - 图1Docs

{"@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


Skip to content

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.