Skip to content
Cloudflare Docs

Client SDK

Connect to agents from any JavaScript runtime — browsers, Node.js, Deno, Bun, or edge functions — using WebSockets or HTTP. The SDK provides real-time state synchronization, RPC method calls, and streaming responses.

Overview

The client SDK offers two ways to connect with a WebSocket connection, and one way to make HTTP requests.

ClientUse Case
useAgentReact hook with automatic reconnection and state management
AgentClientVanilla JavaScript/TypeScript class for any environment
agentFetchHTTP requests when WebSocket is not needed

All clients provide:

  • Bidirectional state sync - Push and receive state updates in real-time
  • RPC calls - Call agent methods with typed arguments and return values
  • Streaming - Handle chunked responses for AI completions
  • Auto-reconnection - Automatic reconnection with exponential backoff

Quick start

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

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!"]);

Connecting to agents

Agent naming

The agent parameter is your agent class name. It is automatically converted from camelCase to kebab-case for the 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/...

Instance names

The name parameter identifies a specific agent instance. If omitted, defaults to "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" });

Connection options

Both useAgent and AgentClient accept connection options:

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),
});

Async query parameters

For authentication tokens or other async data, pass a function that returns a 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
});

The query function is cached and only re-called when:

  • queryDeps change
  • cacheTtl expires
  • The component remounts

State synchronization

Agents can maintain state that syncs bidirectionally with all connected clients.

Receiving state updates

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);
},
});

Pushing state updates

JavaScript
// Update the agent's state from the client
agent.setState({ score: 100, level: 5 });

When you call setState():

  1. The state is sent to the agent over WebSocket
  2. The agent's onStateUpdate() method is called
  3. The agent broadcasts the new state to all connected clients
  4. Your onStateUpdate callback fires with source: "client"

State flow

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

Calling agent methods (RPC)

Call methods on your agent that are decorated with @callable().

Using 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");

Using the stub proxy

The stub property provides a cleaner syntax for method calls:

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 integration

For full type safety, pass your Agent class as a type parameter:

JavaScript
const agent = useAgent({
agent: "MyAgent",
name: "instance-1",
});
// Now stub methods are fully typed
const result = await agent.stub.processData({ input: "test" });

Streaming responses

For methods that return StreamingResponse, handle chunks as they arrive:

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);
},
});

HTTP requests with agentFetch

For one-off requests without maintaining a WebSocket connection:

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" }),
},
);

When to use agentFetch vs WebSocket:

Use agentFetchUse useAgent/AgentClient
One-time requestsReal-time updates needed
Server-to-server callsBidirectional communication
Simple REST-style APIState synchronization
No persistent connection neededMultiple RPC calls

MCP server integration

If your agent uses MCP (Model Context Protocol) servers, you can receive updates about their state:

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(", ")}`);
}
},
});

Error handling

Connection errors

JavaScript
const agent = useAgent({
agent: "MyAgent",
onError: (error) => {
console.error("WebSocket error:", error);
},
onClose: () => {
console.log("Connection closed, will auto-reconnect...");
},
});

RPC errors

JavaScript
try {
const result = await agent.call("riskyMethod", [data]);
} catch (error) {
// Error thrown by the agent method
console.error("RPC failed:", error.message);
}

Streaming errors

JavaScript
await agent.call("streamingMethod", [data], {
onChunk: (chunk) => handleChunk(chunk),
onError: (errorMessage) => {
// Stream-specific error handling
console.error("Stream error:", errorMessage);
},
});

Best practices

1. Use typed stubs

JavaScript
// Prefer this:
const user = await agent.stub.getUser(id);
// Over this:
const user = await agent.call("getUser", [id]);

2. Reconnection is automatic

The client auto-reconnects and the agent automatically sends the current state on each connection. Your onStateUpdate callback will fire with the latest state — no manual re-sync is needed.

3. Optimize query caching

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

4. Clean up connections

In vanilla JS, close connections when done:

JavaScript
const client = new AgentClient({ agent: "MyAgent", host: "..." });
// When done:
client.close();

React's useAgent handles cleanup automatically on unmount.

React hook reference

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

Return value

The useAgent hook returns an object with the following properties and methods:

Property/MethodTypeDescription
agentstringKebab-case agent name
namestringInstance name
setState(state)voidPush state to agent
call(method, args?, options?)PromiseCall agent method
stubProxyTyped method calls
send(data)voidSend raw WebSocket message
close()voidClose connection
reconnect()voidForce reconnection

Vanilla JS reference

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

AgentClient methods

Property/MethodTypeDescription
agentstringKebab-case agent name
namestringInstance name
setState(state)voidPush state to agent
call(method, args?, options?)PromiseCall agent method
send(data)voidSend raw WebSocket message
close()voidClose connection
reconnect()voidForce reconnection

The client also supports WebSocket event listeners:

JavaScript
client.addEventListener("open", () => {});
client.addEventListener("close", () => {});
client.addEventListener("error", () => {});
client.addEventListener("message", () => {});

Next steps