agents 0.16.2 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -8
- package/dist/{agent-tool-types-CTw3UJUP.d.ts → agent-tool-types-Cd1TZPfB.d.ts} +587 -137
- package/dist/agent-tool-types.d.ts +34 -18
- package/dist/agent-tool-types.js +20 -1
- package/dist/agent-tool-types.js.map +1 -0
- package/dist/agent-tools-BXlsuX0d.js +304 -0
- package/dist/agent-tools-BXlsuX0d.js.map +1 -0
- package/dist/agent-tools-_E8wxUIK.d.ts +119 -0
- package/dist/agent-tools.d.ts +24 -18
- package/dist/agent-tools.js.map +1 -1
- package/dist/ai-chat-agent.d.ts +1 -1
- package/dist/ai-chat-agent.js +1 -2
- package/dist/ai-chat-agent.js.map +1 -1
- package/dist/ai-chat-v5-migration.d.ts +1 -1
- package/dist/ai-chat-v5-migration.js +1 -2
- package/dist/ai-chat-v5-migration.js.map +1 -1
- package/dist/ai-react.d.ts +1 -1
- package/dist/ai-react.js +1 -2
- package/dist/ai-react.js.map +1 -1
- package/dist/ai-types.d.ts +1 -7
- package/dist/ai-types.js +2 -8
- package/dist/ai-types.js.map +1 -1
- package/dist/browser/ai.js +1 -1
- package/dist/browser/index.js +1 -1
- package/dist/chat/index.d.ts +1918 -72
- package/dist/chat/index.js +1730 -245
- package/dist/chat/index.js.map +1 -1
- package/dist/chat/react.d.ts +602 -0
- package/dist/chat/react.js +1506 -0
- package/dist/chat/react.js.map +1 -0
- package/dist/chat-sdk/index.d.ts +4 -4
- package/dist/{classPrivateFieldGet2-CZ7QjTXN.js → classPrivateFieldGet2-DZBYAB34.js} +5 -5
- package/dist/{classPrivateMethodInitSpec-D-0__zd9.js → classPrivateMethodInitSpec-qMjJ6sHQ.js} +2 -2
- package/dist/{client-BXJ9n2f7.js → client-BZ-B3NhC.js} +2 -2
- package/dist/client-BZ-B3NhC.js.map +1 -0
- package/dist/client-tools-aIBO0Fk7.d.ts +53 -0
- package/dist/client.d.ts +76 -57
- package/dist/client.js +33 -5
- package/dist/client.js.map +1 -1
- package/dist/{connector-CrKhowfD.js → connector-CdldGF3h.js} +5 -5
- package/dist/{connector-CrKhowfD.js.map → connector-CdldGF3h.js.map} +1 -1
- package/dist/email.js +1 -1
- package/dist/email.js.map +1 -1
- package/dist/{index-B7IbEeze.d.ts → index-CcbnKkNh.d.ts} +188 -14
- package/dist/index.d.ts +91 -71
- package/dist/index.js +562 -24
- package/dist/index.js.map +1 -1
- package/dist/mcp/client.d.ts +18 -14
- package/dist/mcp/client.js +1 -1
- package/dist/mcp/index.d.ts +30 -30
- package/dist/mcp/index.js +7 -7
- package/dist/mcp/index.js.map +1 -1
- package/dist/{agent-tools-3zLG7MgA.js → message-builder-BymO4N_D.js} +45 -126
- package/dist/message-builder-BymO4N_D.js.map +1 -0
- package/dist/observability/index.d.ts +1 -1
- package/dist/observability/index.js +4 -2
- package/dist/observability/index.js.map +1 -1
- package/dist/react.d.ts +123 -110
- package/dist/react.js +44 -11
- package/dist/react.js.map +1 -1
- package/dist/serializable.d.ts +1 -1
- package/dist/skills/compile.js +1 -1
- package/dist/skills/compile.js.map +1 -1
- package/dist/skills/index.js +5 -5
- package/dist/skills/index.js.map +1 -1
- package/dist/sub-routing.d.ts +6 -6
- package/dist/utils.js +1 -1
- package/dist/utils.js.map +1 -1
- package/dist/vite.js +5 -5
- package/dist/vite.js.map +1 -1
- package/dist/wire-types-nflOzNuU.js +240 -0
- package/dist/wire-types-nflOzNuU.js.map +1 -0
- package/dist/{workflow-types-SrZK_o9p.d.ts → workflow-types-Baz_PO5v.d.ts} +42 -22
- package/dist/workflow-types.d.ts +25 -21
- package/dist/workflow-types.js.map +1 -1
- package/dist/workflows.d.ts +22 -21
- package/dist/workflows.js +31 -7
- package/dist/workflows.js.map +1 -1
- package/docs/adding-to-existing-project.md +450 -0
- package/docs/agent-class.md +503 -0
- package/docs/agent-tools.md +552 -0
- package/docs/browse-the-web.md +430 -0
- package/docs/callable-methods.md +627 -0
- package/docs/chat-agents.md +1687 -0
- package/docs/chat-sdk.md +181 -0
- package/docs/client-sdk.md +520 -0
- package/docs/client-tools-continuation.md +177 -0
- package/docs/codemode.md +440 -0
- package/docs/configuration.md +775 -0
- package/docs/cross-domain-authentication.md +171 -0
- package/docs/durable-execution.md +537 -0
- package/docs/email.md +663 -0
- package/docs/get-current-agent.md +204 -0
- package/docs/getting-started.md +305 -0
- package/docs/http-websockets.md +668 -0
- package/docs/human-in-the-loop.md +661 -0
- package/docs/index.md +151 -0
- package/docs/long-running-agents.md +730 -0
- package/docs/mcp-client.md +620 -0
- package/docs/mcp-servers.md +526 -0
- package/docs/mcp-transports.md +308 -0
- package/docs/migration-to-ai-sdk-v5.md +96 -0
- package/docs/migration-to-ai-sdk-v6.md +163 -0
- package/docs/observability.md +261 -0
- package/docs/push-notifications.md +367 -0
- package/docs/queue.md +329 -0
- package/docs/readonly-connections.md +278 -0
- package/docs/resumable-streaming.md +127 -0
- package/docs/retries.md +444 -0
- package/docs/routing.md +749 -0
- package/docs/scheduling.md +898 -0
- package/docs/securing-mcp-servers.md +359 -0
- package/docs/server-driven-messages.md +477 -0
- package/docs/sessions.md +1024 -0
- package/docs/state.md +512 -0
- package/docs/sub-agents.md +389 -0
- package/docs/webhooks.md +604 -0
- package/docs/workflows.md +877 -0
- package/package.json +41 -14
- package/dist/agent-tools-3zLG7MgA.js.map +0 -1
- package/dist/agent-tools-DZhI5F6Q.d.ts +0 -14
- package/dist/client-BXJ9n2f7.js.map +0 -1
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
# Callable Methods
|
|
2
|
+
|
|
3
|
+
Callable methods let clients invoke agent methods over WebSocket using RPC (Remote Procedure Call). Mark methods with `@callable()` to expose them to external clients like browsers, mobile apps, or other services.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { Agent, callable } from "agents";
|
|
9
|
+
|
|
10
|
+
export class MyAgent extends Agent {
|
|
11
|
+
@callable()
|
|
12
|
+
async greet(name: string): Promise<string> {
|
|
13
|
+
return `Hello, ${name}!`;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
// Client
|
|
20
|
+
const result = await agent.stub.greet("World");
|
|
21
|
+
console.log(result); // "Hello, World!"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### How It Works
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
┌─────────┐ ┌─────────┐
|
|
28
|
+
│ Client │ │ Agent │
|
|
29
|
+
└────┬────┘ └────┬────┘
|
|
30
|
+
│ │
|
|
31
|
+
│ agent.stub.greet("World") │
|
|
32
|
+
│ ──────────────────────────────────▶ │
|
|
33
|
+
│ WebSocket RPC message │
|
|
34
|
+
│ │
|
|
35
|
+
│ Check @callable
|
|
36
|
+
│ Execute method
|
|
37
|
+
│ │
|
|
38
|
+
│ ◀──────────────────────────────── │
|
|
39
|
+
│ "Hello, World!" │
|
|
40
|
+
│ │
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### When to Use @callable
|
|
44
|
+
|
|
45
|
+
| Scenario | Use |
|
|
46
|
+
| ------------------------------------ | ----------------------------- |
|
|
47
|
+
| Browser/mobile calling agent | `@callable()` |
|
|
48
|
+
| External service calling agent | `@callable()` |
|
|
49
|
+
| Worker calling agent (same codebase) | DO RPC (no decorator needed) |
|
|
50
|
+
| Agent calling another agent | DO RPC via `getAgentByName()` |
|
|
51
|
+
|
|
52
|
+
The `@callable()` decorator is specifically for WebSocket-based RPC from external clients. When calling from within the same Worker or another agent, use standard [Durable Object RPC](https://developers.cloudflare.com/durable-objects/best-practices/create-durable-object-stubs-and-send-requests/) directly.
|
|
53
|
+
|
|
54
|
+
## TypeScript and Vite Configuration
|
|
55
|
+
|
|
56
|
+
The `@callable()` decorator uses TC39 standard decorators, which require two build-time configurations:
|
|
57
|
+
|
|
58
|
+
**1. Add the `agents/vite` plugin** — Vite 8 uses Oxc for transpilation, which does not yet support TC39 decorators ([oxc#9170](https://github.com/oxc-project/oxc/issues/9170)). The plugin adds the required Babel transform:
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
// vite.config.ts
|
|
62
|
+
import agents from "agents/vite";
|
|
63
|
+
|
|
64
|
+
export default defineConfig({
|
|
65
|
+
plugins: [agents(), react(), cloudflare()]
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The plugin only runs the transform on files containing `@` syntax. It is safe to include even if you do not use decorators.
|
|
70
|
+
|
|
71
|
+
**2. Extend `agents/tsconfig`** — this sets `target: "ES2021"` and all other recommended compiler options:
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{
|
|
75
|
+
"extends": "agents/tsconfig"
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
If you cannot extend the shared config, set `"target": "ES2021"` manually in your `tsconfig.json`.
|
|
80
|
+
|
|
81
|
+
Without both of these, your dev server will fail with `SyntaxError: Invalid or unexpected token`.
|
|
82
|
+
|
|
83
|
+
> **Warning:** Do not set `"experimentalDecorators": true` in your `tsconfig.json`. The Agents SDK uses [TC39 standard decorators](https://github.com/tc39/proposal-decorators), not TypeScript legacy decorators. Enabling `experimentalDecorators` applies an incompatible transform that silently breaks `@callable()` at runtime.
|
|
84
|
+
|
|
85
|
+
## Basic Usage
|
|
86
|
+
|
|
87
|
+
### Defining Callable Methods
|
|
88
|
+
|
|
89
|
+
Add the `@callable()` decorator to any method you want to expose:
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { Agent, callable } from "agents";
|
|
93
|
+
|
|
94
|
+
type State = {
|
|
95
|
+
count: number;
|
|
96
|
+
items: string[];
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export class CounterAgent extends Agent<Env, State> {
|
|
100
|
+
initialState: State = { count: 0, items: [] };
|
|
101
|
+
|
|
102
|
+
@callable()
|
|
103
|
+
increment(): number {
|
|
104
|
+
this.setState({ ...this.state, count: this.state.count + 1 });
|
|
105
|
+
return this.state.count;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@callable()
|
|
109
|
+
decrement(): number {
|
|
110
|
+
this.setState({ ...this.state, count: this.state.count - 1 });
|
|
111
|
+
return this.state.count;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
@callable()
|
|
115
|
+
async addItem(item: string): Promise<string[]> {
|
|
116
|
+
this.setState({ ...this.state, items: [...this.state.items, item] });
|
|
117
|
+
return this.state.items;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@callable()
|
|
121
|
+
getStats(): { count: number; itemCount: number } {
|
|
122
|
+
return {
|
|
123
|
+
count: this.state.count,
|
|
124
|
+
itemCount: this.state.items.length
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Calling from the Client
|
|
131
|
+
|
|
132
|
+
There are two ways to call methods from the client:
|
|
133
|
+
|
|
134
|
+
**Using `agent.stub` (recommended):**
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
// Clean, typed syntax
|
|
138
|
+
const count = await agent.stub.increment();
|
|
139
|
+
const items = await agent.stub.addItem("new item");
|
|
140
|
+
const stats = await agent.stub.getStats();
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Using `agent.call()`:**
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
// Explicit method name as string
|
|
147
|
+
const count = await agent.call("increment");
|
|
148
|
+
const items = await agent.call("addItem", ["new item"]);
|
|
149
|
+
const stats = await agent.call("getStats");
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The `stub` proxy provides better ergonomics and TypeScript support.
|
|
153
|
+
|
|
154
|
+
## Method Signatures
|
|
155
|
+
|
|
156
|
+
### Serializable Types
|
|
157
|
+
|
|
158
|
+
Arguments and return values must be JSON-serializable:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
// ✅ Valid - primitives and plain objects
|
|
162
|
+
@callable()
|
|
163
|
+
processData(input: { name: string; count: number }): { result: boolean } {
|
|
164
|
+
return { result: true };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ✅ Valid - arrays
|
|
168
|
+
@callable()
|
|
169
|
+
processItems(items: string[]): number[] {
|
|
170
|
+
return items.map(item => item.length);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ❌ Invalid - non-serializable types
|
|
174
|
+
@callable()
|
|
175
|
+
badMethod(fn: Function, date: Date): Map<string, unknown> {
|
|
176
|
+
// Functions, Dates, Maps, Sets, etc. cannot be serialized
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Async Methods
|
|
181
|
+
|
|
182
|
+
Both sync and async methods work:
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
// Sync method
|
|
186
|
+
@callable()
|
|
187
|
+
add(a: number, b: number): number {
|
|
188
|
+
return a + b;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Async method
|
|
192
|
+
@callable()
|
|
193
|
+
async fetchUser(id: string): Promise<User> {
|
|
194
|
+
const user = await this.sql`SELECT * FROM users WHERE id = ${id}`;
|
|
195
|
+
return user[0];
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Void Methods
|
|
200
|
+
|
|
201
|
+
Methods that don't return a value:
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
@callable()
|
|
205
|
+
async logEvent(event: string): Promise<void> {
|
|
206
|
+
await this.sql`INSERT INTO events (name) VALUES (${event})`;
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
On the client, these still return a Promise that resolves when the method completes:
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
await agent.stub.logEvent("user-clicked");
|
|
214
|
+
// Resolves when the server confirms execution
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Streaming Responses
|
|
218
|
+
|
|
219
|
+
For methods that produce data over time (like AI text generation), use streaming:
|
|
220
|
+
|
|
221
|
+
### Defining a Streaming Method
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
import { Agent, callable, type StreamingResponse } from "agents";
|
|
225
|
+
|
|
226
|
+
export class AIAgent extends Agent {
|
|
227
|
+
@callable({ streaming: true })
|
|
228
|
+
async generateText(stream: StreamingResponse, prompt: string) {
|
|
229
|
+
// First parameter is always StreamingResponse for streaming methods
|
|
230
|
+
|
|
231
|
+
for await (const chunk of this.llm.stream(prompt)) {
|
|
232
|
+
stream.send(chunk); // Send each chunk to the client
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
stream.end(); // Signal completion
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
@callable({ streaming: true })
|
|
239
|
+
async streamNumbers(stream: StreamingResponse, count: number) {
|
|
240
|
+
for (let i = 0; i < count; i++) {
|
|
241
|
+
stream.send(i);
|
|
242
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
243
|
+
}
|
|
244
|
+
stream.end(count); // Optional final value
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Consuming Streams on the Client
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
// Preferred format (supports timeout and other options)
|
|
253
|
+
await agent.call("generateText", [prompt], {
|
|
254
|
+
stream: {
|
|
255
|
+
onChunk: (chunk) => {
|
|
256
|
+
// Called for each chunk
|
|
257
|
+
appendToOutput(chunk);
|
|
258
|
+
},
|
|
259
|
+
onDone: (finalValue) => {
|
|
260
|
+
// Called when stream ends
|
|
261
|
+
console.log("Stream complete", finalValue);
|
|
262
|
+
},
|
|
263
|
+
onError: (error) => {
|
|
264
|
+
// Called if an error occurs
|
|
265
|
+
console.error("Stream error:", error);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// Legacy format (still supported for backward compatibility)
|
|
271
|
+
await agent.call("generateText", [prompt], {
|
|
272
|
+
onChunk: (chunk) => appendToOutput(chunk),
|
|
273
|
+
onDone: (finalValue) => console.log("Done", finalValue),
|
|
274
|
+
onError: (error) => console.error("Error:", error)
|
|
275
|
+
});
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### StreamingResponse API
|
|
279
|
+
|
|
280
|
+
| Method | Description |
|
|
281
|
+
| ------------------ | ------------------------------------------------ |
|
|
282
|
+
| `send(chunk)` | Send a chunk to the client |
|
|
283
|
+
| `end(finalChunk?)` | End the stream, optionally with a final value |
|
|
284
|
+
| `error(message)` | Send an error to the client and close the stream |
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
@callable({ streaming: true })
|
|
288
|
+
async processWithProgress(stream: StreamingResponse, items: string[]) {
|
|
289
|
+
for (let i = 0; i < items.length; i++) {
|
|
290
|
+
await this.process(items[i]);
|
|
291
|
+
stream.send({ progress: (i + 1) / items.length, item: items[i] });
|
|
292
|
+
}
|
|
293
|
+
stream.end({ completed: true, total: items.length });
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## TypeScript Integration
|
|
298
|
+
|
|
299
|
+
### Typed Client Calls
|
|
300
|
+
|
|
301
|
+
Pass your agent class as a type parameter for full type safety:
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
import { useAgent } from "agents/react";
|
|
305
|
+
import type { MyAgent } from "./server";
|
|
306
|
+
|
|
307
|
+
function App() {
|
|
308
|
+
const agent = useAgent<MyAgent, MyState>({
|
|
309
|
+
agent: "MyAgent",
|
|
310
|
+
name: "default"
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// ✅ TypeScript knows the method signature
|
|
314
|
+
const result = await agent.stub.greet("World");
|
|
315
|
+
// ^? string
|
|
316
|
+
|
|
317
|
+
// ✅ TypeScript catches errors
|
|
318
|
+
await agent.stub.greet(123); // Error: Argument of type 'number' is not assignable
|
|
319
|
+
await agent.stub.nonExistent(); // Error: Property 'nonExistent' does not exist
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Excluding Non-Callable Methods
|
|
324
|
+
|
|
325
|
+
If you have methods that aren't decorated with `@callable()`, you can exclude them from the type:
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
class MyAgent extends Agent {
|
|
329
|
+
@callable()
|
|
330
|
+
publicMethod(): string {
|
|
331
|
+
return "public";
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Not callable from clients
|
|
335
|
+
internalMethod(): void {
|
|
336
|
+
// internal logic
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Exclude internal methods from the client type
|
|
341
|
+
const agent = useAgent<Omit<MyAgent, "internalMethod">, {}>({
|
|
342
|
+
agent: "MyAgent"
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
agent.stub.publicMethod(); // ✅ Works
|
|
346
|
+
agent.stub.internalMethod(); // ✅ TypeScript error
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Type Inference for State
|
|
350
|
+
|
|
351
|
+
When methods return `this.state`, TypeScript correctly infers the type:
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
type MyState = { count: number; name: string };
|
|
355
|
+
|
|
356
|
+
class MyAgent extends Agent<Env, MyState> {
|
|
357
|
+
@callable()
|
|
358
|
+
async getState(): Promise<MyState> {
|
|
359
|
+
return this.state;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Client
|
|
364
|
+
const state = await agent.stub.getState();
|
|
365
|
+
// ^? MyState
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
## Error Handling
|
|
369
|
+
|
|
370
|
+
### Throwing Errors in Callable Methods
|
|
371
|
+
|
|
372
|
+
Errors thrown in callable methods are propagated to the client:
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
@callable()
|
|
376
|
+
async riskyOperation(data: unknown): Promise<void> {
|
|
377
|
+
if (!isValid(data)) {
|
|
378
|
+
throw new Error("Invalid data format");
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
try {
|
|
382
|
+
await this.processData(data);
|
|
383
|
+
} catch (e) {
|
|
384
|
+
throw new Error("Processing failed: " + e.message);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### Client-Side Error Handling
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
try {
|
|
393
|
+
const result = await agent.stub.riskyOperation(data);
|
|
394
|
+
} catch (error) {
|
|
395
|
+
// Error thrown by the agent method
|
|
396
|
+
console.error("RPC failed:", error.message);
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### Streaming Error Handling
|
|
401
|
+
|
|
402
|
+
For streaming methods, use the `onError` callback:
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
await agent.call("streamData", [input], {
|
|
406
|
+
stream: {
|
|
407
|
+
onChunk: (chunk) => handleChunk(chunk),
|
|
408
|
+
onError: (errorMessage) => {
|
|
409
|
+
console.error("Stream error:", errorMessage);
|
|
410
|
+
showErrorUI(errorMessage);
|
|
411
|
+
},
|
|
412
|
+
onDone: (result) => handleComplete(result)
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
Server-side, you can use `stream.error()` to gracefully send an error mid-stream:
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
@callable({ streaming: true })
|
|
421
|
+
async processItems(stream: StreamingResponse, items: string[]) {
|
|
422
|
+
for (const item of items) {
|
|
423
|
+
try {
|
|
424
|
+
const result = await this.process(item);
|
|
425
|
+
stream.send(result);
|
|
426
|
+
} catch (e) {
|
|
427
|
+
stream.error(`Failed to process ${item}: ${e.message}`);
|
|
428
|
+
return; // Stream is now closed
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
stream.end();
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
### Connection Errors
|
|
436
|
+
|
|
437
|
+
If the WebSocket connection closes while RPC calls are pending, they automatically reject with a "Connection closed" error:
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
try {
|
|
441
|
+
const result = await agent.call("longRunningMethod", []);
|
|
442
|
+
} catch (error) {
|
|
443
|
+
if (error.message === "Connection closed") {
|
|
444
|
+
// Handle disconnection
|
|
445
|
+
console.log("Lost connection to agent");
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
#### Retrying After Reconnection
|
|
451
|
+
|
|
452
|
+
PartySocket automatically reconnects after disconnection. To retry a failed call after reconnection, await `agent.ready` before retrying:
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
async function callWithRetry<T>(
|
|
456
|
+
agent: AgentClient,
|
|
457
|
+
method: string,
|
|
458
|
+
args: unknown[] = []
|
|
459
|
+
): Promise<T> {
|
|
460
|
+
try {
|
|
461
|
+
return await agent.call(method, args);
|
|
462
|
+
} catch (error) {
|
|
463
|
+
if (error.message === "Connection closed") {
|
|
464
|
+
await agent.ready; // Wait for reconnection
|
|
465
|
+
return await agent.call(method, args); // Retry once
|
|
466
|
+
}
|
|
467
|
+
throw error;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Usage
|
|
472
|
+
const result = await callWithRetry(agent, "processData", [data]);
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
> **Note:** Only retry idempotent operations. If the server received the request but the connection dropped before the response arrived, retrying could cause duplicate execution.
|
|
476
|
+
|
|
477
|
+
## When NOT to Use @callable
|
|
478
|
+
|
|
479
|
+
### Worker-to-Agent Calls
|
|
480
|
+
|
|
481
|
+
When calling an agent from the same Worker (e.g., in your `fetch` handler), use Durable Object RPC directly:
|
|
482
|
+
|
|
483
|
+
```typescript
|
|
484
|
+
import { getAgentByName } from "agents";
|
|
485
|
+
|
|
486
|
+
export default {
|
|
487
|
+
async fetch(request: Request, env: Env) {
|
|
488
|
+
// Get the agent stub
|
|
489
|
+
const agent = await getAgentByName(env.MyAgent, "instance-name");
|
|
490
|
+
|
|
491
|
+
// Call methods directly - no @callable needed
|
|
492
|
+
const result = await agent.processData(data);
|
|
493
|
+
|
|
494
|
+
return Response.json(result);
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
### Agent-to-Agent Calls
|
|
500
|
+
|
|
501
|
+
When one agent needs to call another:
|
|
502
|
+
|
|
503
|
+
```typescript
|
|
504
|
+
class OrchestratorAgent extends Agent {
|
|
505
|
+
async delegateWork(taskId: string) {
|
|
506
|
+
// Get another agent
|
|
507
|
+
const worker = await getAgentByName(this.env.WorkerAgent, taskId);
|
|
508
|
+
|
|
509
|
+
// Call its methods directly
|
|
510
|
+
const result = await worker.doWork();
|
|
511
|
+
|
|
512
|
+
return result;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
### Why the Distinction?
|
|
518
|
+
|
|
519
|
+
| RPC Type | Transport | Use Case |
|
|
520
|
+
| ----------- | --------- | --------------------------------- |
|
|
521
|
+
| `@callable` | WebSocket | External clients (browsers, apps) |
|
|
522
|
+
| DO RPC | Internal | Worker ↔ Agent, Agent ↔ Agent |
|
|
523
|
+
|
|
524
|
+
DO RPC is more efficient for internal calls since it doesn't go through WebSocket serialization. The `@callable` decorator adds the necessary WebSocket RPC handling for external clients.
|
|
525
|
+
|
|
526
|
+
## API Reference
|
|
527
|
+
|
|
528
|
+
### `@callable(metadata?)` Decorator
|
|
529
|
+
|
|
530
|
+
Marks a method as callable from external clients.
|
|
531
|
+
|
|
532
|
+
```typescript
|
|
533
|
+
import { callable } from "agents";
|
|
534
|
+
|
|
535
|
+
@callable()
|
|
536
|
+
method(): void {}
|
|
537
|
+
|
|
538
|
+
@callable({ streaming: true })
|
|
539
|
+
streamingMethod(stream: StreamingResponse): void {}
|
|
540
|
+
|
|
541
|
+
@callable({ description: "Fetches user data" })
|
|
542
|
+
getUser(id: string): User {}
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
### `CallableMetadata` Type
|
|
546
|
+
|
|
547
|
+
```typescript
|
|
548
|
+
type CallableMetadata = {
|
|
549
|
+
/** Optional description of what the method does */
|
|
550
|
+
description?: string;
|
|
551
|
+
/** Whether the method supports streaming responses */
|
|
552
|
+
streaming?: boolean;
|
|
553
|
+
};
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
### `StreamingResponse` Class
|
|
557
|
+
|
|
558
|
+
Used in streaming callable methods to send data to the client.
|
|
559
|
+
|
|
560
|
+
```typescript
|
|
561
|
+
import { type StreamingResponse } from "agents";
|
|
562
|
+
|
|
563
|
+
@callable({ streaming: true })
|
|
564
|
+
async streamData(stream: StreamingResponse, input: string) {
|
|
565
|
+
stream.send("chunk 1");
|
|
566
|
+
stream.send("chunk 2");
|
|
567
|
+
stream.end("final");
|
|
568
|
+
}
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
| Method | Signature | Description |
|
|
572
|
+
| ------- | -------------------------------- | ---------------------------------- |
|
|
573
|
+
| `send` | `(chunk: unknown) => void` | Send a chunk to the client |
|
|
574
|
+
| `end` | `(finalChunk?: unknown) => void` | End the stream |
|
|
575
|
+
| `error` | `(message: string) => void` | Send an error and close the stream |
|
|
576
|
+
|
|
577
|
+
### Client Methods
|
|
578
|
+
|
|
579
|
+
| Method | Signature | Description |
|
|
580
|
+
| ------------ | -------------------------------------- | --------------------- |
|
|
581
|
+
| `agent.call` | `(method, args?, options?) => Promise` | Call a method by name |
|
|
582
|
+
| `agent.stub` | `Proxy` | Typed method calls |
|
|
583
|
+
|
|
584
|
+
```typescript
|
|
585
|
+
// Using call()
|
|
586
|
+
await agent.call("methodName", [arg1, arg2]);
|
|
587
|
+
await agent.call("streamMethod", [arg], {
|
|
588
|
+
stream: { onChunk, onDone, onError }
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
// With timeout (rejects if call doesn't complete in time)
|
|
592
|
+
await agent.call("slowMethod", [], { timeout: 5000 });
|
|
593
|
+
|
|
594
|
+
// Using stub
|
|
595
|
+
await agent.stub.methodName(arg1, arg2);
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
### `CallOptions` Type
|
|
599
|
+
|
|
600
|
+
```typescript
|
|
601
|
+
type CallOptions = {
|
|
602
|
+
/** Timeout in milliseconds. Rejects if call doesn't complete in time. */
|
|
603
|
+
timeout?: number;
|
|
604
|
+
/** Streaming options */
|
|
605
|
+
stream?: {
|
|
606
|
+
onChunk?: (chunk: unknown) => void;
|
|
607
|
+
onDone?: (finalChunk: unknown) => void;
|
|
608
|
+
onError?: (error: string) => void;
|
|
609
|
+
};
|
|
610
|
+
};
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
> **Backward Compatibility**: The legacy format `{ onChunk, onDone, onError }` (without nesting under `stream`) is still supported. The client auto-detects which format you're using.
|
|
614
|
+
|
|
615
|
+
### `getCallableMethods()` Method
|
|
616
|
+
|
|
617
|
+
Returns a map of all callable methods on the agent with their metadata. Useful for introspection and auto-documentation.
|
|
618
|
+
|
|
619
|
+
```typescript
|
|
620
|
+
const methods = agent.getCallableMethods();
|
|
621
|
+
// Map<string, CallableMetadata>
|
|
622
|
+
|
|
623
|
+
for (const [name, meta] of methods) {
|
|
624
|
+
console.log(`${name}: ${meta.description || "(no description)"}`);
|
|
625
|
+
if (meta.streaming) console.log(" (streaming)");
|
|
626
|
+
}
|
|
627
|
+
```
|