本指南说明如何向现有 Cloudflare Workers 项目添加 Agent。若从零开始,请参阅构建聊天 Agent。
- 带有 Wrangler 配置文件的现有 Cloudflare Workers 项目
- Node.js 18 或更高版本
npm i agentsyarn add agentspnpm add agentsbun add agents对于 React 应用,无需额外包——React 绑定(binding)已包含在内。
对于 Hono 应用:
npm i agents hono-agentsyarn add agents hono-agentspnpm add agents hono-agentsbun add agents hono-agents为 Agent 创建新文件(例如 src/agents/counter.ts):
import { Agent, callable } from "agents";
export class CounterAgent extends Agent {
initialState = { count: 0 };
@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;
}
}import { Agent, callable } from "agents";
export type CounterState = {
count: number;
};
export class CounterAgent extends Agent<Env, CounterState> {
initialState: CounterState = { count: 0 };
@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;
}
}添加 Durable Object 绑定和迁移:
{
"name": "my-existing-project",
"main": "src/index.ts",
// Set this to today's date
"compatibility_date": "2026-08-17",
"compatibility_flags": ["nodejs_compat"],
"durable_objects": {
"bindings": [
{
"name": "CounterAgent",
"class_name": "CounterAgent",
},
],
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["CounterAgent"],
},
],
}name = "my-existing-project"
main = "src/index.ts"
# Set this to today's date
compatibility_date = "2026-08-17"
compatibility_flags = [ "nodejs_compat" ]
[[durable_objects.bindings]]
name = "CounterAgent"
class_name = "CounterAgent"
[[migrations]]
tag = "v1"
new_sqlite_classes = [ "CounterAgent" ]要点:
- 绑定中的
name成为env上的属性(例如env.CounterAgent) class_name必须与导出的类名完全一致new_sqlite_classes启用 SQLite 存储以实现状态持久化- agents 包需要
nodejs_compat标志
若使用 @callable() 装饰器(如上述示例),需要两个构建配置。
tsconfig.json — 继承 agents/tsconfig(或手动设置 "target": "ES2021"):
{
"extends": "agents/tsconfig"
}若有带自定义设置的现有 tsconfig.json,可继承并覆盖:
{
"extends": "agents/tsconfig",
"compilerOptions": {
"paths": { "~/*": ["./src/*"] }
}
}vite.config.ts — 添加 agents() 插件(处理 Vite 8 的 TC39 装饰器转换):
import agents from "agents/vite";
export default defineConfig({
plugins: [
agents(),
// ... your existing plugins
],
});import agents from "agents/vite";
export default defineConfig({
plugins: [
agents(),
// ... your existing plugins
],
});若项目不使用 Vite,仅 tsconfig.json 更改即可——打包工具必须支持 TC39 装饰器(stage 3,version 2023-11)。
更多详情请参阅 TypeScript 配置 和 Vite 配置 参考。
Agent 类必须从主入口点导出。更新 src/index.ts:
// Export the agent class (required for Durable Objects)
export { CounterAgent } from "./agents/counter";
// Your existing exports...
export default {
// ...
};// Export the agent class (required for Durable Objects)
export { CounterAgent } from "./agents/counter";
// Your existing exports...
export default {
// ...
} satisfies ExportedHandler<Env>;选择与项目结构匹配的方案:
import { routeAgentRequest } from "agents";
export { CounterAgent } from "./agents/counter";
export default {
async fetch(request, env, ctx) {
// Try agent routing first
const agentResponse = await routeAgentRequest(request, env);
if (agentResponse) return agentResponse;
// Your existing routing logic
const url = new URL(request.url);
if (url.pathname === "/api/hello") {
return Response.json({ message: "Hello!" });
}
return new Response("Not found", { status: 404 });
},
};import { routeAgentRequest } from "agents";
export { CounterAgent } from "./agents/counter";
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
// Try agent routing first
const agentResponse = await routeAgentRequest(request, env);
if (agentResponse) return agentResponse;
// Your existing routing logic
const url = new URL(request.url);
if (url.pathname === "/api/hello") {
return Response.json({ message: "Hello!" });
}
return new Response("Not found", { status: 404 });
},
} satisfies ExportedHandler<Env>;import { Hono } from "hono";
import { agentsMiddleware } from "hono-agents";
export { CounterAgent } from "./agents/counter";
const app = new Hono();
// Add agents middleware - handles WebSocket upgrades and agent HTTP requests
app.use("*", agentsMiddleware());
// Your existing routes continue to work
app.get("/api/hello", (c) => c.json({ message: "Hello!" }));
export default app;import { Hono } from "hono";
import { agentsMiddleware } from "hono-agents";
export { CounterAgent } from "./agents/counter";
const app = new Hono<{ Bindings: Env }>();
// Add agents middleware - handles WebSocket upgrades and agent HTTP requests
app.use("*", agentsMiddleware());
// Your existing routes continue to work
app.get("/api/hello", (c) => c.json({ message: "Hello!" }));
export default app;若与 Agent 一起提供静态资源,默认先提供静态资源。Worker 代码仅对不匹配静态资源的路径运行:
import { routeAgentRequest } from "agents";
export { CounterAgent } from "./agents/counter";
export default {
async fetch(request, env, ctx) {
// Static assets are served automatically before this runs
// This only handles non-asset requests
// Route to agents
const agentResponse = await routeAgentRequest(request, env);
if (agentResponse) return agentResponse;
return new Response("Not found", { status: 404 });
},
};import { routeAgentRequest } from "agents";
export { CounterAgent } from "./agents/counter";
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
// Static assets are served automatically before this runs
// This only handles non-asset requests
// Route to agents
const agentResponse = await routeAgentRequest(request, env);
if (agentResponse) return agentResponse;
return new Response("Not found", { status: 404 });
},
} satisfies ExportedHandler<Env>;在 Wrangler 配置文件中配置资源:
{
"assets": {
"directory": "./public",
},
}[assets]
directory = "./public"不要手写 Env 接口。运行 wrangler types 生成与 Wrangler 配置匹配的类型定义文件。这可在编译时而非部署时发现配置与代码的不匹配。
添加或重命名绑定时重新运行 wrangler types。
npx wrangler types这将创建包含所有已类型化绑定的类型定义文件,包括 Agent Durable Object 命名空间。Agent 类默认使用生成的 Env 类型,因此无需将其作为类型参数传入——extends Agent 即可,除非需要传入第二个 state 类型参数(例如 Agent<Env, CounterState>)。
有关类型生成的更多详情,请参阅配置。
import { useState } from "react";
import { useAgent } from "agents/react";
function CounterWidget() {
const [count, setCount] = useState(0);
const agent = useAgent({
agent: "CounterAgent",
onStateUpdate: (state) => setCount(state.count),
});
return (
<>
{count}
<button onClick={() => agent.stub.increment()}>+</button>
<button onClick={() => agent.stub.decrement()}>-</button>
</>
);
}import { useState } from "react";
import { useAgent } from "agents/react";
import type { CounterAgent, CounterState } from "./agents/counter";
function CounterWidget() {
const [count, setCount] = useState(0);
const agent = useAgent<CounterAgent, CounterState>({
agent: "CounterAgent",
onStateUpdate: (state) => setCount(state.count),
});
return (
<>
{count}
<button onClick={() => agent.stub.increment()}>+</button>
<button onClick={() => agent.stub.decrement()}>-</button>
</>
);
}要点:
useAgent通过 WebSocket 连接 Agent- Agent 状态变更时触发
onStateUpdate agent.stub.methodName()调用 Agent 上用@callable()标记的方法
import { AgentClient } from "agents/client";
const agent = new AgentClient({
agent: "CounterAgent",
name: "user-123", // Optional: unique instance name
onStateUpdate: (state) => {
document.getElementById("count").textContent = state.count;
},
});
// Call methods
document.getElementById("increment").onclick = () => agent.call("increment");import { AgentClient } from "agents/client";
const agent = new AgentClient({
agent: "CounterAgent",
name: "user-123", // Optional: unique instance name
onStateUpdate: (state) => {
document.getElementById("count").textContent = state.count;
},
});
// Call methods
document.getElementById("increment").onclick = () => agent.call("increment");当你点击按钮时:
- 客户端 通过 WebSocket 调用
agent.stub.increment() - Agent 运行
increment(),通过setState()更新状态 - 状态 自动持久化到 SQLite
- 广播 发送给所有已连接的客户端
- React 通过
onStateUpdate更新
flowchart LR
A["Browser<br/>(React)"] <-->|WebSocket| B["Agent<br/>(Counter)"]
B --> C["SQLite<br/>(State)"]
| 概念 | 含义 |
|---|---|
| Agent 实例 | 每个唯一名称对应一个独立 Agent。CounterAgent:user-123 与 CounterAgent:user-456 相互独立 |
| 持久化状态 | 状态在重启、部署和休眠后仍然保留,存储在 SQLite 中 |
| 实时同步 | 连接到同一 Agent 的所有客户端会即时收到状态更新 |
| 休眠 | 无客户端连接时,Agent 进入休眠(无费用),下次请求时唤醒 |
npm run deploy你的 Agent 现已在 Cloudflare 全球网络上线,靠近用户运行。
在路由到 Agent 之前检查身份验证:
export default {
async fetch(request, env) {
// Check auth for agent routes
if (request.url.includes("/agents/")) {
const authResult = await checkAuth(request, env);
if (!authResult.valid) {
return new Response("Unauthorized", { status: 401 });
}
}
const agentResponse = await routeAgentRequest(request, env);
if (agentResponse) return agentResponse;
// ... rest of routing
},
};export default {
async fetch(request: Request, env: Env) {
// Check auth for agent routes
if (request.url.includes("/agents/")) {
const authResult = await checkAuth(request, env);
if (!authResult.valid) {
return new Response("Unauthorized", { status: 401 });
}
}
const agentResponse = await routeAgentRequest(request, env);
if (agentResponse) return agentResponse;
// ... rest of routing
},
} satisfies ExportedHandler<Env>;默认情况下,Agent 路由为 /agents/{agent-name}/{instance-name}。你可以自定义:
import { routeAgentRequest } from "agents";
const agentResponse = await routeAgentRequest(request, env, {
prefix: "/api/agents", // Now routes at /api/agents/{agent-name}/{instance-name}
});import { routeAgentRequest } from "agents";
const agentResponse = await routeAgentRequest(request, env, {
prefix: "/api/agents", // Now routes at /api/agents/{agent-name}/{instance-name}
});有关 CORS、自定义实例命名和 location hint 等更多选项,请参阅路由。
你可以直接从 Worker 代码与 Agent 交互:
import { getAgentByName } from "agents";
export default {
async fetch(request, env) {
if (request.url.endsWith("/api/increment")) {
// Get a specific agent instance
const counter = await getAgentByName(env.CounterAgent, "shared-counter");
const newCount = await counter.increment();
return Response.json({ count: newCount });
}
// ...
},
};import { getAgentByName } from "agents";
export default {
async fetch(request: Request, env: Env) {
if (request.url.endsWith("/api/increment")) {
// Get a specific agent instance
const counter = await getAgentByName(env.CounterAgent, "shared-counter");
const newCount = await counter.increment();
return Response.json({ count: newCount });
}
// ...
},
} satisfies ExportedHandler<Env>;通过扩展配置添加更多 Agent:
// src/agents/chat.ts
export class Chat extends Agent {
// ...
}
// src/agents/scheduler.ts
export class Scheduler extends Agent {
// ...
}// src/agents/chat.ts
export class Chat extends Agent {
// ...
}
// src/agents/scheduler.ts
export class Scheduler extends Agent {
// ...
}更新 Wrangler 配置文件:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"durable_objects": {
"bindings": [
{
"name": "CounterAgent",
"class_name": "CounterAgent"
},
{
"name": "Chat",
"class_name": "Chat"
},
{
"name": "Scheduler",
"class_name": "Scheduler"
}
]
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": [
"CounterAgent",
"Chat",
"Scheduler"
]
}
]
}[[durable_objects.bindings]]
name = "CounterAgent"
class_name = "CounterAgent"
[[durable_objects.bindings]]
name = "Chat"
class_name = "Chat"
[[durable_objects.bindings]]
name = "Scheduler"
class_name = "Scheduler"
[[migrations]]
tag = "v1"
new_sqlite_classes = ["CounterAgent", "Chat", "Scheduler"]从入口点导出所有 Agent:
export { CounterAgent } from "./agents/counter";
export { Chat } from "./agents/chat";
export { Scheduler } from "./agents/scheduler";export { CounterAgent } from "./agents/counter";
export { Chat } from "./agents/chat";
export { Scheduler } from "./agents/scheduler";- 检查导出 — Agent 类必须从主入口点导出。
- 检查绑定 — Wrangler 配置文件中的
class_name必须与导出的类名完全一致。 - 检查路由 — 默认路由为
/agents/{'{agent-name}'}/{'{instance-name}'}。客户端中的 Agent 名称需与类名匹配(不区分大小写)。
在 Wrangler 配置文件中添加 migration:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": [
"YourAgentClass"
]
}
]
}[[migrations]]
tag = "v1"
new_sqlite_classes = ["YourAgentClass"]确保路由原样返回响应:
// Correct - return the response directly
const agentResponse = await routeAgentRequest(request, env);
if (agentResponse) return agentResponse;
// Wrong - this breaks WebSocket connections
if (agentResponse) return new Response(agentResponse.body);// Correct - return the response directly
const agentResponse = await routeAgentRequest(request, env);
if (agentResponse) return agentResponse;
// Wrong - this breaks WebSocket connections
if (agentResponse) return new Response(agentResponse.body);请检查:
- 是否调用
this.setState(),而非直接修改this.state。 - Agent 类是否在 migrations 的
new_sqlite_classes中。 - 是否连接到同一 Agent 实例名称。
- 客户端是否已接入
onStateUpdate回调。 - WebSocket 连接是否已建立(在浏览器开发者工具中检查)。
确保方法已用 @callable() 装饰:
import { Agent, callable } from "agents";
export class MyAgent extends Agent {
@callable()
increment() {
// ...
}
}import { Agent, callable } from "agents";
export class MyAgent extends Agent {
@callable()
increment() {
// ...
}
}添加 Agent 和 state 类型参数:
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();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();若开发服务器因此失败,请在 tsconfig.json 中设置 "target": "ES2021"。这可确保 Vite 的 esbuild 转译器将 TC39 装饰器降级,而非作为原生语法保留。
{
"compilerOptions": {
"target": "ES2021"
}
}现在你已有可运行的 Agent,可探索以下主题:
| 学习目标 | 参考文档 |
|---|---|
| 添加 AI/LLM 能力 | 使用 AI 模型 |
| 通过 MCP 暴露工具 | MCP 服务器 |
| 运行后台任务 | 调度任务 |
| 处理邮件 | Email 路由 |
| 使用 Cloudflare Workflows | 运行 Workflows |