路由
本指南介绍请求如何被路由到 agents、命名机制如何工作,以及组织 agents 的常见模式。
路由如何工作
当请求到达时,routeAgentRequest() 会检查 URL 并将其路由到合适的 agent 实例:
https://your-worker.dev/agents/{agent-name}/{instance-name}
└────┬────┘ └─────┬─────┘
Class name Unique instance ID
(kebab-case)
示例 URL:
| URL | Agent 类 | 实例 |
|---|---|---|
| /agents/counter/user-123 | Counter | user-123 |
| /agents/chat-room/lobby | ChatRoom | lobby |
| /agents/my-agent/default | MyAgent | default |
名称解析
Agent 类名会自动转换为 URL 中的 kebab-case:
| 类名 | URL 路径 |
|---|---|
| Counter | /agents/counter/… |
| MyAgent | /agents/my-agent/… |
| ChatRoom | /agents/chat-room/… |
| AIAssistant | /agents/ai-assistant/… |
路由会同时匹配原始名称和 kebab-case 形式,所以两种写法都可用:
useAgent({ agent: "Counter" })→/agents/counter/...useAgent({ agent: "counter" })→/agents/counter/...
使用 routeAgentRequest()
routeAgentRequest() 是 agent 路由的主要入口:
JavaScript
import { routeAgentRequest } from "agents";
export default {
async fetch(request, env, ctx) {
// Route to agents - returns Response or undefined
const agentResponse = await routeAgentRequest(request, env);
if (agentResponse) {
return agentResponse;
}
// No agent matched - handle other routes
return new Response("Not found", { status: 404 });
},
};
Explain Code
TypeScript
import { routeAgentRequest } from "agents";
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
// Route to agents - returns Response or undefined
const agentResponse = await routeAgentRequest(request, env);
if (agentResponse) {
return agentResponse;
}
// No agent matched - handle other routes
return new Response("Not found", { status: 404 });
},
} satisfies ExportedHandler<Env>;
Explain Code
实例命名模式
实例名(URL 的最后一部分)决定了哪个 agent 实例处理请求。每个唯一的名称都会获得一个独立的 agent,带有自己的状态。
每个用户一个 agent
每位用户都有自己的 agent 实例:
JavaScript
// Client
const agent = useAgent({
agent: "UserProfile",
name: `user-${userId}`, // e.g., "user-abc123"
});
TypeScript
// Client
const agent = useAgent({
agent: "UserProfile",
name: `user-${userId}`, // e.g., "user-abc123"
});
/agents/user-profile/user-abc123 → User abc123's agent
/agents/user-profile/user-xyz789 → User xyz789's agent (separate instance)
共享房间
多个用户共享同一个 agent 实例:
JavaScript
// Client
const agent = useAgent({
agent: "ChatRoom",
name: roomId, // e.g., "general" or "room-42"
});
TypeScript
// Client
const agent = useAgent({
agent: "ChatRoom",
name: roomId, // e.g., "general" or "room-42"
});
/agents/chat-room/general → All users in "general" share this agent
全局单例
整个应用使用单个实例:
JavaScript
// Client
const agent = useAgent({
agent: "AppConfig",
name: "default", // Or any consistent name
});
TypeScript
// Client
const agent = useAgent({
agent: "AppConfig",
name: "default", // Or any consistent name
});
动态命名
根据上下文生成实例名:
JavaScript
// Per-session
const agent = useAgent({
agent: "Session",
name: sessionId,
});
// Per-document
const agent = useAgent({
agent: "Document",
name: `doc-${documentId}`,
});
// Per-game
const agent = useAgent({
agent: "Game",
name: `game-${gameId}-${Date.now()}`,
});
Explain Code
TypeScript
// Per-session
const agent = useAgent({
agent: "Session",
name: sessionId,
});
// Per-document
const agent = useAgent({
agent: "Document",
name: `doc-${documentId}`,
});
// Per-game
const agent = useAgent({
agent: "Game",
name: `game-${gameId}-${Date.now()}`,
});
Explain Code
自定义 URL 路由
对于需要控制 URL 结构的高级用例,你可以绕过默认的 /agents/{agent}/{name} 模式。
使用 basePath(客户端)
basePath 选项让客户端可以连接到任意 URL 路径:
JavaScript
// Client connects to /user instead of /agents/user-agent/...
const agent = useAgent({
agent: "UserAgent", // Required but ignored when basePath is set
basePath: "user", // → connects to /user
});
TypeScript
// Client connects to /user instead of /agents/user-agent/...
const agent = useAgent({
agent: "UserAgent", // Required but ignored when basePath is set
basePath: "user", // → connects to /user
});
适用场景:
- 你想要不带
/agents/前缀的简洁 URL - 实例名由服务端决定(例如来自 auth/session)
- 你正在与已有的 URL 结构集成
服务端实例选择
使用 basePath 时,必须由服务端处理路由。先用 getAgentByName() 获取 agent 实例,再用 fetch() 把请求转发过去:
JavaScript
export default {
async fetch(request, env) {
const url = new URL(request.url);
// Custom routing - server determines instance from session
if (url.pathname.startsWith("/user/")) {
const session = await getSession(request);
const agent = await getAgentByName(env.UserAgent, session.userId);
return agent.fetch(request); // Forward request directly to agent
}
// Default routing for standard /agents/... paths
return (
(await routeAgentRequest(request, env)) ??
new Response("Not found", { status: 404 })
);
},
};
Explain Code
TypeScript
export default {
async fetch(request: Request, env: Env) {
const url = new URL(request.url);
// Custom routing - server determines instance from session
if (url.pathname.startsWith("/user/")) {
const session = await getSession(request);
const agent = await getAgentByName(env.UserAgent, session.userId);
return agent.fetch(request); // Forward request directly to agent
}
// Default routing for standard /agents/... paths
return (
(await routeAgentRequest(request, env)) ??
new Response("Not found", { status: 404 })
);
},
} satisfies ExportedHandler<Env>;
Explain Code
自定义路径搭配动态实例
将不同路径路由到不同实例:
JavaScript
// Route /chat/{room} to ChatRoom agent
if (url.pathname.startsWith("/chat/")) {
const roomId = url.pathname.replace("/chat/", "");
const agent = await getAgentByName(env.ChatRoom, roomId);
return agent.fetch(request);
}
// Route /doc/{id} to Document agent
if (url.pathname.startsWith("/doc/")) {
const docId = url.pathname.replace("/doc/", "");
const agent = await getAgentByName(env.Document, docId);
return agent.fetch(request);
}
Explain Code
TypeScript
// Route /chat/{room} to ChatRoom agent
if (url.pathname.startsWith("/chat/")) {
const roomId = url.pathname.replace("/chat/", "");
const agent = await getAgentByName(env.ChatRoom, roomId);
return agent.fetch(request);
}
// Route /doc/{id} to Document agent
if (url.pathname.startsWith("/doc/")) {
const docId = url.pathname.replace("/doc/", "");
const agent = await getAgentByName(env.Document, docId);
return agent.fetch(request);
}
Explain Code
在客户端获取实例身份
使用 basePath 时,客户端在服务器返回信息之前并不知道自己连接到了哪个实例。Agent 会在连接时自动发送身份信息:
JavaScript
const agent = useAgent({
agent: "UserAgent",
basePath: "user",
onIdentity: (name, agentType) => {
console.log(`Connected to ${agentType} instance: ${name}`);
// e.g., "Connected to user-agent instance: user-123"
},
});
// Reactive state - re-renders when identity is received
return (
<div>
{agent.identified ? `Connected to: ${agent.name}` : "Connecting..."}
</div>
);
Explain Code
TypeScript
const agent = useAgent({
agent: "UserAgent",
basePath: "user",
onIdentity: (name, agentType) => {
console.log(`Connected to ${agentType} instance: ${name}`);
// e.g., "Connected to user-agent instance: user-123"
},
});
// Reactive state - re-renders when identity is received
return (
<div>
{agent.identified ? `Connected to: ${agent.name}` : "Connecting..."}
</div>
);
Explain Code
对于 AgentClient:
JavaScript
const agent = new AgentClient({
agent: "UserAgent",
basePath: "user",
host: "example.com",
onIdentity: (name, agentType) => {
// Update UI with actual instance name
setInstanceName(name);
},
});
// Wait for identity before proceeding
await agent.ready;
console.log(agent.name); // Now has the server-determined name
Explain Code
TypeScript
const agent = new AgentClient({
agent: "UserAgent",
basePath: "user",
host: "example.com",
onIdentity: (name, agentType) => {
// Update UI with actual instance name
setInstanceName(name);
},
});
// Wait for identity before proceeding
await agent.ready;
console.log(agent.name); // Now has the server-determined name
Explain Code
重连时的身份变化处理
如果重连时身份发生变化(例如 session 失效后用户以另一个身份登录),你可以用 onIdentityChange 处理:
JavaScript
const agent = useAgent({
agent: "UserAgent",
basePath: "user",
onIdentityChange: (oldName, newName, oldAgent, newAgent) => {
console.log(`Session changed: ${oldName} → ${newName}`);
// Refresh state, show notification, etc.
},
});
TypeScript
const agent = useAgent({
agent: "UserAgent",
basePath: "user",
onIdentityChange: (oldName, newName, oldAgent, newAgent) => {
console.log(`Session changed: ${oldName} → ${newName}`);
// Refresh state, show notification, etc.
},
});
如果未提供 onIdentityChange 而身份发生变化,系统会打印一条警告,以便发现意外的会话变更。
出于安全考虑禁用身份信息
如果你的实例名包含敏感数据(session ID、内部用户 ID),你可以禁用身份发送:
JavaScript
class SecureAgent extends Agent {
// Do not expose instance names to clients
static options = { sendIdentityOnConnect: false };
}
TypeScript
class SecureAgent extends Agent {
// Do not expose instance names to clients
static options = { sendIdentityOnConnect: false };
}
身份信息被禁用后:
agent.identified始终为falseagent.ready永远不会 resolve(请改用状态更新)onIdentity与onIdentityChange永远不会被调用
何时使用自定义路由
| 场景 | 方式 |
|---|---|
| 标准 agent 访问 | 默认 /agents/{agent}/{name} |
| 实例来自 auth/session | basePath + getAgentByName + fetch |
| 简洁 URL(无 /agents/ 前缀) | basePath +自定义路由 |
| 遗留 URL 结构 | basePath +自定义路由 |
| 复杂的路由逻辑 | 在 Worker 中自定义路由 |
路由选项
routeAgentRequest() 与 getAgentByName() 都接受用于自定义路由行为的选项。
CORS
跨域请求(常见于前端在不同域名时):
JavaScript
const response = await routeAgentRequest(request, env, {
cors: true, // Enable default CORS headers
});
TypeScript
const response = await routeAgentRequest(request, env, {
cors: true, // Enable default CORS headers
});
或使用自定义 CORS 头部:
JavaScript
const response = await routeAgentRequest(request, env, {
cors: {
"Access-Control-Allow-Origin": "https://myapp.com",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
},
});
TypeScript
const response = await routeAgentRequest(request, env, {
cors: {
"Access-Control-Allow-Origin": "https://myapp.com",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
},
});
位置提示
对于延迟敏感的应用,可以提示 agent 应在哪里运行:
JavaScript
// With getAgentByName
const agent = await getAgentByName(env.MyAgent, "instance-name", {
locationHint: "enam", // Eastern North America
});
// With routeAgentRequest (applies to all matched agents)
const response = await routeAgentRequest(request, env, {
locationHint: "enam",
});
TypeScript
// With getAgentByName
const agent = await getAgentByName(env.MyAgent, "instance-name", {
locationHint: "enam", // Eastern North America
});
// With routeAgentRequest (applies to all matched agents)
const response = await routeAgentRequest(request, env, {
locationHint: "enam",
});
可用的 location hint:wnam、enam、sam、weur、eeur、apac、oc、afr、me
数据辖区(Jurisdiction)
适用于数据驻留(data residency)合规要求:
JavaScript
// With getAgentByName
const agent = await getAgentByName(env.MyAgent, "instance-name", {
jurisdiction: "eu", // EU jurisdiction
});
// With routeAgentRequest (applies to all matched agents)
const response = await routeAgentRequest(request, env, {
jurisdiction: "eu",
});
TypeScript
// With getAgentByName
const agent = await getAgentByName(env.MyAgent, "instance-name", {
jurisdiction: "eu", // EU jurisdiction
});
// With routeAgentRequest (applies to all matched agents)
const response = await routeAgentRequest(request, env, {
jurisdiction: "eu",
});
Props
由于 agent 是由运行时实例化的而非直接构造,因此 props 提供了一种传递初始化参数的方法:
JavaScript
const agent = await getAgentByName(env.MyAgent, "instance-name", {
props: {
userId: session.userId,
config: { maxRetries: 3 },
},
});
TypeScript
const agent = await getAgentByName(env.MyAgent, "instance-name", {
props: {
userId: session.userId,
config: { maxRetries: 3 },
},
});
Props 会被传给 agent 的 onStart 生命周期方法:
JavaScript
class MyAgent extends Agent {
userId;
config;
async onStart(props) {
this.userId = props?.userId;
this.config = props?.config;
}
}
TypeScript
class MyAgent extends Agent<Env, State> {
private userId?: string;
private config?: { maxRetries: number };
async onStart(props?: { userId: string; config: { maxRetries: number } }) {
this.userId = props?.userId;
this.config = props?.config;
}
}
通过 routeAgentRequest 使用 props 时,无论哪个 agent 匹配该 URL,都会收到相同的 props。这非常适合通用上下文,例如认证信息:
JavaScript
export default {
async fetch(request, env) {
const session = await getSession(request);
return routeAgentRequest(request, env, {
props: { userId: session.userId, role: session.role },
});
},
};
TypeScript
export default {
async fetch(request, env) {
const session = await getSession(request);
return routeAgentRequest(request, env, {
props: { userId: session.userId, role: session.role },
});
},
} satisfies ExportedHandler<Env>;
如果是面向特定 agent 的初始化,请改用 getAgentByName,以便精确控制哪个 agent 接收 props。
注意
对于 McpAgent,props 会被自动存储,并可通过 this.props 访问。详见 MCP servers。
钩子(Hooks)
routeAgentRequest 支持在请求到达 agent 之前进行拦截的钩子:
JavaScript
const response = await routeAgentRequest(request, env, {
onBeforeConnect: (req, lobby) => {
// Called before WebSocket connections
// Return a Response to reject, Request to modify, or void to continue
},
onBeforeRequest: (req, lobby) => {
// Called before HTTP requests
// Return a Response to reject, Request to modify, or void to continue
},
});
Explain Code
TypeScript
const response = await routeAgentRequest(request, env, {
onBeforeConnect: (req, lobby) => {
// Called before WebSocket connections
// Return a Response to reject, Request to modify, or void to continue
},
onBeforeRequest: (req, lobby) => {
// Called before HTTP requests
// Return a Response to reject, Request to modify, or void to continue
},
});
Explain Code
这些钩子适合做认证和校验。详细示例见 Cross-domain authentication。
服务端访问 agent
你可以在 Worker 代码中通过 getAgentByName() 进行 RPC 调用:
JavaScript
import { getAgentByName, routeAgentRequest } from "agents";
export default {
async fetch(request, env) {
const url = new URL(request.url);
// API endpoint that interacts with an agent
if (url.pathname === "/api/increment") {
const counter = await getAgentByName(env.Counter, "global-counter");
const newCount = await counter.increment();
return Response.json({ count: newCount });
}
// Regular agent routing
return (
(await routeAgentRequest(request, env)) ??
new Response("Not found", { status: 404 })
);
},
};
Explain Code
TypeScript
import { getAgentByName, routeAgentRequest } from "agents";
export default {
async fetch(request: Request, env: Env) {
const url = new URL(request.url);
// API endpoint that interacts with an agent
if (url.pathname === "/api/increment") {
const counter = await getAgentByName(env.Counter, "global-counter");
const newCount = await counter.increment();
return Response.json({ count: newCount });
}
// Regular agent routing
return (
(await routeAgentRequest(request, env)) ??
new Response("Not found", { status: 404 })
);
},
} satisfies ExportedHandler<Env>;
Explain Code
关于 locationHint、jurisdiction、props 等选项,详见 路由选项。
子路径与 HTTP 方法
请求可以在实例名之后包含子路径。这些子路径会被传递给 agent 的 onRequest() 处理函数:
/agents/api/v1/users → agent: "api", instance: "v1", path: "/users"
/agents/api/v1/users/123 → agent: "api", instance: "v1", path: "/users/123"
在 agent 中处理子路径:
JavaScript
export class API extends Agent {
async onRequest(request) {
const url = new URL(request.url);
// url.pathname contains the full path including /agents/api/v1/...
// Extract the sub-path after your agent's base path
const path = url.pathname.replace(/^\/agents\/api\/[^/]+/, "");
if (request.method === "GET" && path === "/users") {
return Response.json(await this.getUsers());
}
if (request.method === "POST" && path === "/users") {
const data = await request.json();
return Response.json(await this.createUser(data));
}
return new Response("Not found", { status: 404 });
}
}
Explain Code
TypeScript
export class API extends Agent {
async onRequest(request: Request): Promise<Response> {
const url = new URL(request.url);
// url.pathname contains the full path including /agents/api/v1/...
// Extract the sub-path after your agent's base path
const path = url.pathname.replace(/^\/agents\/api\/[^/]+/, "");
if (request.method === "GET" && path === "/users") {
return Response.json(await this.getUsers());
}
if (request.method === "POST" && path === "/users") {
const data = await request.json();
return Response.json(await this.createUser(data));
}
return new Response("Not found", { status: 404 });
}
}
Explain Code
多个 agents
一个项目可以包含多个 agent 类。每个都有自己的命名空间:
JavaScript
// server.ts
export { Counter } from "./agents/counter";
export { ChatRoom } from "./agents/chat-room";
export { UserProfile } from "./agents/user-profile";
export default {
async fetch(request, env) {
return (
(await routeAgentRequest(request, env)) ??
new Response("Not found", { status: 404 })
);
},
};
Explain Code
TypeScript
// server.ts
export { Counter } from "./agents/counter";
export { ChatRoom } from "./agents/chat-room";
export { UserProfile } from "./agents/user-profile";
export default {
async fetch(request: Request, env: Env) {
return (
(await routeAgentRequest(request, env)) ??
new Response("Not found", { status: 404 })
);
},
} satisfies ExportedHandler<Env>;
Explain Code
JSONC
{
"durable_objects": {
"bindings": [
{ "name": "Counter", "class_name": "Counter" },
{ "name": "ChatRoom", "class_name": "ChatRoom" },
{ "name": "UserProfile", "class_name": "UserProfile" },
],
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["Counter", "ChatRoom", "UserProfile"],
},
],
}
Explain Code
TOML
[[durable_objects.bindings]]
name = "Counter"
class_name = "Counter"
[[durable_objects.bindings]]
name = "ChatRoom"
class_name = "ChatRoom"
[[durable_objects.bindings]]
name = "UserProfile"
class_name = "UserProfile"
[[migrations]]
tag = "v1"
new_sqlite_classes = [ "Counter", "ChatRoom", "UserProfile" ]
Explain Code
每个 agent 通过自己的路径访问:
/agents/counter/...
/agents/chat-room/...
/agents/user-profile/...
请求流程
下面展示一个请求如何在系统中流转:
flowchart TD
A[“HTTP Request
or WebSocket”] –> B[“routeAgentRequest
Parse URL path”]
B –> C[“Find binding in
env by name”]
C –> D[“Get/create DO
by instance ID”]
D –> E[“Agent Instance”]
E –> F{“Protocol?”}
F –>|WebSocket| G[“onConnect(), onMessage”]
F –>|HTTP| H[“onRequest()”]
路由结合认证
在请求到达 agent 之前,有几种认证方式可选。
使用认证钩子
routeAgentRequest() 提供了 onBeforeConnect 和 onBeforeRequest 钩子用于认证:
JavaScript
import { Agent, routeAgentRequest } from "agents";
export default {
async fetch(request, env) {
return (
(await routeAgentRequest(request, env, {
// Run before WebSocket connections
onBeforeConnect: async (request) => {
const token = new URL(request.url).searchParams.get("token");
if (!(await verifyToken(token, env))) {
// Return a response to reject the connection
return new Response("Unauthorized", { status: 401 });
}
// Return nothing to allow the connection
},
// Run before HTTP requests
onBeforeRequest: async (request) => {
const auth = request.headers.get("Authorization");
if (!auth || !(await verifyAuth(auth, env))) {
return new Response("Unauthorized", { status: 401 });
}
},
// Optional: prepend a prefix to agent instance names
prefix: "user-",
})) ?? new Response("Not found", { status: 404 })
);
},
};
Explain Code
TypeScript
import { Agent, routeAgentRequest } from "agents";
export default {
async fetch(request: Request, env: Env) {
return (
(await routeAgentRequest(request, env, {
// Run before WebSocket connections
onBeforeConnect: async (request) => {
const token = new URL(request.url).searchParams.get("token");
if (!(await verifyToken(token, env))) {
// Return a response to reject the connection
return new Response("Unauthorized", { status: 401 });
}
// Return nothing to allow the connection
},
// Run before HTTP requests
onBeforeRequest: async (request) => {
const auth = request.headers.get("Authorization");
if (!auth || !(await verifyAuth(auth, env))) {
return new Response("Unauthorized", { status: 401 });
}
},
// Optional: prepend a prefix to agent instance names
prefix: "user-",
})) ?? new Response("Not found", { status: 404 })
);
},
} satisfies ExportedHandler<Env>;
Explain Code
手动认证
在调用 routeAgentRequest() 之前进行认证检查:
JavaScript
export default {
async fetch(request, env) {
const url = new URL(request.url);
// Protect agent routes
if (url.pathname.startsWith("/agents/")) {
const user = await authenticate(request, env);
if (!user) {
return new Response("Unauthorized", { status: 401 });
}
// Optionally, enforce that users can only access their own agents
const instanceName = url.pathname.split("/")[3];
if (instanceName !== `user-${user.id}`) {
return new Response("Forbidden", { status: 403 });
}
}
return (
(await routeAgentRequest(request, env)) ??
new Response("Not found", { status: 404 })
);
},
};
Explain Code
TypeScript
export default {
async fetch(request: Request, env: Env) {
const url = new URL(request.url);
// Protect agent routes
if (url.pathname.startsWith("/agents/")) {
const user = await authenticate(request, env);
if (!user) {
return new Response("Unauthorized", { status: 401 });
}
// Optionally, enforce that users can only access their own agents
const instanceName = url.pathname.split("/")[3];
if (instanceName !== `user-${user.id}`) {
return new Response("Forbidden", { status: 403 });
}
}
return (
(await routeAgentRequest(request, env)) ??
new Response("Not found", { status: 404 })
);
},
} satisfies ExportedHandler<Env>;
Explain Code
使用框架(Hono)
如果你使用 Hono ↗ 等框架,可在中间件中先认证再调用 agent:
JavaScript
import { Agent, getAgentByName } from "agents";
import { Hono } from "hono";
const app = new Hono();
// Authentication middleware
app.use("/agents/*", async (c, next) => {
const token = c.req.header("Authorization")?.replace("Bearer ", "");
if (!token || !(await verifyToken(token, c.env))) {
return c.json({ error: "Unauthorized" }, 401);
}
await next();
});
// Route to a specific agent
app.all("/agents/code-review/:id/*", async (c) => {
const id = c.req.param("id");
const agent = await getAgentByName(c.env.CodeReviewAgent, id);
return agent.fetch(c.req.raw);
});
export default app;
Explain Code
TypeScript
import { Agent, getAgentByName } from "agents";
import { Hono } from "hono";
const app = new Hono<{ Bindings: Env }>();
// Authentication middleware
app.use("/agents/*", async (c, next) => {
const token = c.req.header("Authorization")?.replace("Bearer ", "");
if (!token || !(await verifyToken(token, c.env))) {
return c.json({ error: "Unauthorized" }, 401);
}
await next();
});
// Route to a specific agent
app.all("/agents/code-review/:id/*", async (c) => {
const id = c.req.param("id");
const agent = await getAgentByName(c.env.CodeReviewAgent, id);
return agent.fetch(c.req.raw);
});
export default app;
Explain Code
WebSocket 认证模式(URL 中传 token、JWT 刷新)详见 Cross-domain authentication。
故障排查
Agent namespace not found
错误信息会列出可用的 agents。请检查:
- Agent 类是否从入口文件导出。
- 代码中的类名是否与
wrangler.jsonc中的class_name一致。 - URL 是否使用了正确的 kebab-case 名称。
请求返回 404
- 验证 URL 模式:
/agents/{agent-name}/{instance-name}。 - 检查
routeAgentRequest()是否在 404 处理程序之前被调用。 - 确保
routeAgentRequest()的返回值被返回(而不仅仅是被调用)。
WebSocket 连接失败
- 不要修改
routeAgentRequest()返回的 WebSocket upgrade 响应。 - 如果跨域连接,请确保启用了 CORS。
- 在浏览器开发者工具中查看实际错误。
basePath 不生效
- 确保你的 Worker 处理了自定义路径并转发到了 agent。
- 使用
getAgentByName()+agent.fetch(request)转发请求。 - 设置
basePath时仍需提供agent参数,但它会被忽略。 - 确认服务端路由与客户端
basePath一致。
API 参考
routeAgentRequest(request, env, options?)
将请求路由到合适的 agent。
| 参数 | 类型 | 说明 |
|---|---|---|
| request | Request | 进来的请求 |
| env | Env | 包含 agent 绑定的环境 |
| options.cors | boolean | HeadersInit | 启用 CORS 头部 |
| options.props | Record<string, unknown> | 传递给处理请求的 agent 的 props |
| options.locationHint | string | agent 实例的首选位置 |
| options.jurisdiction | string | agent 实例的数据辖区 |
| options.onBeforeConnect | Function | WebSocket 连接前的回调 |
| options.onBeforeRequest | Function | HTTP 请求前的回调 |
返回值: Promise<Response | undefined> —— 匹配时返回 Response,无 agent 路由时返回 undefined。
getAgentByName(namespace, name, options?)
按名称获取 agent 实例,用于服务端 RPC 调用或请求转发。
| 参数 | 类型 | 说明 |
|---|---|---|
| namespace | DurableObjectNamespace<T> | 来自 env 的 agent 绑定 |
| name | string | 实例名 |
| options.locationHint | string | 首选位置 |
| options.jurisdiction | string | 数据辖区 |
| options.props | Record<string, unknown> | 传给 onStart 的初始化属性 |
返回值: Promise<DurableObjectStub<T>> —— 用于调用 agent 方法或转发请求的类型化 stub。
useAgent(options) / AgentClient 选项
用于自定义路由的客户端连接选项:
| 选项 | 类型 | 说明 |
|---|---|---|
| agent | string | Agent 类名(必填) |
| name | string | 实例名(默认值:“default”) |
| basePath | string | 完整 URL 路径 —— 绕过 agent/name 的 URL 拼接 |
| path | string | 追加到 URL 末尾的额外路径 |
| onIdentity | (name, agent) => void | 服务器发送身份信息时调用 |
| onIdentityChange | (oldName, newName, oldAgent, newAgent) => void | 重连时身份变化时调用 |
返回值属性(React hook):
| 属性 | 类型 | 说明 |
|---|---|---|
| name | string | 当前实例名(响应式) |
| agent | string | 当前 agent 类名(响应式) |
| identified | boolean | 是否已收到身份信息(响应式) |
| ready | Promise<void> | 收到身份信息时 resolve |
Agent.options(服务端)
agent 配置的静态选项:
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| hibernate | boolean | true | agent 空闲时是否进入 hibernate |
| sendIdentityOnConnect | boolean | true | 连接时是否向客户端发送身份信息 |
| hungScheduleTimeoutSeconds | number | 30 | 在认为已运行的 schedule 卡住之前的超时(秒) |
JavaScript
class SecureAgent extends Agent {
static options = { sendIdentityOnConnect: false };
}
TypeScript
class SecureAgent extends Agent {
static options = { sendIdentityOnConnect: false };
}
后续步骤
客户端 SDK 通过 useAgent 与 AgentClient 从浏览器连接。
跨域认证 WebSocket 认证模式。
可调用方法 通过 WebSocket 进行客户端 RPC。
配置 在 wrangler.jsonc 中设置 agent 绑定。