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

快速开始

构建可以持久化、思考和行动的 AI agent。Agent 运行在 Cloudflare 全球网络上,跨请求维持状态,并通过 WebSockets 与客户端实时连接。

你将构建什么: 一个计数器 agent,带持久状态,实时同步到 React 前端。

预计时间: 约 10 分钟

创建新项目

npm yarn pnpm

npm create cloudflare@latest -- --template cloudflare/agents-starter
yarn create cloudflare --template cloudflare/agents-starter
pnpm create cloudflare@latest --template cloudflare/agents-starter

然后安装依赖并启动开发服务器:

Terminal 窗口


cd my-agent

npm install

npm run dev


这会创建一个项目,包含:

  • src/server.ts — 你的 agent 代码
  • src/client.tsx — React 前端
  • wrangler.jsonc — Cloudflare 配置
  • tsconfig.json — 继承自 agents/tsconfig,确保 decorator 和模块设置正确
  • vite.config.ts — 包含 agents/vite 插件以支持 decorator

starter 模板包含两个重要的 SDK 集成。如果你是手动配置项目,这两项都需要加上:

tsconfig.json — 继承 agents/tsconfig,设置了 target: "ES2021" 等推荐选项:


{

  "extends": "agents/tsconfig"

}


vite.config.ts — 包含 agents() 插件,处理 TC39 decorator 转换(在 Vite 8 中 @callable() 必需):

TypeScript


import { cloudflare } from "@cloudflare/vite-plugin";

import react from "@vitejs/plugin-react";

import agents from "agents/vite";

import { defineConfig } from "vite";


export default defineConfig({

  plugins: [agents(), react(), cloudflare()],

});


打开 http://localhost:5173 ↗ 查看你的 agent 运行效果。

你的第一个 agent

从零构建一个简单的计数器 agent。替换 src/server.ts:

JavaScript


import { Agent, routeAgentRequest, callable } from "agents";


// Define the state shape

// Create the agent

export class CounterAgent extends Agent {

  // Initial state for new instances

  initialState = { count: 0 };


  // Methods marked with @callable can be called from the client

  @callable()

  increment() {

    this.setState({ count: this.state.count + 1 });

    return this.state.count;

  }


  @callable()

  decrement() {

    this.setState({ count: this.state.count - 1 });

    return this.state.count;

  }


  @callable()

  reset() {

    this.setState({ count: 0 });

  }

}


// Route requests to agents

export default {

  async fetch(request, env, ctx) {

    return (

      (await routeAgentRequest(request, env)) ??

      new Response("Not found", { status: 404 })

    );

  },

};


Explain Code

TypeScript


import { Agent, routeAgentRequest, callable } from "agents";


// Define the state shape

export type CounterState = {

  count: number;

};


// Create the agent

export class CounterAgent extends Agent<Env, CounterState> {

  // Initial state for new instances

  initialState: CounterState = { count: 0 };


  // Methods marked with @callable can be called from the client

  @callable()

  increment() {

    this.setState({ count: this.state.count + 1 });

    return this.state.count;

  }


  @callable()

  decrement() {

    this.setState({ count: this.state.count - 1 });

    return this.state.count;

  }


  @callable()

  reset() {

    this.setState({ count: 0 });

  }

}


// Route requests to agents

export default {

  async fetch(request: Request, env: Env, ctx: ExecutionContext) {

    return (

      (await routeAgentRequest(request, env)) ??

      new Response("Not found", { status: 404 })

    );

  },

} satisfies ExportedHandler<Env>;


Explain Code

更新 wrangler.jsonc 以注册 agent:

JSONC


{

  "name": "my-agent",

  "main": "src/server.ts",

  // Set this to today's date

  "compatibility_date": "2026-04-29",

  "compatibility_flags": ["nodejs_compat"],

  "durable_objects": {

    "bindings": [

      {

        "name": "CounterAgent",

        "class_name": "CounterAgent",

      },

    ],

  },

  "migrations": [

    {

      "tag": "v1",

      "new_sqlite_classes": ["CounterAgent"],

    },

  ],

}


Explain Code

TOML


name = "my-agent"

main = "src/server.ts"

# Set this to today's date

compatibility_date = "2026-04-29"

compatibility_flags = [ "nodejs_compat" ]


[[durable_objects.bindings]]

name = "CounterAgent"

class_name = "CounterAgent"


[[migrations]]

tag = "v1"

new_sqlite_classes = [ "CounterAgent" ]


Explain Code

从 React 连接

替换 src/client.tsx:

src/client.tsx


import "./styles.css";

import { createRoot } from "react-dom/client";

import { useState } from "react";

import { useAgent } from "agents/react";

import type { CounterAgent, CounterState } from "./server";


export default function App() {

  const [count, setCount] = useState(0);


  // Connect to the Counter agent

  const agent = useAgent<CounterAgent, CounterState>({

    agent: "CounterAgent",

    onStateUpdate: (state) => setCount(state.count),

  });


  return (

    <div style={{ padding: "2rem", fontFamily: "system-ui" }}>

      <h1>Counter Agent</h1>

      <p style={{ fontSize: "3rem" }}>{count}</p>

      <div style={{ display: "flex", gap: "1rem" }}>

        <button onClick={() => agent.stub.decrement()}>-</button>

        <button onClick={() => agent.stub.reset()}>Reset</button>

        <button onClick={() => agent.stub.increment()}>+</button>

      </div>

    </div>

  );

}


const root = createRoot(document.getElementById("root")!);

root.render(<App />);


Explain Code

要点:

  • useAgent 通过 WebSocket 连接到你的 agent
  • onStateUpdate 在 agent 状态变化时触发
  • agent.stub.methodName() 调用 agent 上用 @callable() 标记的方法

刚才发生了什么?

当你点击按钮时:

  1. 客户端 通过 WebSocket 调用 agent.stub.increment()
  2. Agent 运行 increment(),通过 setState() 更新状态
  3. 状态 自动持久化到 SQLite
  4. 广播 发送给所有连接的客户端
  5. React 通过 onStateUpdate 更新

flowchart LR A[“Browser
(React)”] <–>|WebSocket| B[“Agent
(Counter)”] B –> C[“SQLite
(State)”]

关键概念

概念含义
Agent 实例每个唯一的名称对应一个独立的 agent。CounterAgent:user-123 与 CounterAgent:user-456 是分开的
持久状态状态挺过重启、部署和休眠,存储在 SQLite 中
实时同步连接到同一个 agent 的所有客户端会立即收到状态更新
休眠没有客户端连接时,agent 进入休眠(零成本)。下次请求时被唤醒

从 vanilla JavaScript 连接

如果你不使用 React:

JavaScript


import { AgentClient } from "agents/client";


const agent = new AgentClient({

  agent: "CounterAgent",

  name: "my-counter", // optional, defaults to "default"

  onStateUpdate: (state) => {

    console.log("New count:", state.count);

  },

});


// Call methods

await agent.call("increment");

await agent.call("reset");


Explain Code

TypeScript


import { AgentClient } from "agents/client";


const agent = new AgentClient({

  agent: "CounterAgent",

  name: "my-counter", // optional, defaults to "default"

  onStateUpdate: (state) => {

    console.log("New count:", state.count);

  },

});


// Call methods

await agent.call("increment");

await agent.call("reset");


Explain Code

部署到 Cloudflare

Terminal 窗口


npm run deploy


你的 agent 现在已经上线 Cloudflare 全球网络,运行在靠近用户的位置。

故障排查

“Agent not found” 或 404 错误

确保:

  1. Agent 类已从你的服务端文件中导出
  2. wrangler.jsonc 中包含 binding 和 migration
  3. 客户端中的 agent 名称与类名匹配(不区分大小写)

状态没有同步

检查:

  1. 你调用的是 this.setState(),而不是直接修改 this.state
  2. 客户端中已经接好 onStateUpdate 回调
  3. WebSocket 连接已建立(检查浏览器开发者工具)

“Method X is not callable” 错误

确保你的方法用 @callable() 装饰:

JavaScript


import { Agent, callable } from "agents";


export class MyAgent extends Agent {

  @callable()

  increment() {

    // ...

  }

}


TypeScript


import { Agent, callable } from "agents";


export class MyAgent extends Agent {

  @callable()

  increment() {

    // ...

  }

}


agent.stub 的类型错误

加上 agent 和 state 的类型参数:

JavaScript


import { useAgent } from "agents/react";

// Pass the agent and state types to useAgent

const agent = useAgent({

  agent: "CounterAgent",

  onStateUpdate: (state) => setCount(state.count),

});


// Now agent.stub is fully typed

agent.stub.increment();


TypeScript


import { useAgent } from "agents/react";

import type { CounterAgent, CounterState } from "./server";


// Pass the agent and state types to useAgent

const agent = useAgent<CounterAgent, CounterState>({

  agent: "CounterAgent",

  onStateUpdate: (state) => setCount(state.count),

});


// Now agent.stub is fully typed

agent.stub.increment();


Explain Code

@callable() 引发 SyntaxError: Invalid or unexpected token

如果开发服务器报 SyntaxError: Invalid or unexpected token,在 tsconfig.json 中设置 "target": "ES2021"。这能让 Vite 的 esbuild 转换器把 TC39 装饰器降级,而不是当作原生语法直接传递。


{

  "compilerOptions": {

    "target": "ES2021"

  }

}


警告

不要在 tsconfig.json 中设置 "experimentalDecorators": true。Agents SDK 使用的是 TC39 标准装饰器 ↗,不是 TypeScript 旧版装饰器。开启 experimentalDecorators 会应用一种不兼容的转换,在运行时静默破坏 @callable()

下一步

现在你已经有一个可用的 agent,可以继续探索这些主题:

常见模式

学习如何参阅
添加 AI/LLM 能力使用 AI 模型
通过 MCP 暴露工具MCP 服务器
运行后台任务调度任务
处理邮件邮件路由
使用 Cloudflare Workflows运行 Workflows

进一步探索

状态管理 深入了解 setState()、initialState 和 onStateChanged()。

客户端 SDK 完整的 useAgent 和 AgentClient API 参考。

可调用方法 用 @callable() 把方法暴露给客户端。

调度任务 按延迟、调度或 cron 运行任务。