Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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。你重写的是单个组件,而不是整条管道。

关注点AIChatAgentThink
最小子类~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
maxSteps10每轮最多的工具调用步数
configureSession()identity添加上下文块、压缩、搜索、技能 — 参考 Sessions
messageConcurrency“queue”重叠提交的处理方式 — 参考消息并发
waitForMcpConnectionsfalse在推理前等待 MCP 服务器连接
chatRecoverytrue用 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 从多个来源合并工具。如果名称冲突,后来的会覆盖前面的:

  1. 工作区工具readwriteeditlistfindgrepdelete(内置)
  2. getTools() — 你自定义的服务端工具
  3. Session 工具set_contextload_contextsearch_context(来自 configureSession)
  4. 扩展工具 — 来自已加载扩展的工具(以扩展名前缀)
  5. MCP 工具 — 来自已连接的 MCP 服务器
  6. 客户端工具 — 来自浏览器(参考客户端工具)
  7. 调用方工具 — 来自 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 期间一次Sessionyes
beforeTurn(ctx)streamText 之前TurnConfig 或 voidyes
beforeToolCall(ctx)模型调用工具时ToolCallDecision 或 voidyes
afterToolCall(ctx)工具执行之后voidyes
onStepFinish(ctx)每一步结束之后voidyes
onChunk(ctx)每个流式 chunkvoidyes
onChatResponse(result)一轮完成且消息被持久化后voidyes
onChatError(error)一轮中出错时要传播的 errorno

执行顺序

对于一个有两次工具调用的轮次:


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

字段类型描述
systemstring组装好的系统提示词(来自上下文块或 getSystemPrompt())
messagesModelMessage[]组装好的模型消息(已截断、已剪枝)
toolsToolSet合并后的工具集(workspace + getTools + session + MCP + client + caller)
modelLanguageModel来自 getModel() 的模型
continuationboolean是否是续轮(工具结果之后的自动续轮)
bodyRecord<string, unknown>来自客户端请求的自定义 body 字段

TurnConfig

所有字段都是可选的。只返回想要修改的字段即可。

字段类型描述
modelLanguageModel仅本轮覆盖模型
systemstring覆盖系统提示词
messagesModelMessage[]覆盖组装好的消息
toolsToolSet要追加合并的额外工具
activeToolsstring[]限制模型可调用的工具
toolChoiceToolChoice强制使用某个工具
maxStepsnumber仅本轮覆盖 maxSteps
providerOptionsRecord<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 中的 blocksubstitute 操作在类型中已经定义,但还未生效。目前请将该钩子用于日志和分析。

TypeScript


beforeToolCall(ctx: ToolCallContext) {

  console.log(`Tool called: ${ctx.toolName}`, ctx.args);

}


字段类型描述
toolNamestring被调用工具的名称
argsRecord<string, unknown>模型提供的参数

afterToolCall

工具执行后调用。

TypeScript


afterToolCall(ctx: ToolCallResultContext) {

  this.env.ANALYTICS.writeDataPoint({

    blobs: [ctx.toolName],

    doubles: [JSON.stringify(ctx.result).length],

  });

}


字段类型描述
toolNamestring被调用工具的名称
argsRecord<string, unknown>调用工具时使用的参数
resultunknown工具返回的结果

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”这一步运行的原因
textstring这一步生成的文本
toolCallsunknown[]发起的工具调用
toolResultsunknown[]收到的工具结果
finishReasonstring这一步结束的原因
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`);

  }

}


字段类型描述
messageUIMessage已持久化的 assistant 消息
requestIdstring此次轮次的唯一 ID
continuationboolean是否为续轮
status“completed” | “error”“aborted”此轮如何结束
errorstring?错误信息(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 会自动继续对话,而无需新的用户消息。续轮的 TurnContextcontinuation: 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

字段描述
signalAbortSignal,可在流中途取消此轮
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() {

    /* ... */

  }

}


chatRecoverytrue 时,所有四种轮次路径(WebSocket、自动续轮、saveMessagescontinueLastTurn)都会被 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

字段类型描述
streamIdstring被中断轮次的 stream ID
requestIdstring被中断轮次的 request ID
partialTextstring中断前已生成的文本
partialPartsMessagePart[]中断前已累积的 parts
recoveryDataunknown | null此轮次中通过 this.stash() 保存的数据
messagesUIMessage[]当前对话历史
lastBodyRecord<string, unknown>?被中断轮次的 body
lastClientToolsClientToolSchema[]?被中断轮次的客户端工具

ChatRecoveryOptions

字段类型描述
persistboolean?是否持久化部分的 assistant 消息
continueboolean?是否自动以新一轮继续

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/thinkThink、Session、Workspace —— 主类与重新导出
@cloudflare/think/tools/workspacecreateWorkspaceTools() —— 用于自定义存储后端
@cloudflare/think/tools/executecreateExecuteTool() —— 通过 codemode 执行沙箱化代码
@cloudflare/think/tools/extensionscreateExtensionTools() —— LLM 驱动的扩展加载
@cloudflare/think/extensionsExtensionManager、HostBridgeLoopback —— 扩展运行时

peer dependencies

必需备注
agentsyesCloudflare Agents SDK
aiyesVercel AI SDK v6
zodyesSchema 校验(v4)
@cloudflare/shellyes工作区文件系统
@cloudflare/codemodeoptional用于 createExecuteTool

致谢

Think 的设计受 Pi ↗ 启发。

相关内容

  • Sessions — 上下文块、压缩、搜索、多会话(Think 构建于其上的存储层)
  • 子 agentsubAgent()abortSubAgent()deleteSubAgent()(用于派生子 agent 的基类 Agent 方法)
  • Chat agentAIChatAgent,当你需要完全控制 LLM 调用时
  • 长期运行的 agent — 用于多周 agent 生命周期的子 agent 委派模式
  • 持久化执行runFiber() 与崩溃恢复(chatRecovery 使用它)
  • 浏览网页 — 完整的 CDP helper API 参考