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.
Overview
The @callable decorator marks Agent methods as remotely callable from WebSocket clients. Clients can invoke these methods via RPC and receive typed responses.
import { Agent, callable } from "agents";
class MyAgent extends Agent<Env, State> {
@callable()
async greet(name: string) {
return `Hello, ${name}!`;
}
}
Usage
Basic Callable Method
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
@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:
@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
Optional description of what the method does
Whether the method supports streaming responses
Calling from Clients
React (useAgent)
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
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
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
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
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.
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.
Final value to send before closing
stream.end({ complete: true, totalChunks: 10 });
error()
Send an error and close the stream.
stream.error("Failed to generate response");
isClosed
Check if the stream has been closed.
if (!stream.isClosed) {
stream.send({ data: "more data" });
}
Type Safety
Callable methods are type-safe when using TypeScript:
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:
@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:
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:
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:
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:
@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:
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
// ✅ 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
// ✅ Good
@callable()
async addItemToCart(itemId: string, quantity: number) {
// ...
}
// ❌ Bad
@callable()
async doIt(id: string, n: number) {
// ...
}
Return Serializable Data
Only return JSON-serializable data:
// ✅ Good
@callable()
async getUser() {
return { id: "123", name: "Alice" };
}
// ❌ Bad - functions are not serializable
@callable()
async getBadData() {
return { callback: () => {} };
}