Think
@cloudflare/think 是面向 Cloudflare Workers 的、有明确主张的 chat agent 基类。它处理完整的聊天生命周期——agentic loop、消息持久化、流式传输、工具执行、客户端工具、流恢复和扩展——全部由 Durable Object SQLite 支撑。
Think 既可以作为顶层 agent(通过 useAgentChat 与浏览器客户端进行 WebSocket 聊天),也可以作为子 agent(从父 agent 通过 chat() 进行 RPC 流式传输)。
快速开始
安装
Terminal window
npm install @cloudflare/think @cloudflare/ai-chat agents ai @cloudflare/shell zod workers-ai-provider
服务端
JavaScript
import { Think } from "@cloudflare/think";
import { createWorkersAI } from "workers-ai-provider";
import { routeAgentRequest } from "agents";
export class MyAgent extends Think {
getModel() {
return createWorkersAI({ binding: this.env.AI })(
"@cf/moonshotai/kimi-k2.5",
);
}
}
export default {
async fetch(request, env) {
return (
(await routeAgentRequest(request, env)) ||
new Response("Not found", { status: 404 })
);
},
};
TypeScript
import { Think } from "@cloudflare/think";
import { createWorkersAI } from "workers-ai-provider";
import { routeAgentRequest } from "agents";
export class MyAgent extends Think<Env> {
getModel() {
return createWorkersAI({ binding: this.env.AI })(
"@cf/moonshotai/kimi-k2.5",
);
}
}
export default {
async fetch(request: Request, env: Env) {
return (
(await routeAgentRequest(request, env)) ||
new Response("Not found", { status: 404 })
);
},
} satisfies ExportedHandler<Env>;
就这些。Think 处理了 WebSocket 聊天协议、消息持久化、agentic loop、消息净化、流恢复、客户端工具支持以及工作区文件工具。
客户端
JavaScript
import { useAgent } from "agents/react";
import { useAgentChat } from "@cloudflare/ai-chat/react";
function Chat() {
const agent = useAgent({ agent: "MyAgent" });
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="Send a message..." />
<button type="submit">Send</button>
</form>
</div>
);
}
TypeScript
import { useAgent } from "agents/react";
import { useAgentChat } from "@cloudflare/ai-chat/react";
function Chat() {
const agent = useAgent({ agent: "MyAgent" });
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="Send a message..." />
<button type="submit">Send</button>
</form>
</div>
);
}
配置
JSONC
{
"$schema": "./node_modules/wrangler/config-schema.json",
// Set this to today's date
"compatibility_date": "2026-04-29",
"compatibility_flags": [
"nodejs_compat",
"experimental"
],
"ai": {
"binding": "AI"
},
"durable_objects": {
"bindings": [
{
"class_name": "MyAgent",
"name": "MyAgent"
}
]
},
"migrations": [
{
"new_sqlite_classes": [
"MyAgent"
],
"tag": "v1"
}
]
}
TOML
# Set this to today's date
compatibility_date = "2026-04-29"
compatibility_flags = ["nodejs_compat", "experimental"]
[ai]
binding = "AI"
[[durable_objects.bindings]]
class_name = "MyAgent"
name = "MyAgent"
[[migrations]]
new_sqlite_classes = ["MyAgent"]
tag = "v1"
Think 与 AIChatAgent 对比
Think 和 AIChatAgent 都继承自 Agent,使用同样的 cf_agent_chat_* WebSocket 协议。它们的目标不同。
AIChatAgent 是一个协议适配层。你需要重写 onChatMessage,并自行调用 streamText、连接工具、转换消息并返回 Response。AIChatAgent 处理底层管道——消息持久化、流式传输、abort、resume——但 LLM 调用完全由你负责。
Think 是一个有主见的框架。它替你做出了决定:getModel() 返回模型,getSystemPrompt() 或 configureSession() 设置提示词,getTools() 返回工具。默认的 onChatMessage 运行完整的 agentic loop。你重写的是单个组件,而不是整条管道。
| 关注点 | AIChatAgent | Think |
|---|---|---|
| 最小子类 | ~15 行(连接 streamText + 工具 + 系统提示词 + 响应) | 3 行(只需 getModel()) |
| 存储 | 扁平的 SQL 表 | Session:树状消息、上下文块、压缩、FTS5 |
| 重新生成 | 破坏式(旧响应被删除) | 非破坏式分支(旧响应被保留) |
| 上下文管理 | 手动 | 上下文块 + LLM 可写的持久化记忆 |
| 子 agent RPC | 未内置 | chat() 与 StreamCallback |
| 程序化轮次 | saveMessages() | saveMessages() + continueLastTurn() |
| 压缩 | maxPersistedMessages(删除最老的) | 通过 overlay 实现的非破坏式摘要 |
| 搜索 | 不可用 | 单会话和跨会话的 FTS5 全文搜索 |
何时使用 AIChatAgent
- 你需要对 LLM 调用拥有完全控制(RAG、多模型、自定义流式传输)
- 你想要
Response的返回类型,以便用于 HTTP 中间件或测试 - 你正在构建一个对记忆没有要求的简单聊天机器人
何时使用 Think
- 你想快速上线(3 行子类即可串联一切)
- 你需要持久化记忆(模型可读可写的上下文块)
- 你需要长对话(非破坏式压缩)
- 你需要会话搜索(FTS5)
- 你正在构建一个子 agent 系统(父子 RPC + 流式传输)
- 你需要主动型 agent(由定时任务或 webhook 触发的程序化轮次)
配置覆盖
| 方法 / 属性 | 默认值 | 描述 |
|---|---|---|
| getModel() | throws | 返回要使用的 LanguageModel |
| getSystemPrompt() | “You are a helpful assistant.” | 系统提示词(没有上下文块时的回退) |
| getTools() | {} | agentic loop 使用的 AI SDK ToolSet |
| maxSteps | 10 | 每轮最多的工具调用步数 |
| configureSession() | identity | 添加上下文块、压缩、搜索、技能 — 参考 Sessions |
| messageConcurrency | “queue” | 重叠提交的处理方式 — 参考消息并发 |
| waitForMcpConnections | false | 在推理前等待 MCP 服务器连接 |
| chatRecovery | true | 用 runFiber 包裹轮次以实现持久化执行 |
动态配置
Think 接受一个 Config 类型参数用于按实例的类型化配置。配置会持久化到 SQLite,可在休眠和重启后保留。
JavaScript
export class MyAgent extends Think {
getModel() {
const tier = this.getConfig()?.modelTier ?? "fast";
const models = {
fast: "@cf/moonshotai/kimi-k2.5",
capable: "@cf/meta/llama-4-scout-17b-16e-instruct",
};
return createWorkersAI({ binding: this.env.AI })(models[tier]);
}
}
TypeScript
type MyConfig = { modelTier: "fast" | "capable"; theme: string };
export class MyAgent extends Think<Env, MyConfig> {
getModel() {
const tier = this.getConfig()?.modelTier ?? "fast";
const models = {
fast: "@cf/moonshotai/kimi-k2.5",
capable: "@cf/meta/llama-4-scout-17b-16e-instruct",
};
return createWorkersAI({ binding: this.env.AI })(models[tier]);
}
}
| 方法 | 描述 |
|---|---|
| configure(config: Config) | 持久化一个类型化的配置对象 |
| getConfig(): Config | null | 读取持久化的配置;如果从未配置过,返回 null |
通过 @callable 把配置暴露给客户端:
JavaScript
import { callable } from "agents";
export class MyAgent extends Think {
getModel() {
/* ... */
}
@callable()
updateConfig(config) {
this.configure(config);
}
}
TypeScript
import { callable } from "agents";
export class MyAgent extends Think<Env, MyConfig> {
getModel() {
/* ... */
}
@callable()
updateConfig(config: MyConfig) {
this.configure(config);
}
}
Session 集成
Think 使用 Session 来存储对话。重写 configureSession 可以添加持久化记忆、压缩、搜索和技能:
JavaScript
import { Think, Session } from "@cloudflare/think";
export class MyAgent extends Think {
getModel() {
/* ... */
}
configureSession(session) {
return session
.withContext("soul", {
provider: { get: async () => "You are a helpful coding assistant." },
})
.withContext("memory", {
description: "Important facts learned during conversation.",
maxTokens: 2000,
})
.withCachedPrompt();
}
}
TypeScript
import { Think, Session } from "@cloudflare/think";
export class MyAgent extends Think<Env> {
getModel() {
/* ... */
}
configureSession(session: Session) {
return session
.withContext("soul", {
provider: { get: async () => "You are a helpful coding assistant." },
})
.withContext("memory", {
description: "Important facts learned during conversation.",
maxTokens: 2000,
})
.withCachedPrompt();
}
}
当 configureSession 添加了上下文块时,Think 会用这些块构建系统提示词,而不再使用 getSystemPrompt()。Think 的 this.messages getter 直接从 Session 的树状存储中读取。
完整的 Session API——上下文块、压缩、搜索、技能以及多会话支持——请参考 Sessions 文档。
工具
Think 在每一轮都提供内置的工作区文件工具,同时也为自定义工具、代码执行和动态扩展提供集成点。
工具合并顺序
每一轮中,Think 从多个来源合并工具。如果名称冲突,后来的会覆盖前面的:
- 工作区工具 —
read、write、edit、list、find、grep、delete(内置) getTools()— 你自定义的服务端工具- Session 工具 —
set_context、load_context、search_context(来自configureSession) - 扩展工具 — 来自已加载扩展的工具(以扩展名前缀)
- MCP 工具 — 来自已连接的 MCP 服务器
- 客户端工具 — 来自浏览器(参考客户端工具)
- 调用方工具 — 来自
chat()选项(作为子 agent 时)
内置工作区工具
每个 Think agent 都自带 this.workspace —— 一个由 Durable Object SQLite 支撑的虚拟文件系统。工作区工具无需配置即可被模型使用。
| 工具 | 描述 |
|---|---|
| read | 读取文件内容 |
| write | 写入文件内容(会创建父目录) |
| edit | 对已有文件应用查找和替换编辑(支持模糊匹配) |
| list | 列出某路径下的文件和目录 |
| find | 按 glob 模式查找文件 |
| grep | 按正则或固定字符串搜索文件内容 |
| delete | 删除文件或目录 |
R2 溢出
默认情况下,工作区把所有内容存到 SQLite。对于大文件,可以重写 workspace 来加上 R2 溢出:
JavaScript
import { Think } from "@cloudflare/think";
import { Workspace } from "@cloudflare/shell";
export class MyAgent extends Think {
workspace = new Workspace({
sql: this.ctx.storage.sql,
r2: this.env.R2,
name: () => this.name,
});
getModel() {
/* ... */
}
}
TypeScript
import { Think } from "@cloudflare/think";
import { Workspace } from "@cloudflare/shell";
export class MyAgent extends Think<Env> {
override workspace = new Workspace({
sql: this.ctx.storage.sql,
r2: this.env.R2,
name: () => this.name,
});
getModel() {
/* ... */
}
}
这需要一个 R2 bucket 绑定:
JSONC
{
"$schema": "./node_modules/wrangler/config-schema.json",
"r2_buckets": [
{
"binding": "R2",
"bucket_name": "agent-files"
}
]
}
TOML
[[r2_buckets]]
binding = "R2"
bucket_name = "agent-files"
自定义工具
重写 getTools() 来添加你自己的工具。它们就是带 Zod schema 的标准 AI SDK tool() 定义:
JavaScript
import { Think } from "@cloudflare/think";
import { tool } from "ai";
import { z } from "zod";
export class MyAgent extends Think {
getModel() {
/* ... */
}
getTools() {
return {
getWeather: tool({
description: "Get the current weather for a city",
inputSchema: z.object({
city: z.string().describe("City name"),
}),
execute: async ({ city }) => {
const res = await fetch(
`https://api.weather.com/v1/current?q=${city}&key=${this.env.WEATHER_KEY}`,
);
return res.json();
},
}),
};
}
}
TypeScript
import { Think } from "@cloudflare/think";
import { tool } from "ai";
import type { ToolSet } from "ai";
import { z } from "zod";
export class MyAgent extends Think<Env> {
getModel() {
/* ... */
}
getTools(): ToolSet {
return {
getWeather: tool({
description: "Get the current weather for a city",
inputSchema: z.object({
city: z.string().describe("City name"),
}),
execute: async ({ city }) => {
const res = await fetch(
`https://api.weather.com/v1/current?q=${city}&key=${this.env.WEATHER_KEY}`,
);
return res.json();
},
}),
};
}
}
自定义工具会自动与工作区工具合并。如果自定义工具与工作区工具同名,以自定义工具为准。
工具审批
工具可以通过 needsApproval 选项要求用户审批后才执行:
TypeScript
getTools(): ToolSet {
return {
deleteFile: tool({
description: "Delete a file from the system",
inputSchema: z.object({ path: z.string() }),
needsApproval: async ({ path }) => path.startsWith("/important/"),
execute: async ({ path }) => {
await this.workspace.rm(path);
return { deleted: path };
},
}),
};
}
当 needsApproval 返回 true 时,工具调用会被发送给客户端审批。对话会暂停,直到客户端响应 CF_AGENT_TOOL_APPROVAL。
单轮工具覆盖
beforeTurn 钩子可以为某一轮限制或追加工具:
TypeScript
beforeTurn(ctx: TurnContext) {
return {
activeTools: ["read", "write", "getWeather"],
tools: { emergencyTool: this.createEmergencyTool() },
};
}
activeTools 限制模型可以调用的工具。tools 仅为本轮添加额外工具(在已有工具之上合并)。
MCP 工具
Think 从 Agent 基类继承 MCP 客户端支持。来自已连接 MCP 服务器的工具会自动合并到每一轮中。
设置 waitForMcpConnections 以确保推理运行前 MCP 服务器已连接:
JavaScript
export class MyAgent extends Think {
waitForMcpConnections = true; // default 10s timeout
// or: waitForMcpConnections = { timeout: 5000 };
getModel() {
/* ... */
}
}
TypeScript
export class MyAgent extends Think<Env> {
waitForMcpConnections = true; // default 10s timeout
// or: waitForMcpConnections = { timeout: 5000 };
getModel() {
/* ... */
}
}
通过编程方式或 @callable 方法添加 MCP 服务器:
JavaScript
import { callable } from "agents";
export class MyAgent extends Think {
getModel() {
/* ... */
}
@callable()
async addServer(name, url) {
return await this.addMcpServer(name, url);
}
@callable()
async removeServer(serverId) {
await this.removeMcpServer(serverId);
}
}
TypeScript
import { callable } from "agents";
export class MyAgent extends Think<Env> {
getModel() {
/* ... */
}
@callable()
async addServer(name: string, url: string) {
return await this.addMcpServer(name, url);
}
@callable()
async removeServer(serverId: string) {
await this.removeMcpServer(serverId);
}
}
代码执行工具
让 LLM 在沙箱化的 Worker 中编写并运行 JavaScript。需要 @cloudflare/codemode 和一个 worker_loaders 绑定。
Terminal window
npm install @cloudflare/codemode
JavaScript
import { Think } from "@cloudflare/think";
import { createExecuteTool } from "@cloudflare/think/tools/execute";
import { createWorkspaceTools } from "@cloudflare/think/tools/workspace";
export class MyAgent extends Think {
getModel() {
/* ... */
}
getTools() {
return {
execute: createExecuteTool({
tools: createWorkspaceTools(this.workspace),
loader: this.env.LOADER,
}),
};
}
}
TypeScript
import { Think } from "@cloudflare/think";
import { createExecuteTool } from "@cloudflare/think/tools/execute";
import { createWorkspaceTools } from "@cloudflare/think/tools/workspace";
export class MyAgent extends Think<Env> {
getModel() {
/* ... */
}
getTools() {
return {
execute: createExecuteTool({
tools: createWorkspaceTools(this.workspace),
loader: this.env.LOADER,
}),
};
}
}
JSONC
{
"$schema": "./node_modules/wrangler/config-schema.json",
"worker_loaders": [
{
"binding": "LOADER"
}
]
}
TOML
[[worker_loaders]]
binding = "LOADER"
要获得更丰富的文件系统访问能力,传入一个 state 后端:
JavaScript
import { createWorkspaceStateBackend } from "@cloudflare/shell";
createExecuteTool({
tools: myDomainTools,
state: createWorkspaceStateBackend(this.workspace),
loader: this.env.LOADER,
});
TypeScript
import { createWorkspaceStateBackend } from "@cloudflare/shell";
createExecuteTool({
tools: myDomainTools,
state: createWorkspaceStateBackend(this.workspace),
loader: this.env.LOADER,
});
浏览器工具
让你的 agent 通过 Chrome DevTools Protocol(CDP)进行网页检查、抓取、截屏和调试。需要 @cloudflare/codemode 和一个 Browser Run 绑定。
JavaScript
import { Think } from "@cloudflare/think";
import { createBrowserTools } from "@cloudflare/think/tools/browser";
export class MyAgent extends Think {
getModel() {
/* ... */
}
getTools() {
return {
...createBrowserTools({
browser: this.env.BROWSER,
loader: this.env.LOADER,
}),
};
}
}
TypeScript
import { Think } from "@cloudflare/think";
import { createBrowserTools } from "@cloudflare/think/tools/browser";
export class MyAgent extends Think<Env> {
getModel() {
/* ... */
}
getTools() {
return {
...createBrowserTools({
browser: this.env.BROWSER,
loader: this.env.LOADER,
}),
};
}
}
JSONC
{
"$schema": "./node_modules/wrangler/config-schema.json",
"browser": {
"binding": "BROWSER"
},
"worker_loaders": [
{
"binding": "LOADER"
}
]
}
TOML
[browser]
binding = "BROWSER"
[[worker_loaders]]
binding = "LOADER"
它会添加两个工具:
| 工具 | 描述 |
|---|---|
| browser_search | 查询 CDP 协议规范以发现命令、事件和类型 |
| browser_execute | 针对真实浏览器会话运行 CDP 命令(截屏、读取 DOM、JS 求值) |
要使用自定义 Chrome 端点,传入 cdpUrl 而不是 browser:
JavaScript
createBrowserTools({
cdpUrl: "http://localhost:9222",
loader: this.env.LOADER,
});
TypeScript
createBrowserTools({
cdpUrl: "http://localhost:9222",
loader: this.env.LOADER,
});
完整的 CDP helper API 请参考浏览网页。
扩展
扩展是动态加载、沙箱化的 Worker,可在运行时添加工具。LLM 可以编写扩展源码、加载它,并在下一轮中使用新增的工具。
扩展需要一个 worker_loaders 绑定:
JavaScript
import { Think } from "@cloudflare/think";
export class MyAgent extends Think {
extensionLoader = this.env.LOADER;
getModel() {
/* ... */
}
}
TypeScript
import { Think } from "@cloudflare/think";
export class MyAgent extends Think<Env> {
extensionLoader = this.env.LOADER;
getModel() {
/* ... */
}
}
静态扩展
定义启动时加载的扩展:
JavaScript
export class MyAgent extends Think {
extensionLoader = this.env.LOADER;
getModel() {
/* ... */
}
getExtensions() {
return [
{
manifest: {
name: "math",
version: "1.0.0",
permissions: { network: false },
},
source: `({
tools: {
add: {
description: "Add two numbers",
parameters: { a: { type: "number" }, b: { type: "number" } },
execute: async ({ a, b }) => ({ result: a + b })
}
}
})`,
},
];
}
}
TypeScript
export class MyAgent extends Think<Env> {
extensionLoader = this.env.LOADER;
getModel() {
/* ... */
}
getExtensions() {
return [
{
manifest: {
name: "math",
version: "1.0.0",
permissions: { network: false },
},
source: `({
tools: {
add: {
description: "Add two numbers",
parameters: { a: { type: "number" }, b: { type: "number" } },
execute: async ({ a, b }) => ({ result: a + b })
}
}
})`,
},
];
}
}
扩展工具有命名空间——一个名为 math 的扩展,其 add 工具在模型的工具集中是 math_add。
LLM 驱动的扩展
把 createExtensionTools 给到模型,它就能动态加载扩展:
JavaScript
import { createExtensionTools } from "@cloudflare/think/tools/extensions";
export class MyAgent extends Think {
extensionLoader = this.env.LOADER;
getModel() {
/* ... */
}
getTools() {
return {
...createExtensionTools({ manager: this.extensionManager }),
...this.extensionManager.getTools(),
};
}
}
TypeScript
import { createExtensionTools } from "@cloudflare/think/tools/extensions";
export class MyAgent extends Think<Env> {
extensionLoader = this.env.LOADER;
getModel() {
/* ... */
}
getTools() {
return {
...createExtensionTools({ manager: this.extensionManager! }),
...this.extensionManager!.getTools(),
};
}
}
这会给到模型两个工具:
load_extension— 从 JavaScript 源码加载一个新扩展list_extensions— 列出当前已加载的扩展
扩展上下文块
扩展可以在 manifest 中声明上下文块。这些会自动注册到 Session 中:
TypeScript
getExtensions() {
return [{
manifest: {
name: "notes",
version: "1.0.0",
permissions: { network: false },
context: [
{ label: "scratchpad", description: "Extension scratch space", maxTokens: 500 },
],
},
source: `({ tools: { /* ... */ } })`,
}];
}
该上下文块会以 notes_scratchpad 的名字注册(以扩展名作为命名空间)。
自定义工作区后端
各个工具的工厂方法都被导出,以便配合自定义存储后端使用:
JavaScript
import {
createReadTool,
createWriteTool,
createEditTool,
createListTool,
createFindTool,
createGrepTool,
createDeleteTool,
createWorkspaceTools,
} from "@cloudflare/think/tools/workspace";
TypeScript
import {
createReadTool,
createWriteTool,
createEditTool,
createListTool,
createFindTool,
createGrepTool,
createDeleteTool,
createWorkspaceTools,
} from "@cloudflare/think/tools/workspace";
为你的存储后端实现操作接口:
JavaScript
const myReadOps = {
readFile: async (path) => fetchFromMyStorage(path),
stat: async (path) => getFileInfo(path),
};
const readTool = createReadTool({ ops: myReadOps });
TypeScript
import type { ReadOperations } from "@cloudflare/think/tools/workspace";
const myReadOps: ReadOperations = {
readFile: async (path) => fetchFromMyStorage(path),
stat: async (path) => getFileInfo(path),
};
const readTool = createReadTool({ ops: myReadOps });
生命周期钩子
Think 拥有 streamText 调用,并在 chat 轮次的每个阶段提供钩子。无论入口路径是什么——WebSocket 聊天、子 agent chat()、saveMessages 以及工具结果后的自动续轮——钩子都会在每一轮触发。
钩子总览
| 钩子 | 触发时机 | 返回值 | 异步 |
|---|---|---|---|
| configureSession(session) | onStart 期间一次 | Session | yes |
| beforeTurn(ctx) | streamText 之前 | TurnConfig 或 void | yes |
| beforeToolCall(ctx) | 模型调用工具时 | ToolCallDecision 或 void | yes |
| afterToolCall(ctx) | 工具执行之后 | void | yes |
| onStepFinish(ctx) | 每一步结束之后 | void | yes |
| onChunk(ctx) | 每个流式 chunk | void | yes |
| onChatResponse(result) | 一轮完成且消息被持久化后 | void | yes |
| onChatError(error) | 一轮中出错时 | 要传播的 error | no |
执行顺序
对于一个有两次工具调用的轮次:
configureSession() ← once at startup, not per-turn
│
beforeTurn() ← inspect assembled context, override model/tools/prompt
│
┌── streamText ───────────────────────────────────┐
│ onChunk() onChunk() onChunk() ... │
│ │ │
│ beforeToolCall() → tool executes │
│ afterToolCall() │
│ │ │
│ onStepFinish() │
│ │ │
│ onChunk() onChunk() ... │
│ │ │
│ beforeToolCall() → tool executes │
│ afterToolCall() │
│ │ │
│ onStepFinish() │
└─────────────────────────────────────────────────┘
│
onChatResponse() ← message persisted, turn lock released
beforeTurn
在 streamText 之前调用。接收完整组装好的上下文——系统提示词、转换后的消息、合并后的工具以及模型。返回一个 TurnConfig 来覆盖任意部分,或返回 void 接受默认值。
TypeScript
beforeTurn(ctx: TurnContext): TurnConfig | void | Promise<TurnConfig | void>
TurnContext
| 字段 | 类型 | 描述 |
|---|---|---|
| system | string | 组装好的系统提示词(来自上下文块或 getSystemPrompt()) |
| messages | ModelMessage[] | 组装好的模型消息(已截断、已剪枝) |
| tools | ToolSet | 合并后的工具集(workspace + getTools + session + MCP + client + caller) |
| model | LanguageModel | 来自 getModel() 的模型 |
| continuation | boolean | 是否是续轮(工具结果之后的自动续轮) |
| body | Record<string, unknown> | 来自客户端请求的自定义 body 字段 |
TurnConfig
所有字段都是可选的。只返回想要修改的字段即可。
| 字段 | 类型 | 描述 |
|---|---|---|
| model | LanguageModel | 仅本轮覆盖模型 |
| system | string | 覆盖系统提示词 |
| messages | ModelMessage[] | 覆盖组装好的消息 |
| tools | ToolSet | 要追加合并的额外工具 |
| activeTools | string[] | 限制模型可调用的工具 |
| toolChoice | ToolChoice | 强制使用某个工具 |
| maxSteps | number | 仅本轮覆盖 maxSteps |
| providerOptions | Record<string, unknown> | provider 特定选项 |
示例
续轮时切换到更便宜的模型:
TypeScript
beforeTurn(ctx: TurnContext) {
if (ctx.continuation) {
return { model: this.cheapModel };
}
}
限制模型可以调用的工具:
TypeScript
beforeTurn(ctx: TurnContext) {
return { activeTools: ["read", "write", "getWeather"] };
}
从客户端 body 中追加单轮上下文:
TypeScript
beforeTurn(ctx: TurnContext) {
if (ctx.body?.selectedFile) {
return {
system: ctx.system + `\n\nUser is editing: ${ctx.body.selectedFile}`,
};
}
}
beforeToolCall
模型生成工具调用时被调用。只对服务端工具(带 execute 的工具)触发。
注意
beforeToolCall 目前作为观察型钩子触发——在工具执行后,通过 onStepFinish 数据触发。ToolCallDecision 中的 block 和 substitute 操作在类型中已经定义,但还未生效。目前请将该钩子用于日志和分析。
TypeScript
beforeToolCall(ctx: ToolCallContext) {
console.log(`Tool called: ${ctx.toolName}`, ctx.args);
}
| 字段 | 类型 | 描述 |
|---|---|---|
| toolName | string | 被调用工具的名称 |
| args | Record<string, unknown> | 模型提供的参数 |
afterToolCall
工具执行后调用。
TypeScript
afterToolCall(ctx: ToolCallResultContext) {
this.env.ANALYTICS.writeDataPoint({
blobs: [ctx.toolName],
doubles: [JSON.stringify(ctx.result).length],
});
}
| 字段 | 类型 | 描述 |
|---|---|---|
| toolName | string | 被调用工具的名称 |
| args | Record<string, unknown> | 调用工具时使用的参数 |
| result | unknown | 工具返回的结果 |
onStepFinish
agentic loop 中每一步结束后调用。一步是一次 streamText 迭代——模型生成文本、可选地调用工具,然后这一步结束。
TypeScript
onStepFinish(ctx: StepContext) {
console.log(
`Step ${ctx.stepType}: ${ctx.usage.inputTokens}in/${ctx.usage.outputTokens}out`,
);
}
| 字段 | 类型 | 描述 | |
|---|---|---|---|
| stepType | “initial” | “continue” | “tool-result” | 这一步运行的原因 |
| text | string | 这一步生成的文本 | |
| toolCalls | unknown[] | 发起的工具调用 | |
| toolResults | unknown[] | 收到的工具结果 | |
| finishReason | string | 这一步结束的原因 | |
| usage | { inputTokens, outputTokens } | 这一步的 token 用量 |
onChunk
每个流式 chunk 调用一次。频率高——每个 token 都会触发。可用于流式分析、进度提示或 token 计数。仅供观察。
onChatResponse
在一轮 chat 完成且 assistant 消息已被持久化后调用。该钩子运行前轮次锁已经释放,因此可以在内部安全地调用 saveMessages 或其他方法。
对所有完成路径都触发:WebSocket、子 agent RPC、saveMessages 以及自动续轮。
TypeScript
onChatResponse(result: ChatResponseResult) {
if (result.status === "completed") {
console.log(`Turn ${result.requestId}: ${result.message.parts.length} parts`);
}
}
| 字段 | 类型 | 描述 | |
|---|---|---|---|
| message | UIMessage | 已持久化的 assistant 消息 | |
| requestId | string | 此次轮次的唯一 ID | |
| continuation | boolean | 是否为续轮 | |
| status | “completed” | “error” | “aborted” | 此轮如何结束 |
| error | string? | 错误信息(status 为 “error” 时) |
onChatError
chat 轮次出错时调用。返回 error 来传播,或返回另一个 error。本钩子触发之前,部分的 assistant 消息(若有)已被持久化。
TypeScript
onChatError(error: unknown) {
console.error("Chat turn failed:", error);
return new Error("Something went wrong. Please try again.");
}
客户端工具
Think 支持运行在浏览器中的工具。客户端在 chat 请求 body 中发送工具 schema,Think 会把它们与服务端工具合并;LLM 调用客户端工具时,调用会被路由到客户端执行。
定义客户端工具
在客户端,把 clientTools 传给 useAgentChat:
JavaScript
const { messages, sendMessage } = useAgentChat({
agent,
clientTools: {
getUserTimezone: {
description: "Get the user's timezone from their browser",
parameters: {},
execute: async () => {
return Intl.DateTimeFormat().resolvedOptions().timeZone;
},
},
getClipboard: {
description: "Read text from the user's clipboard",
parameters: {},
execute: async () => {
return navigator.clipboard.readText();
},
},
},
});
TypeScript
const { messages, sendMessage } = useAgentChat({
agent,
clientTools: {
getUserTimezone: {
description: "Get the user's timezone from their browser",
parameters: {},
execute: async () => {
return Intl.DateTimeFormat().resolvedOptions().timeZone;
},
},
getClipboard: {
description: "Read text from the user's clipboard",
parameters: {},
execute: async () => {
return navigator.clipboard.readText();
},
},
},
});
客户端工具是服务端没有 execute 的工具——它们只有 schema。当 LLM 为它们生成工具调用时,Think 会把它路由给客户端。
审批流程
在客户端用 onToolCall 处理审批:
JavaScript
const { messages, sendMessage, addToolResult } = useAgentChat({
agent,
onToolCall: ({ toolCall }) => {
if (toolCall.toolName === "read") {
return { approve: true };
}
// Others go through the UI approval flow
},
});
TypeScript
const { messages, sendMessage, addToolResult } = useAgentChat({
agent,
onToolCall: ({ toolCall }) => {
if (toolCall.toolName === "read") {
return { approve: true };
}
// Others go through the UI approval flow
},
});
自动续轮
收到客户端工具结果后,Think 会自动继续对话,而无需新的用户消息。续轮的 TurnContext 中 continuation: true,可以在 beforeTurn 中据此调整模型或工具选择。
消息并发
messageConcurrency 属性控制当一轮 chat 已在进行中时,重叠的用户提交如何处理。
| 策略 | 行为 |
|---|---|
| “queue” | 把每次提交都排队,按顺序处理。默认值。 |
| “latest” | 只保留最新的重叠提交。 |
| “merge” | 所有重叠的用户消息都保留在历史中;模型会在一轮中看到全部消息。 |
| “drop” | 完全忽略重叠的提交。消息不会被持久化。 |
| { strategy: “debounce”, debounceMs?: number } | 带静默窗口的尾沿最新消息(默认 750ms)。 |
JavaScript
import { Think } from "@cloudflare/think";
export class SearchAgent extends Think {
messageConcurrency = "latest";
getModel() {
/* ... */
}
}
TypeScript
import { Think } from "@cloudflare/think";
import type { MessageConcurrency } from "@cloudflare/think";
export class SearchAgent extends Think<Env> {
override messageConcurrency: MessageConcurrency = "latest";
getModel() {
/* ... */
}
}
多标签页广播
Think 会把流式响应广播给所有连接的 WebSocket 客户端。当多个浏览器标签页连接到同一个 agent 时,所有标签页都会实时看到流式响应。工具调用状态(pending、result、approval)也会广播到所有标签页。
子 agent RPC 与程序化轮次
Think 既可以作为顶层 agent,也可以作为子 agent。作为子 agent 时,chat() 方法运行完整的一轮,并通过回调流式返回事件。
chat
TypeScript
async chat(
userMessage: string | UIMessage,
callback: StreamCallback,
options?: ChatOptions,
): Promise<void>
StreamCallback
| 方法 | 触发时机 |
|---|---|
| onEvent(json) | 每个流式 chunk 触发(JSON 序列化的 UIMessageChunk) |
| onDone() | 此轮完成且 assistant 消息已持久化之后 |
| onError(error) | 此轮出错时(如未提供,error 会被抛出) |
ChatOptions
| 字段 | 描述 |
|---|---|
| signal | AbortSignal,可在流中途取消此轮 |
| tools | 仅本轮合并的额外工具(合并优先级最高) |
示例:父 agent 调用子 agent
JavaScript
import { Think } from "@cloudflare/think";
export class ParentAgent extends Think {
getModel() {
/* ... */
}
async delegateToChild(task) {
const child = await this.subAgent(ChildAgent, "child-1");
const chunks = [];
await child.chat(task, {
onEvent: (json) => {
chunks.push(json);
},
onDone: () => {
console.log("Child completed");
},
onError: (error) => {
console.error("Child failed:", error);
},
});
return chunks;
}
}
export class ChildAgent extends Think {
getModel() {
/* ... */
}
getSystemPrompt() {
return "You are a research assistant. Analyze data and report findings.";
}
}
TypeScript
import { Think } from "@cloudflare/think";
export class ParentAgent extends Think<Env> {
getModel() {
/* ... */
}
async delegateToChild(task: string) {
const child = await this.subAgent(ChildAgent, "child-1");
const chunks: string[] = [];
await child.chat(task, {
onEvent: (json) => {
chunks.push(json);
},
onDone: () => {
console.log("Child completed");
},
onError: (error) => {
console.error("Child failed:", error);
},
});
return chunks;
}
}
export class ChildAgent extends Think<Env> {
getModel() {
/* ... */
}
getSystemPrompt() {
return "You are a research assistant. Analyze data and report findings.";
}
}
传入额外工具
tools 选项仅为本轮添加工具,优先级最高:
JavaScript
import { tool } from "ai";
import { z } from "zod";
await child.chat("Summarize the report", callback, {
tools: {
fetchReport: tool({
description: "Fetch the report data",
inputSchema: z.object({}),
execute: async () => this.getReportData(),
}),
},
});
TypeScript
import { tool } from "ai";
import { z } from "zod";
await child.chat("Summarize the report", callback, {
tools: {
fetchReport: tool({
description: "Fetch the report data",
inputSchema: z.object({}),
execute: async () => this.getReportData(),
}),
},
});
中止子 agent 的一轮
传入 AbortSignal 来在流中途取消。被中止时,部分的 assistant 消息仍会被持久化。
JavaScript
const controller = new AbortController();
setTimeout(() => controller.abort(), 30_000);
await child.chat("Long analysis task", callback, {
signal: controller.signal,
});
TypeScript
const controller = new AbortController();
setTimeout(() => controller.abort(), 30_000);
await child.chat("Long analysis task", callback, {
signal: controller.signal,
});
saveMessages
不需要 WebSocket 连接也能注入消息并触发模型轮次。可用于定时回复、由 webhook 触发的轮次、主动型 agent,或在 onChatResponse 中链式调用。
TypeScript
async saveMessages(
messages:
| UIMessage[]
| ((current: UIMessage[]) => UIMessage[] | Promise<UIMessage[]>),
): Promise<SaveMessagesResult>
返回 { requestId, status },其中 status 是 "completed" 或 "skipped"。
静态消息
JavaScript
await this.saveMessages([
{
id: crypto.randomUUID(),
role: "user",
parts: [{ type: "text", text: "Time for your daily summary." }],
},
]);
TypeScript
await this.saveMessages([
{
id: crypto.randomUUID(),
role: "user",
parts: [{ type: "text", text: "Time for your daily summary." }],
},
]);
函数形式
当多次 saveMessages 调用排队时,函数形式会在轮次实际开始时,使用最新的消息执行:
JavaScript
await this.saveMessages((current) => [
...current,
{
id: crypto.randomUUID(),
role: "user",
parts: [{ type: "text", text: "Continue your analysis." }],
},
]);
TypeScript
await this.saveMessages((current) => [
...current,
{
id: crypto.randomUUID(),
role: "user",
parts: [{ type: "text", text: "Continue your analysis." }],
},
]);
定时回复
从 cron 调度触发一次轮次:
JavaScript
export class MyAgent extends Think {
getModel() {
/* ... */
}
async onScheduled() {
await this.saveMessages([
{
id: crypto.randomUUID(),
role: "user",
parts: [{ type: "text", text: "Generate the daily report." }],
},
]);
}
}
TypeScript
export class MyAgent extends Think<Env> {
getModel() {
/* ... */
}
async onScheduled() {
await this.saveMessages([
{
id: crypto.randomUUID(),
role: "user",
parts: [{ type: "text", text: "Generate the daily report." }],
},
]);
}
}
在 onChatResponse 中链式触发
在当前轮次完成后启动一个后续轮次:
TypeScript
async onChatResponse(result: ChatResponseResult) {
if (result.status === "completed" && this.needsFollowUp(result.message)) {
await this.saveMessages([{
id: crypto.randomUUID(),
role: "user",
parts: [{ type: "text", text: "Now summarize what you found." }],
}]);
}
}
continueLastTurn
在不注入新用户消息的情况下,恢复上一轮 assistant 轮次。常用于工具结果到达后,或从中断中恢复后。
TypeScript
protected async continueLastTurn(
body?: Record<string, unknown>,
): Promise<SaveMessagesResult>
如果上一条消息不是 assistant 消息,返回 { requestId, status: "skipped" }。可选的 body 参数会覆盖此次续轮中存储的 body。
聊天恢复
Think 可以把 chat 轮次包裹在 Durable Object fiber 中以实现持久化执行。当 DO 在轮次中途被回收时,可以在重启后恢复该轮。
JavaScript
export class MyAgent extends Think {
chatRecovery = true;
getModel() {
/* ... */
}
}
TypeScript
export class MyAgent extends Think<Env> {
chatRecovery = true;
getModel() {
/* ... */
}
}
当 chatRecovery 为 true 时,所有四种轮次路径(WebSocket、自动续轮、saveMessages、continueLastTurn)都会被 runFiber 包裹。
onChatRecovery
DO 重启后检测到中断的 chat fiber 时,Think 调用 onChatRecovery:
JavaScript
export class MyAgent extends Think {
chatRecovery = true;
getModel() {
/* ... */
}
onChatRecovery(ctx) {
console.log(
`Recovering turn ${ctx.requestId}, partial: ${ctx.partialText.length} chars`,
);
return {
persist: true,
continue: true,
};
}
}
TypeScript
export class MyAgent extends Think<Env> {
chatRecovery = true;
getModel() {
/* ... */
}
onChatRecovery(ctx: ChatRecoveryContext) {
console.log(
`Recovering turn ${ctx.requestId}, partial: ${ctx.partialText.length} chars`,
);
return {
persist: true,
continue: true,
};
}
}
ChatRecoveryContext
| 字段 | 类型 | 描述 |
|---|---|---|
| streamId | string | 被中断轮次的 stream ID |
| requestId | string | 被中断轮次的 request ID |
| partialText | string | 中断前已生成的文本 |
| partialParts | MessagePart[] | 中断前已累积的 parts |
| recoveryData | unknown | null | 此轮次中通过 this.stash() 保存的数据 |
| messages | UIMessage[] | 当前对话历史 |
| lastBody | Record<string, unknown>? | 被中断轮次的 body |
| lastClientTools | ClientToolSchema[]? | 被中断轮次的客户端工具 |
ChatRecoveryOptions
| 字段 | 类型 | 描述 |
|---|---|---|
| persist | boolean? | 是否持久化部分的 assistant 消息 |
| continue | boolean? | 是否自动以新一轮继续 |
persist: true 时保存部分消息。continue: true 时,Think 会在 agent 达到稳定状态后调用 continueLastTurn()。
稳定性检测
Think 提供方法来检查 agent 是否处于稳定状态——没有挂起的工具结果、没有挂起的审批、没有正在进行的轮次。
hasPendingInteraction
如果有任意 assistant 消息存在挂起的工具调用(没有结果或挂起审批的工具),返回 true。
TypeScript
protected hasPendingInteraction(): boolean
waitUntilStable
返回一个 Promise,当 agent 达到稳定状态时 resolve 为 true,如果超时则为 false。
JavaScript
const stable = await this.waitUntilStable({ timeout: 30_000 });
if (stable) {
await this.saveMessages([
{
id: crypto.randomUUID(),
role: "user",
parts: [{ type: "text", text: "Now that you are done, summarize." }],
},
]);
}
TypeScript
const stable = await this.waitUntilStable({ timeout: 30_000 });
if (stable) {
await this.saveMessages([
{
id: crypto.randomUUID(),
role: "user",
parts: [{ type: "text", text: "Now that you are done, summarize." }],
},
]);
}
包导出
| 导出 | 描述 |
|---|---|
| @cloudflare/think | Think、Session、Workspace —— 主类与重新导出 |
| @cloudflare/think/tools/workspace | createWorkspaceTools() —— 用于自定义存储后端 |
| @cloudflare/think/tools/execute | createExecuteTool() —— 通过 codemode 执行沙箱化代码 |
| @cloudflare/think/tools/extensions | createExtensionTools() —— LLM 驱动的扩展加载 |
| @cloudflare/think/extensions | ExtensionManager、HostBridgeLoopback —— 扩展运行时 |
peer dependencies
| 包 | 必需 | 备注 |
|---|---|---|
| agents | yes | Cloudflare Agents SDK |
| ai | yes | Vercel AI SDK v6 |
| zod | yes | Schema 校验(v4) |
| @cloudflare/shell | yes | 工作区文件系统 |
| @cloudflare/codemode | optional | 用于 createExecuteTool |
致谢
Think 的设计受 Pi ↗ 启发。
相关内容
- Sessions — 上下文块、压缩、搜索、多会话(Think 构建于其上的存储层)
- 子 agent —
subAgent()、abortSubAgent()、deleteSubAgent()(用于派生子 agent 的基类 Agent 方法) - Chat agent —
AIChatAgent,当你需要完全控制 LLM 调用时 - 长期运行的 agent — 用于多周 agent 生命周期的子 agent 委派模式
- 持久化执行 —
runFiber()与崩溃恢复(chatRecovery使用它) - 浏览网页 — 完整的 CDP helper API 参考