> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/cloudflare/agents/llms.txt
> Use this file to discover all available pages before exploring further.

# Vanilla JavaScript Client

> Connect to agents from any JavaScript environment with AgentClient and agentFetch

The `AgentClient` class and `agentFetch` function provide a framework-agnostic way to connect to agents from any JavaScript runtime: browsers, Node.js, Deno, Bun, or edge functions.

## Installation

```bash theme={null}
npm install agents partysocket
```

## AgentClient

The `AgentClient` class provides a WebSocket connection to an agent with state synchronization and RPC calls.

### Basic Usage

```typescript theme={null}
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!"]);

// Clean up when done
client.close();
```

### Constructor Options

<ParamField path="agent" type="string" required>
  Name of the agent class to connect to. Automatically converted from camelCase to kebab-case for the URL.
</ParamField>

<ParamField path="host" type="string" required>
  Worker host for the WebSocket connection.
</ParamField>

<ParamField path="name" type="string" default="default">
  Name of the specific agent instance.
</ParamField>

<ParamField path="path" type="string">
  Custom path prefix for the connection URL.
</ParamField>

<ParamField path="query" type="Record<string, string>">
  Query parameters to send with the connection.
</ParamField>

<ParamField path="onStateUpdate" type="(state: State, source: 'server' | 'client') => void">
  Callback invoked when the agent's state is updated.
</ParamField>

<ParamField path="onStateUpdateError" type="(error: string) => void">
  Callback invoked when a state update fails.
</ParamField>

<ParamField path="onIdentity" type="(name: string, agent: string) => void">
  Callback invoked when the server sends the agent's identity on connect.
</ParamField>

<ParamField path="onIdentityChange" type="(oldName: string, newName: string, oldAgent: string, newAgent: string) => void">
  Callback invoked when identity changes on reconnect.
</ParamField>

### Properties

<ResponseField name="agent" type="string">
  The kebab-case agent name.
</ResponseField>

<ResponseField name="name" type="string">
  The agent instance name. Updated when identity is received from the server.
</ResponseField>

<ResponseField name="identified" type="boolean">
  Whether the client has received identity from the server. Becomes `true` after the first identity message is received. Resets to `false` on connection close.
</ResponseField>

<ResponseField name="ready" type="Promise<void>">
  Promise that resolves when identity has been received from the server. Useful for waiting before making calls that depend on knowing the instance. Resets on connection close so it can be awaited again after reconnect.
</ResponseField>

### Methods

<ResponseField name="setState" type="(state: State) => void">
  Push state updates to the agent.

  ```typescript theme={null}
  client.setState({ count: 42 });
  ```
</ResponseField>

<ResponseField name="call" type="<T>(method: string, args?: unknown[], options?: CallOptions | StreamOptions) => Promise<T>">
  Call a method on the agent.

  ```typescript theme={null}
  const result = await client.call("getUser", ["user-123"]);
  ```
</ResponseField>

<ResponseField name="send" type="(data: string | ArrayBuffer | Blob) => void">
  Send raw WebSocket message.

  ```typescript theme={null}
  client.send(JSON.stringify({ type: "ping" }));
  ```
</ResponseField>

<ResponseField name="close" type="(code?: number, reason?: string) => void">
  Close the WebSocket connection. Immediately rejects all pending RPC calls.

  ```typescript theme={null}
  client.close();
  ```
</ResponseField>

<ResponseField name="reconnect" type="() => void">
  Force reconnection.

  ```typescript theme={null}
  client.reconnect();
  ```
</ResponseField>

<ResponseField name="addEventListener" type="(event: string, listener: EventListener) => void">
  Add event listeners for WebSocket events (inherited from PartySocket).

  ```typescript theme={null}
  client.addEventListener("open", () => console.log("Connected"));
  client.addEventListener("close", () => console.log("Disconnected"));
  client.addEventListener("error", (e) => console.error("Error:", e));
  client.addEventListener("message", (e) => console.log("Message:", e.data));
  ```
</ResponseField>

## Calling Methods

### Basic Calls

```typescript theme={null}
// Call with arguments
const user = await client.call("getUser", ["user-123"]);

// Call with multiple arguments
const post = await client.call("createPost", [title, content, tags]);

// Call with no arguments
const stats = await client.call("getStats");
```

### Call Options

The `call` method accepts an optional third parameter for configuring timeouts and streaming:

```typescript theme={null}
// With timeout
const result = await client.call("slowMethod", [data], {
  timeout: 10000 // 10 seconds
});

// With streaming
await client.call("generateText", [prompt], {
  stream: {
    onChunk: (chunk) => console.log("Chunk:", chunk),
    onDone: (final) => console.log("Done:", final),
    onError: (error) => console.error("Error:", error)
  }
});

// With both timeout and streaming
await client.call("generateText", [prompt], {
  timeout: 30000,
  stream: {
    onChunk: (chunk) => console.log("Chunk:", chunk),
    onDone: (final) => console.log("Done:", final),
    onError: (error) => console.error("Error:", error)
  }
});
```

<Note>
  For backward compatibility, the legacy format with streaming options directly in the third parameter is still supported: `client.call(method, args, { onChunk, onDone, onError })`.
</Note>

### Streaming Responses

Handle streaming responses with callbacks:

```typescript theme={null}
await client.call("generateText", [prompt], {
  stream: {
    onChunk: (chunk) => {
      process.stdout.write(chunk);
    },
    onDone: (finalResult) => {
      console.log("\nComplete!");
    },
    onError: (error) => {
      console.error("Stream error:", error);
    }
  }
});
```

## State Management

### Receiving State Updates

```typescript theme={null}
const client = new AgentClient({
  agent: "GameAgent",
  name: "game-123",
  host: "my-worker.workers.dev",
  onStateUpdate: (state, source) => {
    console.log(`State from ${source}:`, state);
    if (source === "server") {
      // Agent pushed state
      updateUI(state);
    } else {
      // We pushed state
      console.log("Our state was accepted");
    }
  }
});
```

### Pushing State Updates

```typescript theme={null}
client.setState({ score: 100, level: 5 });
```

<Info>
  When you call `setState()`, the agent broadcasts the new state to all connected clients. Your `onStateUpdate` callback will fire with `source: "client"`.
</Info>

## Connection Lifecycle

### Waiting for Identity

Use the `ready` promise to wait for the agent to send its identity:

```typescript theme={null}
const client = new AgentClient({
  agent: "MyAgent",
  name: "instance-1",
  host: "my-worker.workers.dev"
});

await client.ready;
console.log(`Connected to ${client.agent}/${client.name}`);
```

### Event Listeners

```typescript theme={null}
client.addEventListener("open", () => {
  console.log("Connection opened");
});

client.addEventListener("close", () => {
  console.log("Connection closed");
});

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

client.addEventListener("message", (event) => {
  console.log("Raw message:", event.data);
});
```

### Manual Reconnection

```typescript theme={null}
client.reconnect();
```

<Note>
  The client automatically reconnects on connection loss using PartySocket's reconnection logic.
</Note>

### Closing the Connection

```typescript theme={null}
// Close when done
client.close();

// Close with custom code and reason
client.close(1000, "Normal closure");
```

<Warning>
  Calling `close()` immediately rejects all pending RPC calls. Any calls made after `close()` will be rejected when the WebSocket close event fires.
</Warning>

## Type Safety

Pass your agent class and state type as type parameters:

```typescript theme={null}
import type { MyAgent, MyAgentState } from "./agents/my-agent";

const client = new AgentClient<MyAgentState>({
  agent: "MyAgent",
  name: "instance-1",
  host: "my-worker.workers.dev",
  onStateUpdate: (state) => {
    // state is typed as MyAgentState
    console.log(state.count);
  }
});
```

## HTTP Requests

For one-off requests without maintaining a WebSocket connection, use `agentFetch`:

### Basic Usage

```typescript theme={null}
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

```typescript theme={null}
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", data: "value" })
  }
);
```

### When to Use agentFetch

<CardGroup cols={2}>
  <Card title="Use agentFetch" icon="check">
    * One-time requests
    * Server-to-server calls
    * Simple REST-style API
    * No persistent connection needed
  </Card>

  <Card title="Use AgentClient" icon="plug">
    * Real-time updates needed
    * Bidirectional communication
    * State synchronization
    * Multiple RPC calls
  </Card>
</CardGroup>

## Examples

### Simple Counter Client

```typescript theme={null}
import { AgentClient } from "agents/client";

const client = new AgentClient({
  agent: "CounterAgent",
  name: "shared-counter",
  host: "my-worker.workers.dev",
  onStateUpdate: (state) => {
    console.log("Count:", state.count);
  }
});

// Wait for connection
await client.ready;

// Increment counter
await client.call("increment");

// Clean up
client.close();
```

### Streaming AI Client

```typescript theme={null}
import { AgentClient } from "agents/client";

const client = new AgentClient({
  agent: "AIAgent",
  name: "assistant",
  host: "my-worker.workers.dev"
});

await client.ready;

let fullResponse = "";

await client.call("generateResponse", ["Hello!"], {
  stream: {
    onChunk: (chunk) => {
      fullResponse += chunk;
      process.stdout.write(chunk);
    },
    onDone: () => {
      console.log("\n\nGeneration complete!");
      console.log("Full response:", fullResponse);
    },
    onError: (error) => {
      console.error("Error:", error);
    }
  }
});

client.close();
```

### Node.js Server-to-Agent Communication

```typescript theme={null}
import { AgentClient } from "agents/client";
import express from "express";

const app = express();
app.use(express.json());

app.post("/process", async (req, res) => {
  const client = new AgentClient({
    agent: "ProcessorAgent",
    name: req.body.agentId,
    host: "my-worker.workers.dev"
  });

  try {
    await client.ready;
    const result = await client.call("process", [req.body.data], {
      timeout: 5000
    });
    res.json({ success: true, result });
  } catch (error) {
    res.status(500).json({ success: false, error: error.message });
  } finally {
    client.close();
  }
});

app.listen(3000);
```

### Edge Function with agentFetch

```typescript theme={null}
import { agentFetch } from "agents/client";

export default async function handler(request: Request) {
  const { agentId } = await request.json();

  const response = await agentFetch(
    {
      agent: "DataAgent",
      name: agentId,
      host: "my-worker.workers.dev"
    },
    {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ action: "getData" })
    }
  );

  const data = await response.json();
  return new Response(JSON.stringify(data), {
    headers: { "Content-Type": "application/json" }
  });
}
```

## Error Handling

### RPC Errors

```typescript theme={null}
try {
  const result = await client.call("riskyMethod", [data]);
} catch (error) {
  console.error("RPC failed:", error.message);
}
```

### Connection Errors

```typescript theme={null}
client.addEventListener("error", (error) => {
  console.error("WebSocket error:", error);
});

client.addEventListener("close", (event) => {
  console.log(`Connection closed: ${event.code} ${event.reason}`);
});
```

### Streaming Errors

```typescript theme={null}
await client.call("streamingMethod", [data], {
  stream: {
    onChunk: (chunk) => handleChunk(chunk),
    onError: (error) => {
      console.error("Stream error:", error);
      // Handle stream-specific errors
    }
  }
});
```

### Timeout Errors

```typescript theme={null}
try {
  const result = await client.call("slowMethod", [data], {
    timeout: 5000
  });
} catch (error) {
  if (error.message.includes("timed out")) {
    console.error("Request timed out after 5 seconds");
  }
}
```

## Best Practices

<AccordionGroup>
  <Accordion title="Always close connections when done">
    In long-running processes, always call `close()` when you are done with the client to free up resources.

    ```typescript theme={null}
    const client = new AgentClient({ agent: "MyAgent", host: "..." });
    try {
      await client.call("doWork", [data]);
    } finally {
      client.close();
    }
    ```
  </Accordion>

  <Accordion title="Wait for ready before making calls">
    Use the `ready` promise to ensure the agent identity is received before making RPC calls.

    ```typescript theme={null}
    await client.ready;
    const result = await client.call("method", [args]);
    ```
  </Accordion>

  <Accordion title="Set appropriate timeouts for long operations">
    Use the `timeout` option for operations that may take longer than expected.

    ```typescript theme={null}
    await client.call("longRunningTask", [data], {
      timeout: 60000 // 60 seconds
    });
    ```
  </Accordion>

  <Accordion title="Use agentFetch for one-off requests">
    If you only need to make a single request, use `agentFetch` instead of creating a WebSocket connection.

    ```typescript theme={null}
    const response = await agentFetch({
      agent: "MyAgent",
      name: "instance",
      host: "my-worker.workers.dev"
    });
    ```
  </Accordion>
</AccordionGroup>

## See Also

<CardGroup cols={2}>
  <Card title="Client SDK Overview" icon="book" href="/client/client-sdk">
    Learn about the full client SDK capabilities
  </Card>

  <Card title="React Hooks" icon="react" href="/client/react-hooks">
    Use the useAgent hook in React applications
  </Card>
</CardGroup>
