> ## 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.

# @callable Decorator

> Mark Agent methods as callable from WebSocket clients via RPC

## Overview

The `@callable` decorator marks Agent methods as remotely callable from WebSocket clients. Clients can invoke these methods via RPC and receive typed responses.

```typescript theme={null}
import { Agent, callable } from "agents";

class MyAgent extends Agent<Env, State> {
  @callable()
  async greet(name: string) {
    return `Hello, ${name}!`;
  }
}
```

## Usage

### Basic Callable Method

```typescript theme={null}
class CounterAgent extends Agent<Env, { count: number }> {
  @callable()
  async increment() {
    this.setState({ count: this.state.count + 1 });
    return this.state.count;
  }

  @callable()
  async add(amount: number) {
    this.setState({ count: this.state.count + amount });
    return this.state.count;
  }
}
```

### With Description

```typescript theme={null}
@callable({ description: "Increments the counter by 1" })
async increment() {
  this.setState({ count: this.state.count + 1 });
  return this.state.count;
}
```

### Streaming Methods

Mark methods as streaming to send multiple responses:

```typescript theme={null}
@callable({ streaming: true })
async *generateText(stream: StreamingResponse, prompt: string) {
  for (let i = 0; i < 10; i++) {
    stream.send({ token: `word-${i}`, index: i });
    await new Promise(resolve => setTimeout(resolve, 100));
  }
  stream.end({ complete: true });
}
```

## Decorator Options

<ParamField path="metadata" type="CallableMetadata">
  <ParamField path="description" type="string">
    Optional description of what the method does
  </ParamField>

  <ParamField path="streaming" type="boolean" default="false">
    Whether the method supports streaming responses
  </ParamField>
</ParamField>

## Calling from Clients

### React (useAgent)

```typescript theme={null}
import { useAgent } from "agents/react";

function Counter() {
  const agent = useAgent({
    agent: "CounterAgent",
    name: "default"
  });

  // Call via call() method
  const handleIncrement = async () => {
    const newCount = await agent.call("increment");
    console.log("New count:", newCount);
  };

  // Or use the stub (typed)
  const handleAdd = async () => {
    const newCount = await agent.stub.add(5);
    console.log("New count:", newCount);
  };

  return (
    <button onClick={handleIncrement}>Increment</button>
  );
}
```

### AgentClient

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

const client = new AgentClient({
  host: "localhost:1999",
  agent: "CounterAgent",
  name: "default"
});

// Call method
const result = await client.call("increment");
console.log("Result:", result);

// With arguments
const sum = await client.call("add", [5]);
```

## Streaming Responses

### Server Side

```typescript theme={null}
class ChatAgent extends Agent<Env, State> {
  @callable({ streaming: true })
  async chat(stream: StreamingResponse, message: string) {
    // Generate response in chunks
    const response = await generateStreamingResponse(message);

    for await (const chunk of response) {
      stream.send({ text: chunk });
    }

    stream.end({ done: true });
  }
}
```

### Client Side

```typescript theme={null}
const client = new AgentClient({
  agent: "ChatAgent",
  name: "default"
});

await client.call("chat", ["Hello!"], {
  stream: {
    onChunk: (chunk) => {
      console.log("Chunk:", chunk.text);
    },
    onDone: (final) => {
      console.log("Stream complete:", final);
    },
    onError: (error) => {
      console.error("Stream error:", error);
    }
  }
});
```

### React Streaming

```typescript theme={null}
function Chat() {
  const agent = useAgent({ agent: "ChatAgent", name: "default" });
  const [chunks, setChunks] = useState<string[]>([]);

  const handleSend = async () => {
    await agent.call("chat", ["Hello!"], {
      onChunk: (chunk) => {
        setChunks(prev => [...prev, chunk.text]);
      },
      onDone: () => {
        console.log("Complete!");
      }
    });
  };

  return (
    <div>
      <div>{chunks.join("")}</div>
      <button onClick={handleSend}>Send</button>
    </div>
  );
}
```

## StreamingResponse API

### send()

Send a chunk of data to the client.

<ParamField path="chunk" type="unknown" required>
  The data to send
</ParamField>

```typescript theme={null}
stream.send({ token: "hello", index: 0 });
```

**Returns:** `boolean` - `false` if stream is already closed (no-op), `true` if sent

### end()

End the stream and send a final value.

<ParamField path="finalValue" type="unknown">
  Final value to send before closing
</ParamField>

```typescript theme={null}
stream.end({ complete: true, totalChunks: 10 });
```

### error()

Send an error and close the stream.

<ParamField path="errorMessage" type="string" required>
  Error message
</ParamField>

```typescript theme={null}
stream.error("Failed to generate response");
```

### isClosed

Check if the stream has been closed.

```typescript theme={null}
if (!stream.isClosed) {
  stream.send({ data: "more data" });
}
```

## Type Safety

Callable methods are type-safe when using TypeScript:

```typescript theme={null}
type MyAgent = Agent<Env, State> & {
  greet(name: string): Promise<string>;
  add(a: number, b: number): Promise<number>;
};

const agent = useAgent<MyAgent>({
  agent: "MyAgent",
  name: "default"
});

// ✅ Type-safe
await agent.stub.greet("Alice");
await agent.stub.add(1, 2);

// ❌ Type error - wrong argument type
await agent.stub.greet(123);
```

## Error Handling

### Server Side

Throw errors in callable methods to send error responses:

```typescript theme={null}
@callable()
async divide(a: number, b: number) {
  if (b === 0) {
    throw new Error("Division by zero");
  }
  return a / b;
}
```

### Client Side

Handle errors in the promise rejection:

```typescript theme={null}
try {
  await agent.call("divide", [10, 0]);
} catch (error) {
  console.error("RPC error:", error.message); // "Division by zero"
}
```

## Timeout

Set a timeout for RPC calls:

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

## Security

### Method Visibility

Only methods marked with `@callable()` can be invoked from clients. Private methods are not accessible:

```typescript theme={null}
class SecureAgent extends Agent<Env, State> {
  @callable()
  async publicMethod() {
    return this.privateMethod();
  }

  // ❌ Not callable from clients
  private privateMethod() {
    return "secret data";
  }
}
```

### Validation

Validate arguments in callable methods:

```typescript theme={null}
@callable()
async updateUser(userId: string, name: string) {
  if (!userId || typeof userId !== "string") {
    throw new Error("Invalid userId");
  }
  if (name.length > 100) {
    throw new Error("Name too long");
  }
  // Safe to proceed
}
```

### Authentication

Use connection state for authentication:

```typescript theme={null}
async onConnect(connection: Connection, ctx: ConnectionContext) {
  const token = new URL(ctx.request.url).searchParams.get("token");
  const userId = await verifyToken(token);
  connection.setState({ userId, authenticated: true });
}

@callable()
async sensitiveOperation() {
  const { connection } = getCurrentAgent();
  const state = connection?.state as { authenticated?: boolean };

  if (!state?.authenticated) {
    throw new Error("Unauthorized");
  }

  // Safe to proceed
}
```

## Best Practices

### Keep Methods Small

```typescript theme={null}
// ✅ Good - focused, single-responsibility
@callable()
async increment() {
  this.setState({ count: this.state.count + 1 });
  return this.state.count;
}

// ❌ Bad - doing too much
@callable()
async doEverything(data: unknown) {
  // Complex logic, multiple responsibilities
}
```

### Use Descriptive Names

```typescript theme={null}
// ✅ Good
@callable()
async addItemToCart(itemId: string, quantity: number) {
  // ...
}

// ❌ Bad
@callable()
async doIt(id: string, n: number) {
  // ...
}
```

### Return Serializable Data

Only return JSON-serializable data:

```typescript theme={null}
// ✅ Good
@callable()
async getUser() {
  return { id: "123", name: "Alice" };
}

// ❌ Bad - functions are not serializable
@callable()
async getBadData() {
  return { callback: () => {} };
}
```

## Related

* [Agent Class](/api/agent-class) - Agent base class
* [useAgent Hook](/api/use-agent-hook) - React hook for RPC
* [AgentClient](/api/agent-client) - Client-side RPC
