跳转到内容
搜索文档

添加到现有项目

最后更新 查看 MarkdownAgent 设置

本指南说明如何向现有 Cloudflare Workers 项目添加 Agent。若从零开始,请参阅构建聊天 Agent

前提条件

  • 带有 Wrangler 配置文件的现有 Cloudflare Workers 项目
  • Node.js 18 或更高版本

1. 安装包

npm i agents

对于 React 应用,无需额外包——React 绑定(binding)已包含在内。

对于 Hono 应用:

npm i agents hono-agents

2. 创建 Agent

为 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;
	}
}

3. 更新 Wrangler 配置

添加 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 标志

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 装饰器转换):

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 配置 参考。

5. 导出 Agent 类

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>;

6. 配置路由

选择与项目结构匹配的方案:

纯 Workers(fetch 处理器)

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>;

Hono

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"

7. 生成 TypeScript 类型

不要手写 Env 接口。运行 wrangler types 生成与 Wrangler 配置匹配的类型定义文件。这可在编译时而非部署时发现配置与代码的不匹配。

添加或重命名绑定时重新运行 wrangler types

npx wrangler types

这将创建包含所有已类型化绑定的类型定义文件,包括 Agent Durable Object 命名空间。Agent 类默认使用生成的 Env 类型,因此无需将其作为类型参数传入——extends Agent 即可,除非需要传入第二个 state 类型参数(例如 Agent<Env, CounterState>)。

有关类型生成的更多详情,请参阅配置

8. 从前端连接

React

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() 标记的方法

原生 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");
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");

工作原理

当你点击按钮时:

  1. 客户端 通过 WebSocket 调用 agent.stub.increment()
  2. Agent 运行 increment(),通过 setState() 更新状态
  3. 状态 自动持久化到 SQLite
  4. 广播 发送给所有已连接的客户端
  5. React 通过 onStateUpdate 更新
flowchart LR
    A["Browser<br/>(React)"] <-->|WebSocket| B["Agent<br/>(Counter)"]
    B --> C["SQLite<br/>(State)"]

核心概念

概念 含义
Agent 实例 每个唯一名称对应一个独立 Agent。CounterAgent:user-123CounterAgent:user-456 相互独立
持久化状态 状态在重启、部署和休眠后仍然保留,存储在 SQLite 中
实时同步 连接到同一 Agent 的所有客户端会即时收到状态更新
休眠 无客户端连接时,Agent 进入休眠(无费用),下次请求时唤醒

部署到 Cloudflare

npm run deploy

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

常见集成模式

带身份验证的 Agent

在路由到 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 路径前缀

默认情况下,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 等更多选项,请参阅路由

从服务端代码访问 Agent

你可以直接从 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

通过扩展配置添加更多 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,或 404 错误

  1. 检查导出 — Agent 类必须从主入口点导出。
  2. 检查绑定 — Wrangler 配置文件中的 class_name 必须与导出的类名完全一致。
  3. 检查路由 — 默认路由为 /agents/{'{agent-name}'}/{'{instance-name}'}。客户端中的 Agent 名称需与类名匹配(不区分大小写)。

No such Durable Object class 错误

在 Wrangler 配置文件中添加 migration:

{
  "$schema": "./node_modules/wrangler/config-schema.json",
  "migrations": [
    {
      "tag": "v1",
      "new_sqlite_classes": [
        "YourAgentClass"
      ]
    }
  ]
}
[[migrations]]
tag = "v1"
new_sqlite_classes = ["YourAgentClass"]

WebSocket 连接失败

确保路由原样返回响应:

// 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);

状态未持久化

请检查:

  1. 是否调用 this.setState(),而非直接修改 this.state
  2. Agent 类是否在 migrations 的 new_sqlite_classes 中。
  3. 是否连接到同一 Agent 实例名称。
  4. 客户端是否已接入 onStateUpdate 回调。
  5. WebSocket 连接是否已建立(在浏览器开发者工具中检查)。

"Method X is not callable" 错误

确保方法已用 @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.stub 类型错误

添加 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();

使用 @callable() 时出现 SyntaxError: Invalid or unexpected token

若开发服务器因此失败,请在 tsconfig.json 中设置 "target": "ES2021"。这可确保 Vite 的 esbuild 转译器将 TC39 装饰器降级,而非作为原生语法保留。

{
	"compilerOptions": {
		"target": "ES2021"
	}
}

后续步骤

现在你已有可运行的 Agent,可探索以下主题:

常见后续步骤

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

进一步探索

状态管理

深入了解 setState()、initialState 和 onStateChanged()。

这篇文档对您有帮助吗?