import { useAgent } from "agents/react"; import { useState } from "react"; import { Button, Input, Surface, Text } from "@cloudflare/kumo"; import { DemoWrapper } from "../../layout"; import { LogPanel, ConnectionStatus, CodeExplanation, HighlightedCode, type CodeSection } from "../../components"; import { useLogs, useUserId, useToast } from "../../hooks"; import type { CallableAgent } from "./callable-agent"; const codeSections: CodeSection[] = [ { title: "Expose methods with @callable", description: 'Decorate any method with @callable() to make it available as an RPC endpoint. The decorator accepts an optional description that shows up in listMethods(). Requires "target": "ES2021" or later in your tsconfig.json. Do not enable "experimentalDecorators" — the SDK uses TC39 standard decorators, not TypeScript legacy decorators.', code: `import { Agent, callable } from "agents"; // tsconfig.json needs: "target": "ES2021" (or later) // Do NOT set "experimentalDecorators": true class CallableAgent extends Agent { @callable({ description: "Add two numbers" }) add(a: number, b: number): number { return a + b; } @callable({ description: "Simulate an async operation" }) async slowOperation(delayMs: number): Promise { await new Promise(resolve => setTimeout(resolve, delayMs)); return \`Completed after \${delayMs}ms\`; } }` }, { title: "Call methods from the client", description: "Use agent.call() to invoke any @callable method. Arguments are passed as an array. Errors thrown on the server are re-thrown on the client.", code: `const agent = useAgent({ agent: "callable-agent", name: "my-instance", }); // Positional arguments passed as an array const sum = await agent.call("add", [5, 3]); // Async methods work the same way const msg = await agent.call("slowOperation", [1000]); // Errors propagate to the client try { await agent.call("throwError", ["oops"]); } catch (e) { console.error(e.message); // "oops" }` }, { title: "Self-describing APIs", description: "Agents can introspect their own callable methods at runtime using getCallableMethods(). This makes it easy to build dynamic UIs or tooling on top of agents.", code: ` @callable() listMethods() { return Array.from(this.getCallableMethods().entries()) .map(([name, meta]) => ({ name, description: meta.description, })); }` } ]; export function CallableDemo() { const userId = useUserId(); const { logs, addLog, clearLogs } = useLogs(); const { toast } = useToast(); const [methods, setMethods] = useState< Array<{ name: string; description?: string }> >([]); const [argA, setArgA] = useState("5"); const [argB, setArgB] = useState("3"); const [echoMessage, setEchoMessage] = useState("Hello, Agent!"); const [delayMs, setDelayMs] = useState("1000"); const [errorMessage, setErrorMessage] = useState("Test error"); const [lastResult, setLastResult] = useState(null); const agent = useAgent({ agent: "callable-agent", name: `callable-demo-${userId}`, onOpen: () => addLog("info", "connected"), onClose: () => addLog("info", "disconnected"), onError: () => addLog("error", "error", "Connection error") }); const handleCall = async (method: string, args: unknown[]) => { addLog("out", "call", { method, args }); setLastResult(null); try { const result = await ( agent.call as (m: string, a?: unknown[]) => Promise )(method, args); addLog("in", "result", result); setLastResult(JSON.stringify(result, null, 2)); toast(method + "() → " + JSON.stringify(result).slice(0, 60), "success"); return result; } catch (e) { const error = e instanceof Error ? e.message : String(e); addLog("error", "error", error); setLastResult(`Error: ${error}`); toast(error, "error"); throw e; } }; const handleListMethods = async () => { try { const result = (await handleCall("listMethods", [])) as Array<{ name: string; description?: string; }>; setMethods(result); } catch { // Error already logged } }; return ( Decorate any method with{" "} @callable() {" "} to expose it as an RPC endpoint. Clients call these methods using{" "} agent.call() {" "} over WebSocket — arguments are serialized, errors propagate, and async methods just work. Methods can optionally include a description for self-documenting APIs. } statusIndicator={ } >
{/* Controls */}
{/* Math Operations */}
Math Operations
) => setArgA(e.target.value) } className="w-20" /> ) => setArgB(e.target.value) } className="w-20" />
{/* Echo */}
Echo
) => setEchoMessage(e.target.value) } className="flex-1" />
{/* Async Operation */}
Async Operation
) => setDelayMs(e.target.value) } className="w-24" placeholder="ms" />

Simulates a slow operation with configurable delay

{/* Error Handling */}
Error Handling
) => setErrorMessage(e.target.value) } className="flex-1" />
{/* Utility */}
Utility Methods
{/* Available Methods */} {methods.length > 0 && (
Available Methods
{methods.map((m) => (
{m.name} {m.description && ( {m.description} )}
))}
)} {/* Last Result */} {lastResult && (
Last Result
)}
{/* Logs */}
); }