添加到现有项目
本指南介绍如何在已有的 Cloudflare Workers 项目中加入 agents。如果你是从零开始,请改用 构建一个聊天 agent。
前置条件
- 一个已存在并带有 Wrangler 配置文件的 Cloudflare Workers 项目
- Node.js 18 或更新版本
1. 安装依赖包
npm yarn pnpm bun
npm i agents
yarn add agents
pnpm add agents
bun add agents
对于 React 应用,无需额外安装包 —— React 绑定已包含在内。
对于 Hono 应用:
npm yarn pnpm bun
npm i agents hono-agents
yarn add agents hono-agents
pnpm add agents hono-agents
bun add agents hono-agents
2. 创建一个 Agent
为你的 agent 新建一个文件(例如 src/agents/counter.ts):
JavaScript
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;
}
}
Explain Code
TypeScript
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;
}
}
Explain Code
3. 更新 Wrangler 配置
添加 Durable Object 绑定与 migration:
JSONC
{
"name": "my-existing-project",
"main": "src/index.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-existing-project"
main = "src/index.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
要点:
- 绑定中的
name会成为env上的属性(例如env.CounterAgent) class_name必须与你导出的类名完全一致new_sqlite_classes启用 SQLite 存储以持久化 statenodejs_compatflag 是 agents 包的必备项
4. 配置 TypeScript 与 Vite
如果你使用 @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 装饰器转换):
JavaScript
import agents from "agents/vite";
export default defineConfig({
plugins: [
agents(),
// ... your existing plugins
],
});
TypeScript
import agents from "agents/vite";
export default defineConfig({
plugins: [
agents(),
// ... your existing plugins
],
});
如果项目不使用 Vite,只更新 tsconfig.json 也足够 —— 你的打包工具必须支持 TC39 装饰器(stage 3,版本 2023-11)。
更多细节请参阅 TypeScript 配置 与 Vite 配置 参考。
5. 导出 Agent 类
你的 agent 类必须从主入口文件导出。更新 src/index.ts:
JavaScript
// Export the agent class (required for Durable Objects)
export { CounterAgent } from "./agents/counter";
// Your existing exports...
export default {
// ...
};
TypeScript
// Export the agent class (required for Durable Objects)
export { CounterAgent } from "./agents/counter";
// Your existing exports...
export default {
// ...
} satisfies ExportedHandler<Env>;
6. 接入路由
根据你的项目结构选择对应方式:
纯 Workers(fetch handler)
JavaScript
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 });
},
};
Explain Code
TypeScript
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>;
Explain Code
Hono
JavaScript
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;
Explain Code
TypeScript
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;
Explain Code
与静态资源一起使用
如果你在提供 agents 的同时也提供静态资源,默认情况下静态资源优先返回。Worker 代码只在路径未匹配静态资源时才运行:
JavaScript
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 });
},
};
Explain Code
TypeScript
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>;
Explain Code
在 Wrangler 配置文件中配置静态资源:
JSONC
{
"assets": {
"directory": "./public",
},
}
TOML
[assets]
directory = "./public"
7. 生成 TypeScript 类型
不要手写 Env 接口。运行 wrangler types 生成与 Wrangler 配置一致的类型定义文件。这样能在编译期、而非部署期发现配置和代码之间的差异。
每次新增或重命名绑定后,请重新运行 wrangler types。
Terminal window
npx wrangler types
这会生成一个类型定义文件,把所有绑定都加上类型,包括你的 agent Durable Object 命名空间。Agent 类默认使用生成的 Env 类型,因此通常不需要显式传类型参数 —— extends Agent 就够了,除非你需要传第二个类型参数表示 state(例如 Agent<Env, CounterState>)。
更多类型生成细节,请参阅 Configuration。
8. 在前端建立连接
React
JavaScript
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>
</>
);
}
Explain Code
TypeScript
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>
</>
);
}
Explain Code
原生 JavaScript
JavaScript
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");
Explain Code
TypeScript
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");
Explain Code
添加多个 agent
通过扩展配置即可添加更多 agent:
JavaScript
// src/agents/chat.ts
export class Chat extends Agent {
// ...
}
// src/agents/scheduler.ts
export class Scheduler extends Agent {
// ...
}
TypeScript
// src/agents/chat.ts
export class Chat extends Agent {
// ...
}
// src/agents/scheduler.ts
export class Scheduler extends Agent {
// ...
}
更新 Wrangler 配置文件:
JSONC
{
"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"],
},
],
}
Explain Code
TOML
[[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" ]
Explain Code
在入口文件中导出所有 agent:
JavaScript
export { CounterAgent } from "./agents/counter";
export { Chat } from "./agents/chat";
export { Scheduler } from "./agents/scheduler";
TypeScript
export { CounterAgent } from "./agents/counter";
export { Chat } from "./agents/chat";
export { Scheduler } from "./agents/scheduler";
常见集成模式
在认证之后接入 agent
在路由到 agent 之前先做认证:
JavaScript
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
},
};
Explain Code
TypeScript
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>;
Explain Code
自定义 agent 路径前缀
默认情况下,agent 的路由是 /agents/{agent-name}/{instance-name}。你可以自定义:
JavaScript
import { routeAgentRequest } from "agents";
const agentResponse = await routeAgentRequest(request, env, {
prefix: "/api/agents", // Now routes at /api/agents/{agent-name}/{instance-name}
});
TypeScript
import { routeAgentRequest } from "agents";
const agentResponse = await routeAgentRequest(request, env, {
prefix: "/api/agents", // Now routes at /api/agents/{agent-name}/{instance-name}
});
更多选项(包括 CORS、自定义实例命名、location hints)请参阅 Routing。
在服务端代码中访问 agent
你可以在 Worker 代码中直接与 agent 交互:
JavaScript
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 });
}
// ...
},
};
Explain Code
TypeScript
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>;
Explain Code
故障排查
Agent not found,或 404 错误
- 检查导出 —— Agent 类必须从主入口文件导出。
- 检查绑定 —— Wrangler 配置中的
class_name必须与导出的类名完全一致。 - 检查路由 —— 默认路由是
/agents/{agent-name}/{instance-name}。
No such Durable Object class 错误
在 Wrangler 配置文件中加上 migration:
JSONC
{
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["YourAgentClass"],
},
],
}
TOML
[[migrations]]
tag = "v1"
new_sqlite_classes = [ "YourAgentClass" ]
WebSocket 连接失败
确保你的路由把响应原样返回:
JavaScript
// 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);
TypeScript
// 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);
state 没有持久化
请检查:
- 你使用的是
this.setState(),而不是直接修改this.state。 - agent 类已加入 migrations 中的
new_sqlite_classes。 - 你正在连接同一个 agent 实例名。
后续步骤
State 管理 管理并同步 agent 的 state。
计划任务 后台任务与 cron 作业。
Agent 类内部细节 完整生命周期与方法参考。
Agents API Agents SDK 的完整 API 参考。