Callable methods·续

Client-side error handling

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

Streaming error handling

For streaming methods, use the onError callback:

await agent.call("streamData", [input], {
    stream: {
        onChunk: (chunk) => handleChunk(chunk),
        onError: (errorMessage) => {
            console.error("Stream error:", errorMessage);
            showErrorUI(errorMessage);
        },
        onDone: (result) => handleComplete(result),
    },
});
await agent.call("streamData", [input], {
    stream: {
        onChunk: (chunk) => handleChunk(chunk),
        onError: (errorMessage) => {
            console.error("Stream error:", errorMessage);
            showErrorUI(errorMessage);
        },
        onDone: (result) => handleComplete(result),
    },
});

Server-side, you can use stream.error() to gracefully send an error mid-stream:

class MyAgent extends Agent {
    @callable({ streaming: true })
    async processItems(stream, items) {
        for (const item of items) {
            try {
                const result = await this.process(item);
                stream.send(result);
            } catch (e) {
                stream.error(`Failed to process ${item}: ${e.message}`);
                return; // Stream is now closed
            }
        }
        stream.end();
    }
}
class MyAgent extends Agent {
    @callable({ streaming: true })
    async processItems(stream: StreamingResponse, items: string[]) {
        for (const item of items) {
            try {
                const result = await this.process(item);
                stream.send(result);
            } catch (e) {
                stream.error(`Failed to process ${item}: ${e.message}`);
                return; // Stream is now closed
            }
        }
        stream.end();
    }
}

Connection errors

If the WebSocket connection closes while RPC calls are pending, they automatically reject with a "Connection closed" error:

try {
    const result = await agent.call("longRunningMethod", []);
} catch (error) {
    if (error.message === "Connection closed") {
        // Handle disconnection
        console.log("Lost connection to agent");
    }
}
try {
    const result = await agent.call("longRunningMethod", []);
} catch (error) {
    if (error.message === "Connection closed") {
        // Handle disconnection
        console.log("Lost connection to agent");
    }
}

Retrying after reconnection

The client automatically reconnects after disconnection. To retry a failed call after reconnection, await agent.ready before retrying:

async function callWithRetry(agent, method, args = []) {
    try {
        return await agent.call(method, args);
    } catch (error) {
        if (error.message === "Connection closed") {
            await agent.ready; // Wait for reconnection
            return await agent.call(method, args); // Retry once
        }
        throw error;
    }
}

// Usage
const result = await callWithRetry(agent, "processData", [data]);
async function callWithRetry<T>(
    agent: AgentClient,
    method: string,
    args: unknown[] = [],
): Promise<T> {
    try {
        return await agent.call(method, args);
    } catch (error) {
        if (error.message === "Connection closed") {
            await agent.ready; // Wait for reconnection
            return await agent.call(method, args); // Retry once
        }
        throw error;
    }
}

// Usage
const result = await callWithRetry(agent, "processData", [data]);

Note

Only retry idempotent operations. If the server received the request but the connection dropped before the response arrived, retrying could cause duplicate execution.

When NOT to use @callable

Worker-to-Agent calls

When calling an agent from the same Worker (for example, in your fetch handler), use Durable Object RPC directly:

import { getAgentByName } from "agents";

export default {
    async fetch(request, env) {
        // Get the agent stub
        const agent = await getAgentByName(env.MyAgent, "instance-name");

// Call methods directly - no @callable needed
        const result = await agent.processData(data);

return Response.json(result);
    },
};
import { getAgentByName } from "agents";

export default {
    async fetch(request: Request, env: Env) {
        // Get the agent stub
        const agent = await getAgentByName(env.MyAgent, "instance-name");

// Call methods directly - no @callable needed
        const result = await agent.processData(data);

return Response.json(result);
    },
} satisfies ExportedHandler<Env>;

Agent-to-Agent calls

When one agent needs to call another:

class OrchestratorAgent extends Agent {
    async delegateWork(taskId) {
        // Get another agent
        const worker = await getAgentByName(this.env.WorkerAgent, taskId);

// Call its methods directly
        const result = await worker.doWork();

return result;
    }
}
class OrchestratorAgent extends Agent {
    async delegateWork(taskId: string) {
        // Get another agent
        const worker = await getAgentByName(this.env.WorkerAgent, taskId);

// Call its methods directly
        const result = await worker.doWork();

return result;
    }
}

Why the distinction?

RPC Type Transport Use Case
@callable WebSocket External clients (browsers, apps)
Durable Object RPC Internal Worker to Agent, Agent to Agent

Durable Object RPC is more efficient for internal calls since it does not go through WebSocket serialization. The @callable decorator adds the necessary WebSocket RPC handling for external clients.

API reference

@callable(metadata?) decorator

Marks a method as callable from external clients.

import { callable } from "agents";

class MyAgent extends Agent {
    @callable()
    method() {}

@callable({ streaming: true })
    streamingMethod(stream) {}

@callable({ description: "Fetches user data" })
    getUser(id) {}
}
import { callable } from "agents";

class MyAgent extends Agent {
    @callable()
    method(): void {}

@callable({ streaming: true })
    streamingMethod(stream: StreamingResponse): void {}

@callable({ description: "Fetches user data" })
    getUser(id: string): User {}
}

CallableMetadata type

type CallableMetadata = {
    /** Optional description of what the method does */
    description?: string;
    /** Whether the method supports streaming responses */
    streaming?: boolean;
};

StreamingResponse class

Used in streaming callable methods to send data to the client.

import {} from "agents";

class MyAgent extends Agent {
    @callable({ streaming: true })
    async streamData(stream, input) {
        stream.send("chunk 1");
        stream.send("chunk 2");
        stream.end("final");
    }
}
import { type StreamingResponse } from "agents";

class MyAgent extends Agent {
    @callable({ streaming: true })
    async streamData(stream: StreamingResponse, input: string) {
        stream.send("chunk 1");
        stream.send("chunk 2");
        stream.end("final");
    }
}
Method Signature Description
send (chunk: unknown) => void Send a chunk to the client
end (finalChunk?: unknown) => void End the stream
error (message: string) => void Send an error and close the stream

Client methods

Method Signature Description
agent.call (method, args?, options?) => Promise Call a method by name
agent.stub Proxy Typed method calls
// Using call()
await agent.call("methodName", [arg1, arg2]);
await agent.call("streamMethod", [arg], {
    stream: { onChunk, onDone, onError },
});

// With timeout (rejects if call does not complete in time)
await agent.call("slowMethod", [], { timeout: 5000 });

// Using stub
await agent.stub.methodName(arg1, arg2);
// Using call()
await agent.call("methodName", [arg1, arg2]);
await agent.call("streamMethod", [arg], {
    stream: { onChunk, onDone, onError },
});

// With timeout (rejects if call does not complete in time)
await agent.call("slowMethod", [], { timeout: 5000 });

// Using stub
await agent.stub.methodName(arg1, arg2);

CallOptions type

type CallOptions = {
    /** Timeout in milliseconds. Rejects if call does not complete in time. */
    timeout?: number;
    /** Streaming options */
    stream?: {
        onChunk?: (chunk: unknown) => void;
        onDone?: (finalChunk: unknown) => void;
        onError?: (error: string) => void;
    };
};

Note

The legacy format { onChunk, onDone, onError } (without nesting under stream) is still supported. The client automatically detects which format you are using.

getCallableMethods() method

Returns a map of all callable methods on the agent with their metadata. Useful for introspection and automatic documentation.

const methods = agent.getCallableMethods();
// Map<string, CallableMetadata>

for (const [name, meta] of methods) {
    console.log(`${name}: ${meta.description || "(no description)"}`);
    if (meta.streaming) console.log("  (streaming)");
}
const methods = agent.getCallableMethods();
// Map<string, CallableMetadata>

for (const [name, meta] of methods) {
    console.log(`${name}: ${meta.description || "(no description)"}`);
    if (meta.streaming) console.log("  (streaming)");
}

Troubleshooting

SyntaxError: Invalid or unexpected token

If your dev server fails with SyntaxError: Invalid or unexpected token when using @callable(), you need two things:

1. Add the agents/vite plugin — Vite 8 uses Oxc for transpilation, which does not yet support TC39 decorators. The plugin adds the required transform:

vite.config.tsts

import agents from "agents/vite";

export default defineConfig({
    plugins: [agents(), react(), cloudflare()],
});

2. Extend agents/tsconfig — this sets "target": "ES2021" and all other recommended compiler options:

tsconfig.jsonjson

{
    "extends": "agents/tsconfig"
}

If you cannot extend the shared config, set "target": "ES2021" manually in your tsconfig.json.

Caution

Do not set "experimentalDecorators": true in your tsconfig.json. The Agents SDK uses TC39 standard decorators ↗︎, not TypeScript legacy decorators. Enabling experimentalDecorators applies an incompatible transform that silently breaks @callable() at runtime.

Next steps

Agents API

Complete API reference for the Agents SDK.

WebSockets

Real-time bidirectional communication with clients.

State management

Sync state between agents and clients.

Was this helpful?

YesNo

On this page

Callable methods·续 - 图1Docs

{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/agents/runtime/lifecycle/callable-methods/#page","headline":"Callable methods","description":"Expose Agent methods to external clients over WebSocket RPC using the @callable() decorator.","url":"https://developers.cloudflare.com/agents/runtime/lifecycle/callable-methods/","inLanguage":"en","image":"https://developers.cloudflare.com/agents/runtime/lifecycle/callable-methods/og.png?v=30f5b86891924c7a","dateModified":"2026-06-03","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}


description: Access the current Agent context from external utility functions using getCurrentAgent() in the Agents SDK. title: getCurrentAgent() image: https://developers.cloudflare.com/agents/runtime/lifecycle/get-current-agent/og.png?v=326b34596aad5414


Skip to content

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