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

路由

本指南介绍请求如何被路由到 agents、命名机制如何工作,以及组织 agents 的常见模式。

路由如何工作

当请求到达时,routeAgentRequest() 会检查 URL 并将其路由到合适的 agent 实例:


https://your-worker.dev/agents/{agent-name}/{instance-name}

                               └────┬────┘   └─────┬─────┘

                               Class name     Unique instance ID

                              (kebab-case)


示例 URL:

URLAgent 类实例
/agents/counter/user-123Counteruser-123
/agents/chat-room/lobbyChatRoomlobby
/agents/my-agent/defaultMyAgentdefault

名称解析

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 始终为 false
  • agent.ready 永远不会 resolve(请改用状态更新)
  • onIdentityonIdentityChange 永远不会被调用

何时使用自定义路由

场景方式
标准 agent 访问默认 /agents/{agent}/{name}
实例来自 auth/sessionbasePath + 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:wnamenamsamweureeurapacocafrme

数据辖区(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

关于 locationHintjurisdictionprops 等选项,详见 路由选项

子路径与 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() 提供了 onBeforeConnectonBeforeRequest 钩子用于认证:

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。请检查:

  1. Agent 类是否从入口文件导出。
  2. 代码中的类名是否与 wrangler.jsonc 中的 class_name 一致。
  3. URL 是否使用了正确的 kebab-case 名称。

请求返回 404

  1. 验证 URL 模式:/agents/{agent-name}/{instance-name}
  2. 检查 routeAgentRequest() 是否在 404 处理程序之前被调用。
  3. 确保 routeAgentRequest() 的返回值被返回(而不仅仅是被调用)。

WebSocket 连接失败

  1. 不要修改 routeAgentRequest() 返回的 WebSocket upgrade 响应。
  2. 如果跨域连接,请确保启用了 CORS。
  3. 在浏览器开发者工具中查看实际错误。

basePath 不生效

  1. 确保你的 Worker 处理了自定义路径并转发到了 agent。
  2. 使用 getAgentByName() + agent.fetch(request) 转发请求。
  3. 设置 basePath 时仍需提供 agent 参数,但它会被忽略。
  4. 确认服务端路由与客户端 basePath 一致。

API 参考

routeAgentRequest(request, env, options?)

将请求路由到合适的 agent。

参数类型说明
requestRequest进来的请求
envEnv包含 agent 绑定的环境
options.corsboolean | HeadersInit启用 CORS 头部
options.propsRecord<string, unknown>传递给处理请求的 agent 的 props
options.locationHintstringagent 实例的首选位置
options.jurisdictionstringagent 实例的数据辖区
options.onBeforeConnectFunctionWebSocket 连接前的回调
options.onBeforeRequestFunctionHTTP 请求前的回调

返回值: Promise<Response | undefined> —— 匹配时返回 Response,无 agent 路由时返回 undefined。

getAgentByName(namespace, name, options?)

按名称获取 agent 实例,用于服务端 RPC 调用或请求转发。

参数类型说明
namespaceDurableObjectNamespace<T>来自 env 的 agent 绑定
namestring实例名
options.locationHintstring首选位置
options.jurisdictionstring数据辖区
options.propsRecord<string, unknown>传给 onStart 的初始化属性

返回值: Promise<DurableObjectStub<T>> —— 用于调用 agent 方法或转发请求的类型化 stub。

useAgent(options) / AgentClient 选项

用于自定义路由的客户端连接选项:

选项类型说明
agentstringAgent 类名(必填)
namestring实例名(默认值:“default”)
basePathstring完整 URL 路径 —— 绕过 agent/name 的 URL 拼接
pathstring追加到 URL 末尾的额外路径
onIdentity(name, agent) => void服务器发送身份信息时调用
onIdentityChange(oldName, newName, oldAgent, newAgent) => void重连时身份变化时调用

返回值属性(React hook):

属性类型说明
namestring当前实例名(响应式)
agentstring当前 agent 类名(响应式)
identifiedboolean是否已收到身份信息(响应式)
readyPromise<void>收到身份信息时 resolve

Agent.options(服务端)

agent 配置的静态选项:

选项类型默认值说明
hibernatebooleantrueagent 空闲时是否进入 hibernate
sendIdentityOnConnectbooleantrue连接时是否向客户端发送身份信息
hungScheduleTimeoutSecondsnumber30在认为已运行的 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 绑定。