客户端 SDK
通过 WebSocket 或 HTTP,从任意 JavaScript 运行时(浏览器、Node.js、Deno、Bun 或边缘函数)连接到 Agent。SDK 提供实时状态同步、RPC 方法调用和流式响应。
概览
客户端 SDK 提供两种 WebSocket 连接方式,以及一种 HTTP 请求方式。
| 客户端 | 适用场景 |
|---|---|
| useAgent | React 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" });
连接选项
useAgent 和 AgentClient 都接受连接选项:
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 可以维护与所有连接客户端双向同步的状态。
读取当前状态
useAgent 和 AgentClient 都暴露了一个 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() 时:
- 状态通过 WebSocket 发送到 agent
- agent 的
onStateChanged()方法被调用 - agent 把新状态广播给所有连接的客户端
- 你的
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 返回一个对象,包含以下属性和方法:
| 属性/方法 | 类型 | 描述 |
|---|---|---|
| agent | string | kebab-case 的 agent 名称 |
| name | string | 实例名称 |
| setState(state) | void | 把状态推送给 agent |
| call(method, args?, options?) | Promise | 调用 agent 方法 |
| stub | Proxy | 类型化的方法调用 |
| 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 方法
| 属性/方法 | 类型 | 描述 |
|---|---|---|
| agent | string | kebab-case 的 agent 名称 |
| name | string | 实例名称 |
| 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 聊天客户端集成。