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

客户端 SDK

通过 WebSocket 或 HTTP,从任意 JavaScript 运行时(浏览器、Node.js、Deno、Bun 或边缘函数)连接到 Agent。SDK 提供实时状态同步、RPC 方法调用和流式响应。

概览

客户端 SDK 提供两种 WebSocket 连接方式,以及一种 HTTP 请求方式。

客户端适用场景
useAgentReact hook,自动重连和状态管理
AgentClient适用于任意环境的 vanilla JavaScript/TypeScript 类
agentFetch不需要 WebSocket 时的 HTTP 请求

所有客户端都提供:

  • 双向状态同步 — 实时推送和接收状态更新
  • RPC 调用 — 用类型化的参数和返回值调用 agent 方法
  • 流式响应 — 处理 AI 生成的分块响应
  • 自动重连 — 带指数退避的自动重连

快速开始

React

JavaScript


import { useAgent } from "agents/react";


function Chat() {

  const agent = useAgent({

    agent: "ChatAgent",

    name: "room-123",

    onStateUpdate: (state) => {

      console.log("New state:", state);

    },

  });


  const sendMessage = async () => {

    const response = await agent.call("sendMessage", ["Hello!"]);

    console.log("Response:", response);

  };


  return <button onClick={sendMessage}>Send</button>;

}


Explain Code

TypeScript


import { useAgent } from "agents/react";


function Chat() {

  const agent = useAgent({

    agent: "ChatAgent",

    name: "room-123",

    onStateUpdate: (state) => {

      console.log("New state:", state);

    },

  });


  const sendMessage = async () => {

    const response = await agent.call("sendMessage", ["Hello!"]);

    console.log("Response:", response);

  };


  return <button onClick={sendMessage}>Send</button>;

}


Explain Code

Vanilla JavaScript

JavaScript


import { AgentClient } from "agents/client";


const client = new AgentClient({

  agent: "ChatAgent",

  name: "room-123",

  host: "your-worker.your-subdomain.workers.dev",

  onStateUpdate: (state) => {

    console.log("New state:", state);

  },

});


// Call a method

const response = await client.call("sendMessage", ["Hello!"]);


Explain Code

TypeScript


import { AgentClient } from "agents/client";


const client = new AgentClient({

  agent: "ChatAgent",

  name: "room-123",

  host: "your-worker.your-subdomain.workers.dev",

  onStateUpdate: (state) => {

    console.log("New state:", state);

  },

});


// Call a method

const response = await client.call("sendMessage", ["Hello!"]);


Explain Code

连接到 agent

Agent 命名

agent 参数是你的 agent 类名。它会自动从 camelCase 转换为 kebab-case 用于 URL:

JavaScript


// These are equivalent:

useAgent({ agent: "ChatAgent" }); // → /agents/chat-agent/...

useAgent({ agent: "MyCustomAgent" }); // → /agents/my-custom-agent/...

useAgent({ agent: "LOUD_AGENT" }); // → /agents/loud-agent/...


TypeScript


// These are equivalent:

useAgent({ agent: "ChatAgent" }); // → /agents/chat-agent/...

useAgent({ agent: "MyCustomAgent" }); // → /agents/my-custom-agent/...

useAgent({ agent: "LOUD_AGENT" }); // → /agents/loud-agent/...


实例名称

name 参数标识具体的 agent 实例。如果省略,默认为 "default":

JavaScript


// Connect to a specific chat room

useAgent({ agent: "ChatAgent", name: "room-123" });


// Connect to a user's personal agent

useAgent({ agent: "UserAgent", name: userId });


// Uses "default" instance

useAgent({ agent: "ChatAgent" });


TypeScript


// Connect to a specific chat room

useAgent({ agent: "ChatAgent", name: "room-123" });


// Connect to a user's personal agent

useAgent({ agent: "UserAgent", name: userId });


// Uses "default" instance

useAgent({ agent: "ChatAgent" });


连接选项

useAgentAgentClient 都接受连接选项:

JavaScript


useAgent({

  agent: "ChatAgent",

  name: "room-123",


  // Connection settings

  host: "my-worker.workers.dev", // Custom host (defaults to current origin)

  path: "/custom/path", // Custom path prefix


  // Query parameters (sent on connection)

  query: {

    token: "abc123",

    version: "2",

  },


  // Event handlers

  onOpen: () => console.log("Connected"),

  onClose: () => console.log("Disconnected"),

  onError: (error) => console.error("Error:", error),

});


Explain Code

TypeScript


useAgent({

  agent: "ChatAgent",

  name: "room-123",


  // Connection settings

  host: "my-worker.workers.dev", // Custom host (defaults to current origin)

  path: "/custom/path", // Custom path prefix


  // Query parameters (sent on connection)

  query: {

    token: "abc123",

    version: "2",

  },


  // Event handlers

  onOpen: () => console.log("Connected"),

  onClose: () => console.log("Disconnected"),

  onError: (error) => console.error("Error:", error),

});


Explain Code

异步查询参数

对于身份验证 token 或其他异步数据,可以传入一个返回 Promise 的函数:

JavaScript


useAgent({

  agent: "ChatAgent",

  name: "room-123",


  // Async query - called before connecting

  query: async () => {

    const token = await getAuthToken();

    return { token };

  },


  // Dependencies that trigger re-fetching the query

  queryDeps: [userId],


  // Cache TTL for the query result (default: 5 minutes)

  cacheTtl: 60 * 1000, // 1 minute

});


Explain Code

TypeScript


useAgent({

  agent: "ChatAgent",

  name: "room-123",


  // Async query - called before connecting

  query: async () => {

    const token = await getAuthToken();

    return { token };

  },


  // Dependencies that trigger re-fetching the query

  queryDeps: [userId],


  // Cache TTL for the query result (default: 5 minutes)

  cacheTtl: 60 * 1000, // 1 minute

});


Explain Code

查询函数会被缓存,只有在以下情况才会重新调用:

  • queryDeps 发生变化
  • cacheTtl 过期
  • WebSocket 连接关闭(自动失效缓存)
  • 组件重新挂载

断开连接时自动失效缓存

当 WebSocket 连接关闭时(无论是网络问题、服务器重启还是显式断开),异步查询缓存都会自动失效。这确保了客户端重连时,查询函数会重新执行以获取新数据。这一点对于在断开期间可能已过期的身份验证 token 尤其重要。

状态同步

Agent 可以维护与所有连接客户端双向同步的状态。

读取当前状态

useAgentAgentClient 都暴露了一个 state 属性,反映 agent 当前的状态。在收到服务端发出的第一条状态消息之前,它的值为 undefined

JavaScript


const agent = useAgent({ agent: "GameAgent", name: "game-123" });


// Read the current state at any time

console.log("Current score:", agent.state?.score);


TypeScript


const agent = useAgent({ agent: "GameAgent", name: "game-123" });


// Read the current state at any time

console.log("Current score:", agent.state?.score);


使用 useAgent 时,状态更新会触发 React 重新渲染,所以 agent.state 在 JSX 中始终反映最新值。使用 AgentClient 时,state 字段会在每次收到服务端广播或调用 setState 时同步更新。

接收状态更新

JavaScript


const agent = useAgent({

  agent: "GameAgent",

  name: "game-123",

  onStateUpdate: (state, source) => {

    // state: The new state from the agent

    // source: "server" (agent pushed) or "client" (you pushed)

    console.log(`State updated from ${source}:`, state);

    setGameState(state);

  },

});


Explain Code

TypeScript


const agent = useAgent({

  agent: "GameAgent",

  name: "game-123",

  onStateUpdate: (state, source) => {

    // state: The new state from the agent

    // source: "server" (agent pushed) or "client" (you pushed)

    console.log(`State updated from ${source}:`, state);

    setGameState(state);

  },

});


Explain Code

推送状态更新

JavaScript


// Update the agent's state from the client

agent.setState({ score: 100, level: 5 });


TypeScript


// Update the agent's state from the client

agent.setState({ score: 100, level: 5 });


调用 setState() 时:

  1. 状态通过 WebSocket 发送到 agent
  2. agent 的 onStateChanged() 方法被调用
  3. agent 把新状态广播给所有连接的客户端
  4. 你的 onStateUpdate 回调被触发,source"client"

状态流

sequenceDiagram participant Client participant Agent Client->>Agent: setState() Agent–>>Client: onStateUpdate (broadcast)

调用 agent 方法(RPC)

调用 agent 上用 @callable() 装饰的方法。

注意

@callable() 装饰器只在从外部运行时(浏览器、其他服务)调用方法时才需要。当从同一个 Worker 内部调用时,可以直接在 stub 上使用标准的 Durable Object RPC,无需装饰器。

使用 call()

JavaScript


// Basic call

const result = await agent.call("getUser", [userId]);


// Call with multiple arguments

const result = await agent.call("createPost", [title, content, tags]);


// Call with no arguments

const result = await agent.call("getStats");


TypeScript


// Basic call

const result = await agent.call("getUser", [userId]);


// Call with multiple arguments

const result = await agent.call("createPost", [title, content, tags]);


// Call with no arguments

const result = await agent.call("getStats");


使用 stub 代理

stub 属性提供了更简洁的方法调用语法:

JavaScript


// Instead of:

const user = await agent.call("getUser", ["user-123"]);


// You can write:

const user = await agent.stub.getUser("user-123");


// Multiple arguments work naturally:

const post = await agent.stub.createPost(title, content, tags);


TypeScript


// Instead of:

const user = await agent.call("getUser", ["user-123"]);


// You can write:

const user = await agent.stub.getUser("user-123");


// Multiple arguments work naturally:

const post = await agent.stub.createPost(title, content, tags);


TypeScript 集成

为获得完整的类型安全,把你的 Agent 类作为类型参数传入:

JavaScript


const agent = useAgent({

  agent: "MyAgent",

  name: "instance-1",

});


// Now stub methods are fully typed

const result = await agent.stub.processData({ input: "test" });


TypeScript


import type { MyAgent } from "./agents/my-agent";


const agent = useAgent<MyAgent>({

  agent: "MyAgent",

  name: "instance-1",

});


// Now stub methods are fully typed

const result = await agent.stub.processData({ input: "test" });


流式响应

对于返回 StreamingResponse 的方法,处理到达的分块:

JavaScript


// Agent-side:

class MyAgent extends Agent {

  @callable({ streaming: true })

  async generateText(stream, prompt) {

    for await (const chunk of llm.stream(prompt)) {

      await stream.write(chunk);

    }

  }

}


// Client-side:

await agent.call("generateText", [prompt], {

  onChunk: (chunk) => {

    // Called for each chunk

    appendToOutput(chunk);

  },

  onDone: (finalResult) => {

    // Called when stream completes

    console.log("Complete:", finalResult);

  },

  onError: (error) => {

    // Called if streaming fails

    console.error("Stream error:", error);

  },

});


Explain Code

TypeScript


// Agent-side:

class MyAgent extends Agent {

  @callable({ streaming: true })

  async generateText(stream: StreamingResponse, prompt: string) {

    for await (const chunk of llm.stream(prompt)) {

      await stream.write(chunk);

    }

  }

}


// Client-side:

await agent.call("generateText", [prompt], {

  onChunk: (chunk) => {

    // Called for each chunk

    appendToOutput(chunk);

  },

  onDone: (finalResult) => {

    // Called when stream completes

    console.log("Complete:", finalResult);

  },

  onError: (error) => {

    // Called if streaming fails

    console.error("Stream error:", error);

  },

});


Explain Code

用 agentFetch 发起 HTTP 请求

适用于一次性请求,无需维护 WebSocket 连接的场景:

JavaScript


import { agentFetch } from "agents/client";


// GET request

const response = await agentFetch({

  agent: "DataAgent",

  name: "instance-1",

  host: "my-worker.workers.dev",

});


const data = await response.json();


// POST request with body

const response = await agentFetch(

  {

    agent: "DataAgent",

    name: "instance-1",

    host: "my-worker.workers.dev",

  },

  {

    method: "POST",

    headers: { "Content-Type": "application/json" },

    body: JSON.stringify({ action: "process" }),

  },

);


Explain Code

TypeScript


import { agentFetch } from "agents/client";


// GET request

const response = await agentFetch({

  agent: "DataAgent",

  name: "instance-1",

  host: "my-worker.workers.dev",

});


const data = await response.json();


// POST request with body

const response = await agentFetch(

  {

    agent: "DataAgent",

    name: "instance-1",

    host: "my-worker.workers.dev",

  },

  {

    method: "POST",

    headers: { "Content-Type": "application/json" },

    body: JSON.stringify({ action: "process" }),

  },

);


Explain Code

何时使用 agentFetch vs WebSocket:

使用 agentFetch使用 useAgent / AgentClient
一次性请求需要实时更新
服务端到服务端调用双向通信
简单的 REST 风格 API状态同步
不需要持久连接多次 RPC 调用

MCP 服务器集成

如果你的 agent 使用了 MCP (Model Context Protocol) 服务器,可以接收关于这些服务器状态的更新:

JavaScript


const agent = useAgent({

  agent: "AssistantAgent",

  name: "session-123",

  onMcpUpdate: (mcpServers) => {

    // mcpServers is a record of server states

    for (const [serverId, server] of Object.entries(mcpServers)) {

      console.log(`${serverId}: ${server.connectionState}`);

      console.log(`Tools: ${server.tools?.map((t) => t.name).join(", ")}`);

    }

  },

});


Explain Code

TypeScript


const agent = useAgent({

  agent: "AssistantAgent",

  name: "session-123",

  onMcpUpdate: (mcpServers) => {

    // mcpServers is a record of server states

    for (const [serverId, server] of Object.entries(mcpServers)) {

      console.log(`${serverId}: ${server.connectionState}`);

      console.log(`Tools: ${server.tools?.map((t) => t.name).join(", ")}`);

    }

  },

});


Explain Code

错误处理

连接错误

JavaScript


const agent = useAgent({

  agent: "MyAgent",

  onError: (error) => {

    console.error("WebSocket error:", error);

  },

  onClose: () => {

    console.log("Connection closed, will auto-reconnect...");

  },

});


TypeScript


const agent = useAgent({

  agent: "MyAgent",

  onError: (error) => {

    console.error("WebSocket error:", error);

  },

  onClose: () => {

    console.log("Connection closed, will auto-reconnect...");

  },

});


RPC 错误

JavaScript


try {

  const result = await agent.call("riskyMethod", [data]);

} catch (error) {

  // Error thrown by the agent method

  console.error("RPC failed:", error.message);

}


TypeScript


try {

  const result = await agent.call("riskyMethod", [data]);

} catch (error) {

  // Error thrown by the agent method

  console.error("RPC failed:", error.message);

}


流式错误

JavaScript


await agent.call("streamingMethod", [data], {

  onChunk: (chunk) => handleChunk(chunk),

  onError: (errorMessage) => {

    // Stream-specific error handling

    console.error("Stream error:", errorMessage);

  },

});


TypeScript


await agent.call("streamingMethod", [data], {

  onChunk: (chunk) => handleChunk(chunk),

  onError: (errorMessage) => {

    // Stream-specific error handling

    console.error("Stream error:", errorMessage);

  },

});


最佳实践

1. 使用类型化的 stub

JavaScript


// Prefer this:

const user = await agent.stub.getUser(id);


// Over this:

const user = await agent.call("getUser", [id]);


TypeScript


// Prefer this:

const user = await agent.stub.getUser(id);


// Over this:

const user = await agent.call("getUser", [id]);


2. 自动重连

客户端会自动重连,agent 也会在每次连接时自动发送当前状态。你的 onStateUpdate 回调会在最新状态到达时触发 — 不需要手动重新同步。如果使用异步 query 函数做身份验证,缓存会在断开连接时自动失效,确保重连时获取新的 token。

3. 优化 query 缓存

JavaScript


// For auth tokens that expire hourly:

useAgent({

  query: async () => ({ token: await getToken() }),

  cacheTtl: 55 * 60 * 1000, // Refresh 5 min before expiry

  queryDeps: [userId], // Refresh if user changes

});


TypeScript


// For auth tokens that expire hourly:

useAgent({

  query: async () => ({ token: await getToken() }),

  cacheTtl: 55 * 60 * 1000, // Refresh 5 min before expiry

  queryDeps: [userId], // Refresh if user changes

});


4. 清理连接

在 vanilla JS 中,使用完毕后关闭连接:

JavaScript


const client = new AgentClient({ agent: "MyAgent", host: "..." });


// When done:

client.close();


TypeScript


const client = new AgentClient({ agent: "MyAgent", host: "..." });


// When done:

client.close();


React 的 useAgent 会在组件卸载时自动处理清理。

React hook 参考

UseAgentOptions

TypeScript


type UseAgentOptions<State> = {

  // Required

  agent: string; // Agent class name


  // Optional

  name?: string; // Instance name (default: "default")

  host?: string; // Custom host

  path?: string; // Custom path prefix


  // Query parameters

  query?: Record<string, string> | (() => Promise<Record<string, string>>);

  queryDeps?: unknown[]; // Dependencies for async query

  cacheTtl?: number; // Query cache TTL in ms (default: 5 min)


  // Callbacks

  onStateUpdate?: (state: State, source: "server" | "client") => void;

  onMcpUpdate?: (mcpServers: MCPServersState) => void;

  onOpen?: () => void;

  onClose?: () => void;

  onError?: (error: Event) => void;

  onMessage?: (message: MessageEvent) => void;

};


Explain Code

返回值

useAgent hook 返回一个对象,包含以下属性和方法:

属性/方法类型描述
agentstringkebab-case 的 agent 名称
namestring实例名称
setState(state)void把状态推送给 agent
call(method, args?, options?)Promise调用 agent 方法
stubProxy类型化的方法调用
send(data)void发送原始 WebSocket 消息
close()void关闭连接
reconnect()void强制重连

Vanilla JS 参考

AgentClientOptions

TypeScript


type AgentClientOptions<State> = {

  // Required

  agent: string; // Agent class name

  host: string; // Worker host


  // Optional

  name?: string; // Instance name (default: "default")

  path?: string; // Custom path prefix

  query?: Record<string, string>;


  // Callbacks

  onStateUpdate?: (state: State, source: "server" | "client") => void;

};


Explain Code

AgentClient 方法

属性/方法类型描述
agentstringkebab-case 的 agent 名称
namestring实例名称
setState(state)void把状态推送给 agent
call(method, args?, options?)Promise调用 agent 方法
send(data)void发送原始 WebSocket 消息
close()void关闭连接
reconnect()void强制重连

客户端也支持 WebSocket 事件监听器:

JavaScript


client.addEventListener("open", () => {});

client.addEventListener("close", () => {});

client.addEventListener("error", () => {});

client.addEventListener("message", () => {});


TypeScript


client.addEventListener("open", () => {});

client.addEventListener("close", () => {});

client.addEventListener("error", () => {});

client.addEventListener("message", () => {});


下一步

路由 URL 模式和自定义路由选项。

可调用方法 通过 WebSocket 实现客户端到服务端的方法调用 RPC。

跨域身份验证 跨域保护 WebSocket 连接。

构建聊天 agent 完整的 AI 聊天客户端集成。