Skip to content

非公式本サイトは非公式の日本語ドキュメントであり、Cloudflare 公式サイトではありません。最新情報はdevelopers.cloudflare.comをご確認ください。

チャットエージェント

最終更新 Markdown で表示Agent セットアップ

AIChatAgentuseAgentChat で AI チャットインターフェイスを構築します。メッセージは SQLite に自動永続化され、切断時にストリームが再開し、ツール呼び出しはサーバーとクライアントをまたいで動きます。

概要

@cloudflare/ai-chat パッケージは、次の 2 つの主要な API を提供します。

エクスポート インポート 用途
AIChatAgent @cloudflare/ai-chat メッセージの永続化とストリーミングを持つサーバー側エージェントクラス
useAgentChat @cloudflare/ai-chat/react チャット UI を構築する React フック

高度なヘルパーは @cloudflare/ai-chat/react@cloudflare/ai-chat/typesagents/chat からも利用できます。パッケージ表面の全体は エクスポート を参照してください。

AI SDK と Cloudflare Durable Objects の上に構築されており、次が得られます。

  • 自動メッセージ永続化 — 会話は SQLite に保存され、再起動後も残ります
  • 再開可能なストリーミング — 切断したクライアントはデータ損失なしにストリーム途中から再開します
  • リアルタイム同期 — メッセージは WebSocket 経由ですべての接続クライアントへブロードキャストされます
  • ツール対応 — サーバー側、クライアント側、ヒューマンインザループのツールパターン
  • Data parts — テキストとあわせて、引用、進捗、利用量などの型付き JSON をメッセージに付けられます
  • 行サイズ保護 — メッセージが SQLite の上限に近づくと自動コンパクションします

クイックスタート

インストール

npm i @cloudflare/ai-chat agents ai @ai-sdk/react workers-ai-provider

サーバー

import { AIChatAgent } from "@cloudflare/ai-chat";
import { createWorkersAI } from "workers-ai-provider";
import { streamText, convertToModelMessages } from "ai";

export class ChatAgent extends AIChatAgent {
	async onChatMessage() {
		// Use any provider such as workers-ai-provider, openai, anthropic, google, etc.
		const workersai = createWorkersAI({ binding: this.env.AI });

		const result = streamText({
			model: workersai("@cf/zai-org/glm-4.7-flash"),
			messages: await convertToModelMessages(this.messages),
		});

		return result.toUIMessageStreamResponse();
	}
}
import { AIChatAgent } from "@cloudflare/ai-chat";
import { createWorkersAI } from "workers-ai-provider";
import { streamText, convertToModelMessages } from "ai";

export class ChatAgent extends AIChatAgent {
	async onChatMessage() {
		// Use any provider such as workers-ai-provider, openai, anthropic, google, etc.
		const workersai = createWorkersAI({ binding: this.env.AI });

		const result = streamText({
			model: workersai("@cf/zai-org/glm-4.7-flash"),
			messages: await convertToModelMessages(this.messages),
		});

		return result.toUIMessageStreamResponse();
	}
}

クライアント

import { useAgent } from "agents/react";
import { useAgentChat } from "@cloudflare/ai-chat/react";

function Chat() {
	const agent = useAgent({ agent: "ChatAgent" });
	const { messages, sendMessage, status } = useAgentChat({ agent });

	return (
		<div>
			{messages.map((msg) => (
				<div key={msg.id}>
					<strong>{msg.role}:</strong>
					{msg.parts.map((part, i) =>
						part.type === "text" ? <span key={i}>{part.text}</span> : null,
					)}
				</div>
			))}

			<form
				onSubmit={(e) => {
					e.preventDefault();
					const input = e.currentTarget.elements.namedItem("input");
					sendMessage({ text: input.value });
					input.value = "";
				}}
			>
				<input name="input" placeholder="Type a message..." />
				<button type="submit" disabled={status !== "ready"}>
					Send
				</button>
			</form>
		</div>
	);
}
import { useAgent } from "agents/react";
import { useAgentChat } from "@cloudflare/ai-chat/react";

function Chat() {
	const agent = useAgent({ agent: "ChatAgent" });
	const { messages, sendMessage, status } = useAgentChat({ agent });

	return (
		<div>
			{messages.map((msg) => (
				<div key={msg.id}>
					<strong>{msg.role}:</strong>
					{msg.parts.map((part, i) =>
						part.type === "text" ? <span key={i}>{part.text}</span> : null,
					)}
				</div>
			))}

			<form
				onSubmit={(e) => {
					e.preventDefault();
					const input = e.currentTarget.elements.namedItem(
						"input",
					) as HTMLInputElement;
					sendMessage({ text: input.value });
					input.value = "";
				}}
			>
				<input name="input" placeholder="Type a message..." />
				<button type="submit" disabled={status !== "ready"}>
					Send
				</button>
			</form>
		</div>
	);
}

Wrangler 設定

// wrangler.jsonc
{
	"ai": { "binding": "AI" },
	"durable_objects": {
		"bindings": [{ "name": "ChatAgent", "class_name": "ChatAgent" }],
	},
	"migrations": [{ "tag": "v1", "new_sqlite_classes": ["ChatAgent"] }],
}

new_sqlite_classes マイグレーションは必須です。AIChatAgent はメッセージの永続化とストリームチャンクのバッファに SQLite を使います。

しくみ

sequenceDiagram
    participant Client as Client (useAgentChat)
    participant Agent as AIChatAgent
    participant DB as SQLite

    Client->>Agent: CF_AGENT_USE_CHAT_REQUEST (WebSocket)
    Agent->>DB: Persist messages
    Agent->>Agent: onChatMessage()
    loop Streaming response
        Agent-->>Client: CF_AGENT_USE_CHAT_RESPONSE (chunks)
        Agent->>DB: Buffer chunks
    end
    Agent->>DB: Persist final message
    Agent-->>Client: CF_AGENT_CHAT_MESSAGES (broadcast to all clients)
  1. クライアントは WebSocket 経由でメッセージを送ります
  2. AIChatAgent はメッセージを SQLite に永続化し、onChatMessage メソッドを呼びます
  3. メソッドはストリーミング Response を返します(通常は streamText から)
  4. チャンクは WebSocket 経由でリアルタイムにストリームバックされます
  5. ストリームが完了すると、最終メッセージが永続化され、すべての接続へブロードキャストされます

サーバー API

AIChatAgent

agents パッケージの Agent を拡張します。会話状態、永続化、ストリーミングを管理します。

import { AIChatAgent } from "@cloudflare/ai-chat";

export class ChatAgent extends AIChatAgent {
	// Access current messages
	// this.messages: UIMessage[]

	// Limit stored messages (optional)
	maxPersistedMessages = 200;

	async onChatMessage(onFinish, options) {
		// onFinish: callback for streamText (cleanup is automatic)
		// options.abortSignal: cancel signal
		// options.body: custom data from client
		// options.continuation: true for continuation turns
		// Return a Response (streaming or plain text)
	}
}
import { AIChatAgent } from "@cloudflare/ai-chat";

export class ChatAgent extends AIChatAgent {
	// Access current messages
	// this.messages: UIMessage[]

	// Limit stored messages (optional)
	maxPersistedMessages = 200;

	async onChatMessage(onFinish, options?) {
		// onFinish: callback for streamText (cleanup is automatic)
		// options.abortSignal: cancel signal
		// options.body: custom data from client
		// options.continuation: true for continuation turns
		// Return a Response (streaming or plain text)
	}
}

onChatMessage

オーバーライドする主なメソッドです。会話コンテキストを受け取り、Response を返す必要があります。

ストリーミング応答(最も一般的):

export class ChatAgent extends AIChatAgent {
	async onChatMessage() {
		const workersai = createWorkersAI({ binding: this.env.AI });

		const result = streamText({
			model: workersai("@cf/zai-org/glm-4.7-flash"),
			system: "You are a helpful assistant.",
			messages: await convertToModelMessages(this.messages),
		});

		return result.toUIMessageStreamResponse();
	}
}
export class ChatAgent extends AIChatAgent {
	async onChatMessage() {
		const workersai = createWorkersAI({ binding: this.env.AI });

		const result = streamText({
			model: workersai("@cf/zai-org/glm-4.7-flash"),
			system: "You are a helpful assistant.",
			messages: await convertToModelMessages(this.messages),
		});

		return result.toUIMessageStreamResponse();
	}
}

プレーンテキスト応答:

export class ChatAgent extends AIChatAgent {
	async onChatMessage() {
		return new Response("Hello! I am a simple agent.", {
			headers: { "Content-Type": "text/plain" },
		});
	}
}

カスタム body データとリクエスト ID へのアクセス:

export class ChatAgent extends AIChatAgent {
	async onChatMessage(_onFinish, options) {
		const { timezone, userId } = options?.body ?? {};
		// Use these values in your LLM call or business logic

		// options.requestId — unique identifier for this chat request,
		// useful for logging and correlating events
		console.log("Request ID:", options?.requestId);

		if (options?.continuation) {
			// This turn continues a previous assistant message after a tool result,
			// continueLastTurn(), or recovery.
		}
	}
}

options.continuation は、ツール結果または承認のあとの自動継続、continueLastTurn() の呼び出し、回復したターンで true です。継続ターンで別のモデルを選ぶ、システムプロンプトを調整する、高価なコンテキスト組み立てをスキップするために使います。

this.messages

SQLite から読み込んだ現在の会話履歴です。AI SDK の UIMessage オブジェクトの配列です。メッセージは各やり取りのあと自動永続化されます。

maxPersistedMessages

SQLite に保存するメッセージ数の上限です。上限を超えると、最も古いメッセージが削除されます。これはストレージだけを制御し、LLM へ送る内容には影響しません。

export class ChatAgent extends AIChatAgent {
	maxPersistedMessages = 200;
}
export class ChatAgent extends AIChatAgent {
	maxPersistedMessages = 200;
}

モデルへ送る内容を制御するには、AI SDK の pruneMessages() を使います。

import { streamText, convertToModelMessages, pruneMessages } from "ai";

export class ChatAgent extends AIChatAgent {
	async onChatMessage() {
		const workersai = createWorkersAI({ binding: this.env.AI });

		const result = streamText({
			model: workersai("@cf/zai-org/glm-4.7-flash"),
			messages: pruneMessages({
				messages: await convertToModelMessages(this.messages),
				reasoning: "before-last-message",
				toolCalls: "before-last-2-messages",
			}),
		});

		return result.toUIMessageStreamResponse();
	}
}
import { streamText, convertToModelMessages, pruneMessages } from "ai";

export class ChatAgent extends AIChatAgent {
	async onChatMessage() {
		const workersai = createWorkersAI({ binding: this.env.AI });

		const result = streamText({
			model: workersai("@cf/zai-org/glm-4.7-flash"),
			messages: pruneMessages({
				messages: await convertToModelMessages(this.messages),
				reasoning: "before-last-message",
				toolCalls: "before-last-2-messages",
			}),
		});

		return result.toUIMessageStreamResponse();
	}
}

waitForMcpConnections

onChatMessage を呼ぶ前に、AIChatAgent が MCP サーバー接続の安定を待つかを制御します。this.mcp.getAITools() がツール一式を返すことを確保します。特に Durable Object のハイバネーション後、接続がバックグラウンドで復元されているときに重要です。

動作
{ timeout: 10_000 } 最大 10 秒待ちます(デフォルト)
{ timeout: N } 最大 N ミリ秒待ちます
true すべての接続が準備できるまで無期限に待ちます
false 待ちません(0.2.0 より前の動作)
export class ChatAgent extends AIChatAgent {
	// Default — waits up to 10 seconds
	// waitForMcpConnections = { timeout: 10_000 };

	// Wait forever
	waitForMcpConnections = true;

	// Disable waiting
	waitForMcpConnections = false;
}
export class ChatAgent extends AIChatAgent {
	// Default — waits up to 10 seconds
	// waitForMcpConnections = { timeout: 10_000 };

	// Wait forever
	waitForMcpConnections = true;

	// Disable waiting
	waitForMcpConnections = false;
}

より低レベルの制御には、代わりに onChatMessage 内で this.mcp.waitForConnections() を直接呼びます。

messageConcurrency

チャットターンがすでにアクティブまたはキュー済みのとき、重なるユーザー送信がどう振る舞うかを制御します。

export class ChatAgent extends AIChatAgent {
	messageConcurrency = "queue";
}
export class ChatAgent extends AIChatAgent {
	messageConcurrency = "queue";
}
戦略 動作
"queue"(デフォルト) すべての送信をキューし、順に処理します
"latest" 重なる送信のうち最新だけを残します。上書きされた送信はユーザーメッセージを永続化しますが、モデルターンは開始しません
"merge" 重なる送信をキューし、最新のキュー済みターンが走る前に末尾のユーザーメッセージを 1 つの結合ターンへまとめます
"drop" 重なる送信を完全に無視します。メッセージは永続化されません。
{ strategy: "debounce", debounceMs?: number } 静穏ウィンドウ付きの trailing-edge latest です(デフォルト 750ms)

この設定は sendMessage() の送信にだけ適用されます。再生成、ツール継続、承認、クリア、プログラムによる saveMessages() 呼び出しは、既存の直列化された動作を保ちます。

persistMessagessaveMessages

persistMessages はメッセージを SQLite に保存し、更新をすべての接続クライアントへブロードキャストしますが、モデルターンは 起動しません。新しい応答を始めずに会話へメッセージを注入したいときに使います。

saveMessages はメッセージを永続化し、かつ 新しい応答のために onChatMessage() を起動します。開始前にアクティブなチャットターンの完了を待つため、スケジュールされた、またはプログラムによるメッセージが進行中のストリームと重なりません。

// Store messages without triggering a response
await this.persistMessages(messages);

// Store messages AND trigger onChatMessage
const { requestId, status } = await this.saveMessages(messages);
// Store messages without triggering a response
await this.persistMessages(messages);

// Store messages AND trigger onChatMessage
const { requestId, status } = await this.saveMessages(messages);

saveMessages はメッセージ配列、または最新の永続化済み this.messages から次のメッセージリストを導く関数のいずれかを受け付けます。複数の呼び出しがキューされるときに古いベースラインを避けるため、関数形式を使います。

await this.saveMessages((messages) => [
	...messages,
	{
		id: crypto.randomUUID(),
		role: "user",
		parts: [{ type: "text", text: "Summarize the latest data" }],
		createdAt: new Date(),
	},
]);
await this.saveMessages((messages) => [
	...messages,
	{
		id: crypto.randomUUID(),
		role: "user",
		parts: [{ type: "text", text: "Summarize the latest data" }],
		createdAt: new Date(),
	},
]);

saveMessages{ requestId, status, error? } を返します。status はターンが実行された場合 "completed"、ストリームがエラーを報告した場合 "error"、開始前にチャットがクリアされた場合 "skipped"、外部の AbortSignal が完了前にキャンセルした場合 "aborted" です。status"error" のとき、利用可能であれば error にストリームのエラーメッセージが含まれます。

プログラムによるターンをチャットエージェントの外からキャンセルするには options.signal を渡します。親のツール呼び出しが、内部生成のリクエスト ID を知らなくても子エージェントのターンをキャンセルする必要があるときに便利です。

const controller = new AbortController();

const result = await this.saveMessages(
	(messages) => [...messages, syntheticUserMessage],
	{ signal: controller.signal },
);

if (result.status === "aborted") {
	// Partial chunks already streamed are persisted.
}
const controller = new AbortController();

const result = await this.saveMessages(
	(messages) => [...messages, syntheticUserMessage],
	{ signal: controller.signal },
);

if (result.status === "aborted") {
	// Partial chunks already streamed are persisted.
}

continueLastTurn() は同じ options.signal 引数を受け付けます。AbortSignal オブジェクトは Durable Object の RPC 境界を越えられないため、saveMessages() または continueLastTurn() を呼ぶ Durable Object 内でコントローラーを構築します。シグナルはメモリ内だけです。Durable Object がターン途中でハイバネートすると、回復したターンは元のシグナルなしで動きます。キャンセルが再起動後も残る必要がある場合は、キャンセル意図を永続化します。

onChatResponse

チャットターンがアシスタントメッセージを生成して永続化したあとに呼ばれます。このフックが走る前にターンロックは解放されるため、内部から saveMessages を呼んでも安全です。アシスタントメッセージを永続化するターン経路で発火します。WebSocket チャットリクエスト、saveMessages、自動継続です。アシスタントパートを生成する前にターンが失敗した場合、エラーは元のリクエスト経由で表面化します。

export class ChatAgent extends AIChatAgent {
	async onChatResponse(result) {
		if (result.status === "completed") {
			console.log("Turn completed:", result.requestId);
		}
		if (result.status === "error") {
			console.error("Turn failed:", result.error);
		}
	}
}
import type { ChatResponseResult } from "@cloudflare/ai-chat";

export class ChatAgent extends AIChatAgent {
	protected async onChatResponse(result: ChatResponseResult) {
		if (result.status === "completed") {
			console.log("Turn completed:", result.requestId);
		}
		if (result.status === "error") {
			console.error("Turn failed:", result.error);
		}
	}
}

ChatResponseResult には次が含まれます。

フィールド 説明
message UIMessage このターンの確定したアシスタントメッセージ
requestId string このターンに関連するリクエスト ID
continuation boolean このターンが前のアシスタントターンの継続かどうか
status "completed" | "error" | "aborted" ターンの終了方法
error string | undefined status"error" のときのエラーメッセージ

sanitizeMessageForPersistence

メッセージがストレージへ永続化される前に、カスタム変換を適用するためにこのメソッドをオーバーライドします。このフックは組み込みのサニタイズ(OpenAI メタデータの除去、Anthropic のプロバイダー実行ツールペイロードの切り詰め、空の reasoning パートのフィルター)の あと に走ります。

export class ChatAgent extends AIChatAgent {
	sanitizeMessageForPersistence(message) {
		return {
			...message,
			parts: message.parts.map((part) => {
				if (
					"output" in part &&
					typeof part.output === "string" &&
					part.output.length > 1000
				) {
					return { ...part, output: "[redacted]" };
				}
				return part;
			}),
		};
	}
}
export class ChatAgent extends AIChatAgent {
	protected sanitizeMessageForPersistence(message: UIMessage): UIMessage {
		return {
			...message,
			parts: message.parts.map((part) => {
				if (
					"output" in part &&
					typeof part.output === "string" &&
					part.output.length > 1000
				) {
					return { ...part, output: "[redacted]" };
				}
				return part;
			}),
		};
	}
}

ターンライフサイクルのヘルパー

これらのメソッドは、プログラムによるターンの調整と、保留中のやり取りの待機に役立ちます。

hasPendingInteraction()

アシスタントメッセージがクライアントツールの結果または承認を待っているとき true を返します。

if (this.hasPendingInteraction()) {
	console.log("Waiting for user to approve or provide tool output");
}
if (this.hasPendingInteraction()) {
	console.log("Waiting for user to approve or provide tool output");
}

waitUntilStable()

会話が完全に安定するまで待ちます。アクティブなストリーム、保留中のクライアントツールやり取り、キュー済みの継続ターンがない状態です。安定したら true を返し、保留中のやり取りが解決する前にタイムアウトした場合は false を返します。

const stable = await this.waitUntilStable({ timeout: 30_000 });
if (stable) {
	console.log("All turns complete, safe to proceed");
}
const stable = await this.waitUntilStable({ timeout: 30_000 });
if (stable) {
	console.log("All turns complete, safe to proceed");
}

サーバー駆動のフローでは、saveMessages とあわせて特に便利です。

await this.saveMessages((messages) => [...messages, syntheticUserMessage]);
await this.waitUntilStable({ timeout: 60_000 });
// The assistant has finished responding
await this.saveMessages((messages) => [...messages, syntheticUserMessage]);
await this.waitUntilStable({ timeout: 60_000 });
// The assistant has finished responding

resetTurnState()

アクティブなターンを中止し、キュー済みの継続を無効化します。組み込みの CF_AGENT_CHAT_CLEAR ハンドラーはこれを自動で呼びますが、必要なら手動でも呼べます。

ライフサイクルフック

カスタムロジックを追加するには onConnectonClose をオーバーライドします。ストリーム再開とメッセージ同期は自動で扱われます。

export class ChatAgent extends AIChatAgent {
	async onConnect(connection, ctx) {
		// Your custom logic (e.g., logging, auth checks)
		console.log("Client connected:", connection.id);
		// Stream resumption and message sync are handled automatically
	}

	async onClose(connection, code, reason, wasClean) {
		console.log("Client disconnected:", connection.id);
		// Connection cleanup is handled automatically
	}
}
export class ChatAgent extends AIChatAgent {
	async onConnect(connection, ctx) {
		// Your custom logic (e.g., logging, auth checks)
		console.log("Client connected:", connection.id);
		// Stream resumption and message sync are handled automatically
	}

	async onClose(connection, code, reason, wasClean) {
		console.log("Client disconnected:", connection.id);
		// Connection cleanup is handled automatically
	}
}

destroy() メソッドは保留中のチャットリクエストをキャンセルし、ストリーム状態をクリーンアップします。Durable Object が退避されるときに自動で呼ばれますが、必要なら手動でも呼べます。

リクエストのキャンセル

ユーザーがチャット UI で「stop」をクリックすると、クライアントは CF_AGENT_CHAT_REQUEST_CANCEL メッセージを送ります。サーバーはこれを optionsabortSignal へ伝播します。

export class ChatAgent extends AIChatAgent {
	async onChatMessage(_onFinish, options) {
		const result = streamText({
			model: workersai("@cf/zai-org/glm-4.7-flash"),
			messages: await convertToModelMessages(this.messages),
			abortSignal: options?.abortSignal, // Pass through for cancellation
		});

		return result.toUIMessageStreamResponse();
	}
}
export class ChatAgent extends AIChatAgent {
	async onChatMessage(_onFinish, options) {
		const result = streamText({
			model: workersai("@cf/zai-org/glm-4.7-flash"),
			messages: await convertToModelMessages(this.messages),
			abortSignal: options?.abortSignal, // Pass through for cancellation
		});

		return result.toUIMessageStreamResponse();
	}
}

サブクラスは Durable Object 内からもターンをキャンセルできます。

protected abortRequest(requestId: string, reason?: unknown): void
protected abortAllRequests(): void

リクエスト ID が分かっているときは abortRequest() を使います。現在動いているターンをキャンセルすべき単一目的のヘルパーには abortAllRequests() を使います。プログラムによるターンで呼び出し元からシグナルを渡せる場合は、SaveMessagesOptions.signal を優先します。

ストリーム回復

自動ストリーム再開(useAgentChatresume オプション)は クライアント再接続回復 です。クライアントが切断して再接続したときにアクティブなストリームを再開します。Durable Object の退避は対象外です。モデル呼び出しの最中に Worker プロセスまたは Durable Object が退避されると、ストリーム自体がなくなります。その場合を durable chat recovery が扱います。

ストリーム途中の Durable Object 退避は、LLM 接続を永続的に切断します。Durable recovery はすべての AIChatAgentThink のチャットターンを runFiber() で包みます。fiber はストリーミング中の自動 keepAlive と、再起動時の回復フックを提供します。

fiber の行は退避後も SQLite に残ります。次のアクティベーションで、フレームワークは中断された fiber を検出します。バッファされたストリームチャンクから部分応答を再構築し、onChatRecovery を呼びます。

Durable recovery は常にオンです。回復予算と終了時の動作を調整するときにだけ chatRecovery を使います。

export class ChatAgent extends AIChatAgent {
	chatRecovery = {
		maxAttempts: 10,
		stableTimeoutMs: 10_000,
		terminalMessage: "The assistant was interrupted and could not recover.",
		// Primary stuck-turn bound. Resets on every progress-bearing attempt, so a
		// turn that keeps producing content survives unbounded interruption.
		noProgressTimeoutMs: 5 * 60 * 1000,
		// Runaway-loop guard. Defaults to 1,000. Set a higher value for a long
		// agentic turn, or Infinity to remove the cap.
		maxRecoveryWork: 200,
		// Caller policy consulted from the second recovery attempt onward. Return
		// false to stop recovery. This is where you enforce a token/cost budget.
		// Note: this is called as `config.shouldKeepRecovering(ctx)`, so it is not
		// bound to the agent instance — track spend in your own store keyed by the
		// incident.
		async shouldKeepRecovering(ctx) {
			return (await getSpendForTurn(ctx.recoveryRootRequestId)) < MAX_SPEND;
		},
		async onExhausted(ctx) {
			console.warn("Chat recovery exhausted", ctx.incidentId, ctx.reason);
		},
	};
}
export class ChatAgent extends AIChatAgent {
	override chatRecovery = {
		maxAttempts: 10,
		stableTimeoutMs: 10_000,
		terminalMessage: "The assistant was interrupted and could not recover.",
		// Primary stuck-turn bound. Resets on every progress-bearing attempt, so a
		// turn that keeps producing content survives unbounded interruption.
		noProgressTimeoutMs: 5 * 60 * 1000,
		// Runaway-loop guard. Defaults to 1,000. Set a higher value for a long
		// agentic turn, or Infinity to remove the cap.
		maxRecoveryWork: 200,
		// Caller policy consulted from the second recovery attempt onward. Return
		// false to stop recovery. This is where you enforce a token/cost budget.
		// Note: this is called as `config.shouldKeepRecovering(ctx)`, so it is not
		// bound to the agent instance — track spend in your own store keyed by the
		// incident.
		async shouldKeepRecovering(ctx) {
			return (await getSpendForTurn(ctx.recoveryRootRequestId)) < MAX_SPEND;
		},
		async onExhausted(ctx) {
			console.warn("Chat recovery exhausted", ctx.incidentId, ctx.reason);
		},
	};
}

chatRecovery オブジェクトは次の設定オプションを受け付けます。

フィールド デフォルト 説明
maxAttempts 10 終了枯渇前の試行上限です。前進があるとリセットされるため、健全な長いターンではなく、進捗なしの短いアラームループを捕捉します。
stableTimeoutMs 10_000 回復試行が isolate の安定状態を待つ時間です。超えると再スケジュールします。
terminalMessage 汎用メッセージ 回復を諦めたときにユーザーへ表示するメッセージです。
noProgressTimeoutMs 300_000(5 分) 主なスタックターン上限です。インシデントが前進なしでこの時間を超えると封印されます(no_progress_timeout)。進捗のある試行ごとにリセット されるため、コンテンツを出し続けるターンは無制限の中断を生き抜けます。
maxRecoveryWork 1,000 暴走ループのガードです。インシデント開始以降に生成された content/tool 単位の上限です。まだ進捗しているターンでも超えると封印されます。長いエージェントターンではより大きい値または Infinity を設定します。
maxOomRetries 3 Durable Object のメモリ上限リセットに対するリトライ予算です。最初のメモリ上限リセットのあとに止めるには 0 を設定します。
shouldKeepRecovering 2 回目の回復試行以降に参照される呼び出し元ポリシーです。false を返すと回復を止めます。トークンまたはコスト予算の強制に使います。ctx.work は粗いセグメント数でありトークンではないため、実際の消費は自分で追跡します。
onExhausted 回復を諦めたときに、終了メッセージを届ける前に一度呼ばれます。理由は ctx.reason を確認します。

ChatRecoveryProgressContextshouldKeepRecovering に渡される ctx)には次のフィールドがあります。

フィールド 説明
incidentId string この回復インシデントの安定 ID です。
requestId string 現在の継続のリクエスト ID です(チェーンされた継続ごとに変わります)。
recoveryRootRequestId string 継続チェーン全体の安定 ID です。インシデント単位の予算追跡に適したキーです。
attempt number このインシデントの試行番号です(このフックが走るときは 2 以上)。
maxAttempts number 設定された試行上限です。
recoveryKind "retry" | "continue" 未回答のユーザーターンをリトライするか、部分的なアシスタントターンを継続するかです。
work number インシデント開始以降に生成された content/tool セグメントの粗い単調カウントです(トークンではありません)。
ageMs number インシデントの最初の中断からの経過ミリ秒です。

進捗しているターンは、maxRecoveryWork 上限内である限り、繰り返しの中断を生き抜けます。回復は次のいずれかの ctx.reason 値で封印されます。

  • no_progress_timeout — 進捗なしウィンドウ内で前進がありません(スタックしたターン)。
  • max_attempts_exceeded — 進捗なしの短いアラームループに試行上限を使い切りました。
  • work_budget_exceeded — ターンはコンテンツを出し続けましたが maxRecoveryWork を超えました(暴走ループ)。
  • recovery_abortedshouldKeepRecovering フックが false を返しました。
  • out_of_memory — 回復がメモリ上限のリトライ予算を超えました。
  • stable_timeout — 安定状態を待つ回復試行がタイムアウトし続け、予算が尽きました(極端な churn)。

人待ちのターンは封印されない

ターンは、自力では解決できないクライアントやり取りで一時停止できます。サーバー execute のないクライアント側ツール呼び出し(結果をクライアントが再生する)、または approval-requested パートです。そのようなターンは人待ちであり、スタックではありません。

やり取りが保留中のあいだ、ターンはすべての回復予算から免除されます。進捗なしウィンドウ、試行上限、maxRecoveryWorkshouldKeepRecovering はすべて停止します。デプロイで中断されたプロンプトにユーザーが数分かけて答えても、封印は起きません。回復は失敗させずにターンを駐車し、ユーザーの最終的な承認またはツール結果が通常の継続経路で再開します。

この免除はクライアント専用です。飛行中に execute() が殺されたサーバーツールは真の orphan であり、免除されず、トランスクリプト修復で回復します。

終了枯渇は可観測性で監視します。

import { subscribe } from "agents/observability";

const unsubscribe = subscribe("chat", (event) => {
	if (event.type === "chat:recovery:exhausted") {
		console.error("Chat recovery exhausted", event.payload);
	}
});
import { subscribe } from "agents/observability";

const unsubscribe = subscribe("chat", (event) => {
	if (event.type === "chat:recovery:exhausted") {
		console.error("Chat recovery exhausted", event.payload);
	}
});

onChatRecovery

プロバイダー固有の回復を実装するにはオーバーライドします。デフォルトの動作は部分応答を永続化し、continueLastTurn() 経由で継続をスケジュールします。

export class ChatAgent extends AIChatAgent {
	async onChatRecovery(ctx) {
		console.log(`Recovered ${ctx.partialText.length} chars of partial text`);

		// Default: persist partial + schedule continuation
		return {};
	}
}
import type {
	ChatRecoveryContext,
	ChatRecoveryOptions,
} from "@cloudflare/ai-chat";

export class ChatAgent extends AIChatAgent {
	override async onChatRecovery(
		ctx: ChatRecoveryContext,
	): Promise<ChatRecoveryOptions> {
		console.log(`Recovered ${ctx.partialText.length} chars of partial text`);

		// Default: persist partial + schedule continuation
		return {};
	}
}

ChatRecoveryContext:

フィールド 説明
incidentId string この回復インシデントの安定 ID
attempt number このインシデントの現在の試行番号です。1 から始まります
maxAttempts number 終了枯渇前の設定された試行上限
recoveryKind "retry" | "continue" 未回答のユーザーターンをリトライするか、部分的なアシスタントターンを継続するか
streamId string 中断されたストリームの ID
requestId string 元のチャットリクエストの ID
partialText string 退避前に生成されたテキスト
partialParts MessagePart[] 退避前に生成されたメッセージパート(テキスト、reasoning、ツール呼び出し)
recoveryData unknown | null this.stash() からのデータです。完全にユーザー制御です
messages ChatMessage[] 会話履歴全体
lastBody Record<string, unknown> | undefined 元のリクエストボディ
lastClientTools ClientToolSchema[] | undefined 元のリクエストからのクライアントツールスキーマ
createdAt number 中断されたターンが始まったエポックミリ秒

ChatRecoveryOptions:

フィールド デフォルト 説明
persist true 部分応答をアシスタントメッセージとして保存します
continue true continueLastTurn() 経由で継続をスケジュールします

よく使う戻り値:

  • {} — 部分を永続化し、自動継続します(デフォルト。アシスタント prefill に対応するプロバイダーで動きます)
  • { continue: false } — 部分を永続化しますが自動継続しません(継続は自分で扱います)
  • { persist: false, continue: false } — 未確定の残りを永続化せず、すべて自分で扱います(たとえばプロバイダーから完了済み応答を取得する)

確定済みの作業は決して破棄されません。persist: false は、失う確定済みのものがない部分の永続化だけを抑制します。すでに確定済みのツール結果(完了した、しばしば非冪等な作業)を持つ部分は、設定に関係なく永続化されます。アプリが完了したツール呼び出しを誤って捨てることはなく、安全のために { persist: true } を付ける必要もありません。

ストリームチャンクが書かれる前に回復が起きると、継続する部分アシスタントメッセージはありません。最新の永続化メッセージがまだ中断ターンの未回答ユーザーメッセージなら、continuefalse でない限り、フレームワークはそのターンを自動リトライします。

古い回復をスキップするには ctx.createdAt を使います。

override async onChatRecovery(
	ctx: ChatRecoveryContext,
): Promise<ChatRecoveryOptions> {
	if (Date.now() - ctx.createdAt > 2 * 60 * 1000) {
		return { continue: false };
	}
	return {};
}

自動継続を制御する

自動継続が適切でないときでも、durable な記帳は有効のままです。

  • 別のモデル呼び出しが安全でないときは { continue: false } を返します。
  • キャンセル意図を永続化し、onChatRecovery() で読みます。
  • 外部副作用の前に冪等キーを記録します。
  • durable な消費データと回復予算を使い、コストを制限します。

continueLastTurn

保存済みのリクエストボディで onChatMessage を再呼び出しし、最後のアシスタントメッセージに追記します。応答は継続としてストリーミングされます。既存のアシスタントメッセージへの追記であり、新しいメッセージではありません。合成ユーザーメッセージは作成されません。

protected continueLastTurn(
	body?: Record<string, unknown>,
	options?: SaveMessagesOptions,
): Promise<SaveMessagesResult>;

デフォルトの回復経路から自動で呼ばれます。スケジュールされたコールバックや他のエントリポイントから手動で呼ぶこともできます。省略可能な body パラメーターは、この継続の保存済みリクエストボディを上書きします。実行中に継続をキャンセルするには options.signal を渡します。

回復データの stash

回復用のプロバイダー固有データを永続化するには、onChatMessage 内で this.stash() を使います。stash は fiber の SQLite 行に保存され、エージェント状態とは別で、onChatRecovery では ctx.recoveryData として利用できます。

export class ChatAgent extends AIChatAgent {
	async onChatMessage(_onFinish, options) {
		const result = streamText({
			model: openai("gpt-5.4"),
			messages: await convertToModelMessages(this.messages),
			providerOptions: { openai: { store: true } },
			includeRawChunks: true,
			onChunk: ({ chunk }) => {
				if (chunk.type === "raw") {
					const raw = chunk.rawValue;

					if (raw?.type === "response.created" && raw.response?.id) {
						this.stash({ responseId: raw.response.id });
					}
				}
			},
		});
		return result.toUIMessageStreamResponse();
	}
}
export class ChatAgent extends AIChatAgent {
	async onChatMessage(_onFinish, options) {
		const result = streamText({
			model: openai("gpt-5.4"),
			messages: await convertToModelMessages(this.messages),
			providerOptions: { openai: { store: true } },
			includeRawChunks: true,
			onChunk: ({ chunk }) => {
				if (chunk.type === "raw") {
					const raw = chunk.rawValue as {
						type?: string;
						response?: { id?: string };
					};
					if (raw?.type === "response.created" && raw.response?.id) {
						this.stash({ responseId: raw.response.id });
					}
				}
			},
		});
		return result.toUIMessageStreamResponse();
	}
}

プロバイダー別の回復戦略

適切な戦略は、プロバイダーがアシスタント prefill に対応しているかと、切断後に応答がサーバー側で続くかによって決まります。

プロバイダー 戦略 トークンコスト
Workers AI continueLastTurn() — アシスタント prefill でモデルが継続します
OpenAI(Responses API) ID で完了済み応答を取得します — 無駄なトークンはありません ゼロ
Anthropic 部分を永続化し、継続のために合成ユーザーメッセージを送ります

クライアントでの回復ステータス

ターンが回復中のとき、エージェントは cf_agent_chat_recovering ステータスフレームをブロードキャストします。クライアントは固まって見える代わりに「recovering…」インジケーターを出せます。回復継続がスケジュールされたときにセットされ、すべての終了結果でクリアされるため、インジケーターが永遠に回り続けることはありません。useAgentChatisRecovering フラグ経由で消費します(戻り値 を参照)。シグナルは助言的で後方互換です。理解しないクライアントは無視します。

トランスクリプト修復(orphan 化したツール呼び出しの修復。削除せずエラー結果として残すため記録が残り、モデルがツールを黙って再実行しません。プロバイダー呼び出し前に不正または欠落したツール入力を正規化します)は transcript 可観測性チャネルで発行されます。

チャット回復がより広い長時間エージェントの話にどう収まるかは、長時間エージェント: 中断された LLM ストリームの回復 を参照してください。基盤の fiber API は Durable Execution を参照してください。

クライアント API

useAgentChat

WebSocket 経由で AIChatAgent に接続する React フックです。AI SDK の useChat をネイティブ WebSocket トランスポートで包みます。

import { useAgent } from "agents/react";
import { useAgentChat } from "@cloudflare/ai-chat/react";

function Chat() {
	const agent = useAgent({ agent: "ChatAgent" });
	const {
		messages,
		sendMessage,
		clearHistory,
		addToolOutput,
		addToolApprovalResponse,
		setMessages,
		status,
		isStreaming,
		isServerStreaming,
		isToolContinuation,
		isRecovering,
	} = useAgentChat({ agent });

	// ...
}
import { useAgent } from "agents/react";
import { useAgentChat } from "@cloudflare/ai-chat/react";

function Chat() {
	const agent = useAgent({ agent: "ChatAgent" });
	const {
		messages,
		sendMessage,
		clearHistory,
		addToolOutput,
		addToolApprovalResponse,
		setMessages,
		status,
		isStreaming,
		isServerStreaming,
		isToolContinuation,
		isRecovering,
	} = useAgentChat({ agent });

	// ...
}

オプション

オプション デフォルト 説明
agent ReturnType<typeof useAgent> 必須 useAgent からのエージェント接続
onToolCall ({ toolCall, addToolOutput }) => void クライアント側ツール実行を扱います
tools Record<string, AITool> 高度: ブラウザーからクライアント実行ツールを動的に登録します
autoContinueAfterToolResult boolean true クライアントツールの結果と承認のあと、会話を自動継続します
resume boolean true 再接続時の自動ストリーム再開を有効にします
cancelOnClientAbort boolean false 一般的なクライアントストリームの abort またはクリーンアップ時にサーバーテンをキャンセルします
body object | () => object すべてのリクエストと一緒に送るカスタムデータ
prepareSendMessagesRequest (options) => { body?, headers? } 高度なリクエスト単位のカスタマイズ
getInitialMessages (options) => Promise<UIMessage[]> または null カスタムの初期メッセージローダーです。HTTP fetch を完全にスキップするには null にします(messages を直接渡すときに便利です)
syncMessagesToServer boolean true true のとき、setMessages はトランスクリプトをサーバーへ送ります。サーバー権威のトランスクリプトストレージを持つホストでは false にし、setMessages がローカルビューだけを更新するようにします

戻り値

プロパティ 説明
messages UIMessage[] 現在の会話メッセージ
sendMessage (message) => void メッセージを送ります
clearHistory () => void 会話をクリアします(クライアントとサーバー)
addToolOutput ({ toolCallId, output }) => void クライアント側ツールの出力を提供します
addToolApprovalResponse ({ id, approved }) => void 承認が必要なツールを承認または拒否します
setMessages (messages | updater) => void メッセージを直接設定します(サーバーへ同期します)
status string "ready""submitted""streaming"、または "error"
isStreaming boolean エージェントがストリーミング中、またはアクティブなクライアントツール待ちのとき true
isServerStreaming boolean サーバー開始のストリームまたはアクティブなクライアントツールフェーズ進行中のとき true
isToolContinuation boolean ツール結果または承認のあとの自動継続が動いているとき true
isRecovering boolean durable ターンが回復中(中断され再開中)のとき true です。isStreaming とは別です。回復中のターンはまだトークンを出していません。「recovering…」ヒントを描画します。ほとんどの UI は isStreaming || isRecovering を「busy」として扱います

UI が新しいユーザー送信と、ツール結果のあとの継続を区別すべきときは isToolContinuation を使います。たとえば、タイピングインジケーターは status === "submitted" && !isToolContinuation のときだけ表示し、isStreaming が true のときは読み込みコントロールを無効のままにします。

非 React クライアント

useAgentChat は React 専用です。Vue、Svelte、またはバニラ JavaScript では、agents/chat/transportWebSocketChatTransport をエクスポートし、AgentClient の WebSocket 接続を AI SDK のトランスポートインターフェイスに適応します。このエントリポイントに React の peer dependency は不要です。

import { useChat } from "@ai-sdk/vue";
import { AgentClient } from "agents/client";
import { WebSocketChatTransport } from "agents/chat/transport";

const agent = new AgentClient({
	agent: "ChatAgent",
	name: "user-123",
	host: window.location.host,
});

const { messages, sendMessage, status } = useChat({
	transport: new WebSocketChatTransport({
		agent,
		cancelOnClientAbort: true,
	}),
});
import { useChat } from "@ai-sdk/vue";
import { AgentClient } from "agents/client";
import { WebSocketChatTransport } from "agents/chat/transport";

const agent = new AgentClient({
	agent: "ChatAgent",
	name: "user-123",
	host: window.location.host,
});

const { messages, sendMessage, status } = useChat({
	transport: new WebSocketChatTransport({
		agent,
		cancelOnClientAbort: true,
	}),
});

トランスポートは新しいターン、再生成されたターン、ストリームキャンセルをカバーします。useAgentChat より低レベルのプリミティブです。永続化済み履歴の読み込み、再接続後の自動ストリーム再開、タブ横断のトランスクリプト同期、クライアント側ツール継続は React フックの責任のままです。クライアントが必要とするものをトランスポートの上に実装します。

vue-chat の例 は最小の Vue クライアントを示し、比較用の完全な React 統合は ai-chat を参照してください。

ツール

AIChatAgent は 3 つのツールパターンに対応し、いずれも AI SDK の tool() 関数を使います。

パターン 実行場所 使う場面
サーバー側 サーバー(自動) API 呼び出し、データベースクエリ、計算
クライアント側 ブラウザー(onToolCall 経由) 位置情報、クリップボード、カメラ、ローカルストレージ
承認 サーバー(ユーザー承認後) 支払い、削除、外部アクション

サーバー側ツール

execute 関数を持つツールはサーバー上で自動実行されます。

import { streamText, convertToModelMessages, tool, stepCountIs } from "ai";
import { z } from "zod";
export class ChatAgent extends AIChatAgent {
	async onChatMessage() {
		const workersai = createWorkersAI({ binding: this.env.AI });

		const result = streamText({
			model: workersai("@cf/zai-org/glm-4.7-flash"),
			messages: await convertToModelMessages(this.messages),
			tools: {
				getWeather: tool({
					description: "Get weather for a city",
					inputSchema: z.object({ city: z.string() }),
					execute: async ({ city }) => {
						const data = await fetchWeather(city);
						return { temperature: data.temp, condition: data.condition };
					},
				}),
			},
			stopWhen: stepCountIs(5),
		});

		return result.toUIMessageStreamResponse();
	}
}
import { streamText, convertToModelMessages, tool, stepCountIs } from "ai";
import { z } from "zod";
export class ChatAgent extends AIChatAgent {
	async onChatMessage() {
		const workersai = createWorkersAI({ binding: this.env.AI });

		const result = streamText({
			model: workersai("@cf/zai-org/glm-4.7-flash"),
			messages: await convertToModelMessages(this.messages),
			tools: {
				getWeather: tool({
					description: "Get weather for a city",
					inputSchema: z.object({ city: z.string() }),
					execute: async ({ city }) => {
						const data = await fetchWeather(city);
						return { temperature: data.temp, condition: data.condition };
					},
				}),
			},
			stopWhen: stepCountIs(5),
		});

		return result.toUIMessageStreamResponse();
	}
}

クライアント側ツール

サーバーで execute なしのツールを定義し、クライアントで onToolCall で扱います。ブラウザー API が必要なツールに使います。

サーバー:

tools: {
	getLocation: tool({
		description: "Get the user's location from the browser",
		inputSchema: z.object({}),
		// No execute — the client handles it
	});
}
tools: {
	getLocation: tool({
		description: "Get the user's location from the browser",
		inputSchema: z.object({}),
		// No execute — the client handles it
	});
}

クライアント:

const { messages, sendMessage } = useAgentChat({
	agent,
	onToolCall: async ({ toolCall, addToolOutput }) => {
		if (toolCall.toolName === "getLocation") {
			const pos = await new Promise((resolve, reject) =>
				navigator.geolocation.getCurrentPosition(resolve, reject),
			);
			addToolOutput({
				toolCallId: toolCall.toolCallId,
				output: { lat: pos.coords.latitude, lng: pos.coords.longitude },
			});
		}
	},
});
const { messages, sendMessage } = useAgentChat({
	agent,
	onToolCall: async ({ toolCall, addToolOutput }) => {
		if (toolCall.toolName === "getLocation") {
			const pos = await new Promise((resolve, reject) =>
				navigator.geolocation.getCurrentPosition(resolve, reject),
			);
			addToolOutput({
				toolCallId: toolCall.toolCallId,
				output: { lat: pos.coords.latitude, lng: pos.coords.longitude },
			});
		}
	},
});

LLM が getLocation を呼び出すと、ストリームは一時停止します。onToolCall コールバックが発火し、コードが出力を提供し、会話が続きます。

ブラウザーが実行時に利用可能なツールを決める SDK またはプラットフォームでは、useAgentChattools オブジェクトを渡します。クライアント側 execute 関数を持つツールはシリアライズされ、サーバーへ自動送信されます。サーバーでは、この動的ツールパターン向けに options.clientToolscreateToolsFromClientSchemas() が引き続き対応しています。

ツール承認(ヒューマンインザループ)

実行前にユーザー確認が必要なツールには needsApproval を使います。

サーバー:

tools: {
	processPayment: tool({
		description: "Process a payment",
		inputSchema: z.object({
			amount: z.coerce.number(),
			recipient: z.string(),
		}),
		needsApproval: async ({ amount }) => amount > 100,
		execute: async ({ amount, recipient }) => charge(amount, recipient),
	});
}

クライアント:

import { getToolName, isToolUIPart } from "ai";
import {
	getToolApproval,
	getToolCallId,
	getToolPartState,
} from "@cloudflare/ai-chat/react";

const { messages, addToolApprovalResponse } = useAgentChat({ agent });

// Render pending approvals from message parts
{
	messages.map((msg) =>
		msg.parts
			.filter(
				(part) =>
					isToolUIPart(part) && getToolPartState(part) === "waiting-approval",
			)
			.map((part) => (
				<div key={getToolCallId(part)}>
					<p>Approve {getToolName(part)}?</p>
					<button
						onClick={() => {
							const approval = getToolApproval(part);
							if (!approval) return;
							addToolApprovalResponse({
								id: approval.id,
								approved: true,
							});
						}}
					>
						Approve
					</button>
					<button
						onClick={() => {
							const approval = getToolApproval(part);
							if (!approval) return;
							addToolApprovalResponse({
								id: approval.id,
								approved: false,
							});
						}}
					>
						Reject
					</button>
				</div>
			)),
	);
}

addToolOutput によるカスタム拒否メッセージ

ユーザーがツールを拒否すると、addToolApprovalResponse({ id, approved: false }) はツール状態を汎用メッセージ付きの output-denied に設定します。拒否のより具体的な理由を LLM に伝えるには、代わりに state: "output-error"addToolOutput を使います。

const { addToolOutput } = useAgentChat({ agent });

// Reject with a custom error message
addToolOutput({
	toolCallId: part.toolCallId,
	state: "output-error",
	errorText: "User declined: insufficient budget for this quarter",
});
const { addToolOutput } = useAgentChat({ agent });

// Reject with a custom error message
addToolOutput({
	toolCallId: part.toolCallId,
	state: "output-error",
	errorText: "User declined: insufficient budget for this quarter",
});

これはカスタムエラーテキスト付きの tool_result を LLM へ送り、適切に応答できるようにします(たとえば代替案の提案や確認質問)。

addToolApprovalResponseapproved: false)は、autoContinueAfterToolResult が有効なとき(デフォルト)会話を自動継続します。state: "output-error"addToolOutput は自動継続し ません。LLM にエラーへ応答させたい場合は、あとで sendMessage() を呼びます。

その他のパターンは ヒューマンインザループ を参照してください。

カスタムリクエストデータ

すべてのチャットリクエストにカスタムデータを含めるには、body オプションを使います。

const { messages, sendMessage } = useAgentChat({
	agent,
	body: {
		timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
		userId: currentUser.id,
	},
});
const { messages, sendMessage } = useAgentChat({
	agent,
	body: {
		timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
		userId: currentUser.id,
	},
});

動的な値には関数を使います。

body: () => ({
	token: getAuthToken(),
	timestamp: Date.now(),
});
body: () => ({
	token: getAuthToken(),
	timestamp: Date.now(),
});

サーバーでこれらのフィールドにアクセスします。

export class ChatAgent extends AIChatAgent {
	async onChatMessage(_onFinish, options) {
		const { timezone, userId } = options?.body ?? {};
		// ...
	}
}
export class ChatAgent extends AIChatAgent {
	async onChatMessage(_onFinish, options) {
		const { timezone, userId } = options?.body ?? {};
		// ...
	}
}

高度なリクエスト単位のカスタマイズ(カスタムヘッダー、リクエストごとの異なる body)には prepareSendMessagesRequest を使います。

const { messages, sendMessage } = useAgentChat({
	agent,
	prepareSendMessagesRequest: async ({ messages, trigger }) => ({
		headers: { Authorization: `Bearer ${await getToken()}` },
		body: { requestedAt: Date.now() },
	}),
});
const { messages, sendMessage } = useAgentChat({
	agent,
	prepareSendMessagesRequest: async ({ messages, trigger }) => ({
		headers: { Authorization: `Bearer ${await getToken()}` },
		body: { requestedAt: Date.now() },
	}),
});

Data parts

Data parts を使うと、テキストとあわせて型付き JSON をメッセージに付けられます。進捗インジケーター、ソース引用、トークン利用量、UI が必要とする任意の構造化データです。

Data parts を書く(サーバー)

サーバーから data parts を送るには、createUIMessageStreamwriter.write() を使います。

import {
	streamText,
	convertToModelMessages,
	createUIMessageStream,
	createUIMessageStreamResponse,
} from "ai";

export class ChatAgent extends AIChatAgent {
	async onChatMessage() {
		const workersai = createWorkersAI({ binding: this.env.AI });

		const stream = createUIMessageStream({
			execute: async ({ writer }) => {
				const result = streamText({
					model: workersai("@cf/zai-org/glm-4.7-flash"),
					messages: await convertToModelMessages(this.messages),
				});

				// Merge the LLM stream
				writer.merge(result.toUIMessageStream());

				// Write a data part — persisted to message.parts
				writer.write({
					type: "data-sources",
					id: "src-1",
					data: { query: "agents", status: "searching", results: [] },
				});

				// Later: update the same part in-place (same type + id)
				writer.write({
					type: "data-sources",
					id: "src-1",
					data: {
						query: "agents",
						status: "found",
						results: ["Agents SDK docs", "Durable Objects guide"],
					},
				});
			},
		});

		return createUIMessageStreamResponse({ stream });
	}
}
import {
	streamText,
	convertToModelMessages,
	createUIMessageStream,
	createUIMessageStreamResponse,
} from "ai";

export class ChatAgent extends AIChatAgent {
	async onChatMessage() {
		const workersai = createWorkersAI({ binding: this.env.AI });

		const stream = createUIMessageStream({
			execute: async ({ writer }) => {
				const result = streamText({
					model: workersai("@cf/zai-org/glm-4.7-flash"),
					messages: await convertToModelMessages(this.messages),
				});

				// Merge the LLM stream
				writer.merge(result.toUIMessageStream());

				// Write a data part — persisted to message.parts
				writer.write({
					type: "data-sources",
					id: "src-1",
					data: { query: "agents", status: "searching", results: [] },
				});

				// Later: update the same part in-place (same type + id)
				writer.write({
					type: "data-sources",
					id: "src-1",
					data: {
						query: "agents",
						status: "found",
						results: ["Agents SDK docs", "Durable Objects guide"],
					},
				});
			},
		});

		return createUIMessageStreamResponse({ stream });
	}
}

3 つのパターン

パターン 方法 永続化? 用途
Reconciliation 同じ type + id → その場で更新 はい 段階的な状態(searching → found)
Append id なし、または異なる id → 追記 はい ログエントリ、複数の引用
Transient transient: truemessage.parts に追加しない いいえ 一時的なステータス(thinking インジケーター)

Transient parts は接続中のクライアントへリアルタイムで配信されますが、SQLite 永続化と message.parts からは除外されます。消費するには onData コールバックを使います。

Data parts を読む(クライアント)

非 Transient の data parts は message.parts に現れます。型付けするには UIMessage generic を使います。

import { useAgentChat } from "@cloudflare/ai-chat/react";

const { messages } = useAgentChat({ agent });

// Typed access — no casts needed
for (const msg of messages) {
	for (const part of msg.parts) {
		if (part.type === "data-sources") {
			console.log(part.data.results); // string[]
		}
	}
}
import { useAgentChat } from "@cloudflare/ai-chat/react";
import type { UIMessage } from "ai";

type ChatMessage = UIMessage<
	unknown,
	{
		sources: { query: string; status: string; results: string[] };
		usage: { model: string; inputTokens: number; outputTokens: number };
	}
>;

const { messages } = useAgentChat<unknown, ChatMessage>({ agent });

// Typed access — no casts needed
for (const msg of messages) {
	for (const part of msg.parts) {
		if (part.type === "data-sources") {
			console.log(part.data.results); // string[]
		}
	}
}

onData による Transient parts

Transient の data parts は message.parts にありません。代わりに onData コールバックを使います。

const [thinking, setThinking] = useState(false);

const { messages } = useAgentChat({
	agent,
	onData(part) {
		if (part.type === "data-thinking") {
			setThinking(true);
		}
	},
});
const [thinking, setThinking] = useState(false);

const { messages } = useAgentChat<unknown, ChatMessage>({
	agent,
	onData(part) {
		if (part.type === "data-thinking") {
			setThinking(true);
		}
	},
});

サーバーでは、transient: true で transient parts を書き込みます。

writer.write({
	transient: true,
	type: "data-thinking",
	data: { model: "glm-4.7-flash", startedAt: new Date().toISOString() },
});
writer.write({
	transient: true,
	type: "data-thinking",
	data: { model: "glm-4.7-flash", startedAt: new Date().toISOString() },
});

onData はすべてのコードパスで発火します。新しいメッセージ、ストリーム再開、タブ間ブロードキャストです。

再開可能なストリーミング

クライアントが切断して再接続すると、ストリームは自動的に再開します。設定は不要で、そのまま動作します。

ストリーミング中は次のように動きます。

  1. 生成されたチャンクはすべて SQLite にバッファされます
  2. クライアントが切断しても、サーバーはストリーミングとバッファを続けます
  3. クライアントが再接続すると、バッファ済みチャンクを受け取り、ライブストリーミングを再開します

汎用のクライアントストリーム abort やクリーンアップは、デフォルトではブラウザ内に留まります。サーバーターンは動き続け、あとから再開できます。明示的に stop() を呼ぶとサーバーターンはキャンセルされます。

const { messages, stop } = useAgentChat({ agent });

return <button onClick={stop}>Stop</button>;
const { messages, stop } = useAgentChat({ agent });

return <button onClick={stop}>Stop</button>;

ブラウザのライフサイクルにサーバーのライフサイクルを意図的に合わせたい場合(リクエスト寿命やトークン節約のフローなど)は、cancelOnClientAbort: true を設定します。明示的な stop() はこのオプションに関係なく、常にサーバー作業をキャンセルします。

無効化するには resume: false を使います。

const { messages } = useAgentChat({ agent, resume: false });
const { messages } = useAgentChat({ agent, resume: false });

ストレージ管理

行サイズの保護

Workers SQLite の行には 2 MB の上限があります。この上限を超えないよう、AIChatAgent はシリアライズ済みメッセージがおよそ 1.8 MB に達すると圧縮を始めます。たとえばツールが非常に大きな出力を返したときです。

  1. ツール出力の圧縮 — 大きなツール出力は、ツールの再実行を提案するようモデルへ指示する LLM 向け要約に置き換えられます
  2. テキストの切り詰め — ツール圧縮後もメッセージが大きすぎる場合、テキスト parts は注記付きで切り詰められます

圧縮されたメッセージには metadata.compactedToolOutputs が含まれます。クライアントはこれを検出して、適切に表示できます。

LLM コンテキストとストレージの制御

ストレージ(maxPersistedMessages)と LLM コンテキストは独立しています。

対象 制御 範囲
SQLite が保存するメッセージ数 maxPersistedMessages 永続化
モデルが見る内容 pruneMessages() LLM コンテキスト
行サイズ制限 自動圧縮 メッセージごと
export class ChatAgent extends AIChatAgent {
	async onChatMessage() {
		const result = streamText({
			model: workersai("@cf/zai-org/glm-4.7-flash"),
			messages: pruneMessages({
				// LLM context limit
				messages: await convertToModelMessages(this.messages),
				reasoning: "before-last-message",
				toolCalls: "before-last-2-messages",
			}),
		});

		return result.toUIMessageStreamResponse();
	}
}
export class ChatAgent extends AIChatAgent {
	async onChatMessage() {
		const result = streamText({
			model: workersai("@cf/zai-org/glm-4.7-flash"),
			messages: pruneMessages({
				// LLM context limit
				messages: await convertToModelMessages(this.messages),
				reasoning: "before-last-message",
				toolCalls: "before-last-2-messages",
			}),
		});

		return result.toUIMessageStreamResponse();
	}
}

別の AI プロバイダーを使う

AIChatAgent は、AI SDK 互換の任意のプロバイダーで動作します。使うモデルはサーバーコードが決めます。クライアント側で手動変更する必要はありません。

Workers AI (Cloudflare)

import { createWorkersAI } from "workers-ai-provider";

const workersai = createWorkersAI({ binding: this.env.AI });
const result = streamText({
	model: workersai("@cf/zai-org/glm-4.7-flash"),
	messages: await convertToModelMessages(this.messages),
});
import { createWorkersAI } from "workers-ai-provider";

const workersai = createWorkersAI({ binding: this.env.AI });
const result = streamText({
	model: workersai("@cf/zai-org/glm-4.7-flash"),
	messages: await convertToModelMessages(this.messages),
});

OpenAI

import { createOpenAI } from "@ai-sdk/openai";

const openai = createOpenAI({ apiKey: this.env.OPENAI_API_KEY });
const result = streamText({
	model: openai.chat("gpt-4o"),
	messages: await convertToModelMessages(this.messages),
});
import { createOpenAI } from "@ai-sdk/openai";

const openai = createOpenAI({ apiKey: this.env.OPENAI_API_KEY });
const result = streamText({
	model: openai.chat("gpt-4o"),
	messages: await convertToModelMessages(this.messages),
});

Anthropic

import { createAnthropic } from "@ai-sdk/anthropic";

const anthropic = createAnthropic({ apiKey: this.env.ANTHROPIC_API_KEY });
const result = streamText({
	model: anthropic("claude-sonnet-4-20250514"),
	messages: await convertToModelMessages(this.messages),
});
import { createAnthropic } from "@ai-sdk/anthropic";

const anthropic = createAnthropic({ apiKey: this.env.ANTHROPIC_API_KEY });
const result = streamText({
	model: anthropic("claude-sonnet-4-20250514"),
	messages: await convertToModelMessages(this.messages),
});

高度なパターン

onChatMessagestreamText 呼び出しを完全に制御できるので、任意の AI SDK 機能を直接使えます。以下のパターンはそのまま動作します。特別な AIChatAgent 設定は不要です。

動的なモデルとツールの制御

マルチステップのエージェントループで、ステップ間にモデル、利用可能なツール、システムプロンプトを変えるには prepareStep を使います。

import { streamText, convertToModelMessages, tool, stepCountIs } from "ai";
import { z } from "zod";

export class ChatAgent extends AIChatAgent {
	async onChatMessage() {
		const result = streamText({
			model: cheapModel, // Default model for simple steps
			messages: await convertToModelMessages(this.messages),
			tools: {
				search: searchTool,
				analyze: analyzeTool,
				summarize: summarizeTool,
			},
			stopWhen: stepCountIs(10),
			prepareStep: async ({ stepNumber, messages }) => {
				// Phase 1: Search (steps 0-2)
				if (stepNumber <= 2) {
					return {
						activeTools: ["search"],
						toolChoice: "required", // Force tool use
					};
				}

				// Phase 2: Analyze with a stronger model (steps 3-5)
				if (stepNumber <= 5) {
					return {
						model: expensiveModel,
						activeTools: ["analyze"],
					};
				}

				// Phase 3: Summarize
				return { activeTools: ["summarize"] };
			},
		});

		return result.toUIMessageStreamResponse();
	}
}
import { streamText, convertToModelMessages, tool, stepCountIs } from "ai";
import { z } from "zod";

export class ChatAgent extends AIChatAgent {
	async onChatMessage() {
		const result = streamText({
			model: cheapModel, // Default model for simple steps
			messages: await convertToModelMessages(this.messages),
			tools: {
				search: searchTool,
				analyze: analyzeTool,
				summarize: summarizeTool,
			},
			stopWhen: stepCountIs(10),
			prepareStep: async ({ stepNumber, messages }) => {
				// Phase 1: Search (steps 0-2)
				if (stepNumber <= 2) {
					return {
						activeTools: ["search"],
						toolChoice: "required", // Force tool use
					};
				}

				// Phase 2: Analyze with a stronger model (steps 3-5)
				if (stepNumber <= 5) {
					return {
						model: expensiveModel,
						activeTools: ["analyze"],
					};
				}

				// Phase 3: Summarize
				return { activeTools: ["summarize"] };
			},
		});

		return result.toUIMessageStreamResponse();
	}
}

prepareStep は各ステップの前に実行され、modelactiveToolstoolChoicesystemmessages の上書きを返せます。次の用途に使います。

  • モデルの切り替え — 単純なステップは安いモデル、推論は上位モデル
  • ツールの段階化 — 各ステップで使えるツールを制限する
  • コンテキスト管理 — トークン上限内に収めるためメッセージを刈り込み、または変換する
  • ツール呼び出しの強制 — 特定ツールを必須にするには toolChoice: { type: "tool", toolName: "search" } を使う

言語モデルミドルウェア

チャットロジックを変えずにガードレール、RAG、キャッシュ、ログを追加するには wrapLanguageModel を使います。

import { streamText, convertToModelMessages, wrapLanguageModel } from "ai";

const guardrailMiddleware = {
	wrapGenerate: async ({ doGenerate }) => {
		const { text, ...rest } = await doGenerate();
		// Filter PII or sensitive content from the response
		const cleaned = text?.replace(/\b\d{3}-\d{2}-\d{4}\b/g, "[REDACTED]");
		return { text: cleaned, ...rest };
	},
};

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();
	}
}
import { streamText, convertToModelMessages, wrapLanguageModel } from "ai";
import type { LanguageModelV3Middleware } from "@ai-sdk/provider";

const guardrailMiddleware: LanguageModelV3Middleware = {
	wrapGenerate: async ({ doGenerate }) => {
		const { text, ...rest } = await doGenerate();
		// Filter PII or sensitive content from the response
		const cleaned = text?.replace(/\b\d{3}-\d{2}-\d{4}\b/g, "[REDACTED]");
		return { text: cleaned, ...rest };
	},
};

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();
	}
}

AI SDK には組み込みミドルウェアがあります。

  • extractReasoningMiddleware — DeepSeek R1 などのモデルから chain-of-thought を取り出す
  • defaultSettingsMiddleware — デフォルトの temperature、max tokens などを適用する
  • simulateStreamingMiddleware — 非ストリーミングモデルにストリーミングを足す

複数のミドルウェアは順番に合成されます。middleware: [first, second]first(second(model)) として適用されます。

構造化出力

構造化データの抽出には、ツール内で generateObject を使います。

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();
	}
}

プロセス内サブエージェントへの委譲

ツールは、独自のコンテキストを持つ焦点を絞ったサブ呼び出しに作業を委譲できます。再利用可能なエージェントを定義するには ToolLoopAgent を使い、ツールの 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();
	}
}

リサーチエージェントは独自のコンテキストで動きます。トークン予算はオーケストレーターと別です。親モデルに戻るのは要約だけです。

予備結果で進捗をストリーミングする

デフォルトでは、execute が返るまでツール part は読み込み中として表示されます。ツールの作業中にクライアントへ進捗をストリーミングするには、非同期ジェネレーター(async function*)を使います。

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 };
	},
});

yield はクライアント上のツール part をリアルタイムで更新します(preliminary: true)。最後に yield した値が、モデルが見る最終出力になります。

このパターンは次のときに役立ちます。

  • 大量の情報探索が必要で、メインコンテキストを膨らませたくないとき
  • 長時間ツールのリアルタイム進捗を見せたいとき
  • 独立したリサーチを並列化したいとき(複数のツール呼び出しは同時に動きます)
  • サブタスクごとに異なるモデルやシステムプロンプトが必要なとき

詳細は AI SDK Agents ドキュメントSubagentsPreliminary Tool Results を参照してください。

複数クライアントの同期

複数のクライアントが同じエージェントインスタンスに接続すると、メッセージはすべての接続へ自動ブロードキャストされます。1 つのクライアントがメッセージを送ると、接続中の他のクライアントは更新されたメッセージ一覧を受け取ります。

Client A ──── sendMessage("Hello") ────▶ AIChatAgent

                                        persist + stream

Client A ◀── CF_AGENT_USE_CHAT_RESPONSE ──────┤
Client B ◀── CF_AGENT_CHAT_MESSAGES ──────────┘

発信元のクライアントはストリーミング応答を受け取ります。他のクライアントは CF_AGENT_CHAT_MESSAGES ブロードキャストで最終メッセージを受け取ります。

API リファレンス

エクスポート

インポートパス エクスポート
@cloudflare/ai-chat AIChatAgentcreateToolsFromClientSchemasClientToolSchemaChatRecoveryContextChatRecoveryOptionsChatRecoveryConfigChatRecoveryExhaustedContextResolvedChatRecoveryConfig、ライフサイクル型
@cloudflare/ai-chat/react useAgentChatextractClientToolSchemasgetToolPartStategetToolCallIdgetToolInputgetToolOutputgetToolApproval
@cloudflare/ai-chat/types MessageTypeOutgoingMessageIncomingMessage
agents/chat SaveMessagesResultSaveMessagesOptionsCHAT_MESSAGE_TYPESROW_MAX_BYTESisReplayChunk() などの共有の高度なチャットプリミティブ
agents/chat/transport 非 React クライアント向けの WebSocketChatTransport とその AgentConnection 接続型

WebSocket プロトコル

チャットプロトコルは、WebSocket 上の型付き JSON メッセージを使います。

メッセージ 方向 目的
CF_AGENT_USE_CHAT_REQUEST クライアント → サーバー チャットメッセージを送る
CF_AGENT_USE_CHAT_RESPONSE サーバー → クライアント 応答チャンクをストリームする
CF_AGENT_CHAT_MESSAGES サーバー → クライアント 更新されたメッセージをブロードキャストする
CF_AGENT_CHAT_CLEAR 双方向 会話をクリアする
CF_AGENT_CHAT_REQUEST_CANCEL クライアント → サーバー 進行中のストリームをキャンセルする
CF_AGENT_TOOL_RESULT クライアント → サーバー ツール出力を提供する
CF_AGENT_TOOL_APPROVAL クライアント → サーバー ツールを承認または拒否する
CF_AGENT_MESSAGE_UPDATED サーバー → クライアント メッセージ更新を通知する
CF_AGENT_STREAM_RESUMING サーバー → クライアント ストリーム再開を通知する
CF_AGENT_STREAM_RESUME_REQUEST クライアント → サーバー ストリーム再開チェックを要求する
CF_AGENT_STREAM_RESUME_ACK サーバー → クライアント カーソルからストリームを再開する
CF_AGENT_STREAM_RESUME_NONE サーバー → クライアント 再開可能なストリームがない

非推奨 API

次の API は非推奨です。使用するとコンソール警告が出ます。将来のリリースで削除されます。

非推奨 置き換え 備考
addToolResult({ toolCallId, result }) addToolOutput({ toolCallId, output }) AI SDK の用語に合わせた名前変更
detectToolsRequiringConfirmation() ツール定義の needsApproval を使う 承認はグローバルフィルターではなく、ツール単位になりました
toolsRequiringConfirmation オプション 個別ツールの needsApproval を使う グローバル一覧の代わりにツール単位の承認

以前のバージョンからアップグレードする場合は、非推奨の呼び出しを置き換え先に変えます。非推奨 API はまだ動きますが、将来のメジャーバージョンで削除されます。

createToolsFromClientSchemas()extractClientToolSchemas()useAgentChattools オプションは、動的なクライアント側ツール向けに引き続きサポートされます。高度な API であり、非推奨 API ではありません。

次のステップ

耐久実行

長時間作業向けの runFiber()、stash()、クラッシュ回復。

役に立ちましたか?