agents 0.23.0 → 0.24.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/dist/{agent-routing-DE5zmCQ8.d.ts → agent-routing-B2XLNMxq.d.ts} +196 -189
- package/dist/agent-routing.d.ts +1 -1
- package/dist/agent-tool-types.d.ts +26 -26
- package/dist/{agent-tools-DtXMTDGM.d.ts → agent-tools-Dj5Mzlw7.d.ts} +7 -7
- package/dist/agent-tools.d.ts +21 -21
- package/dist/{capability-BjSKYpzg.js → capability-B4WbF81e.js} +2 -1
- package/dist/capability-B4WbF81e.js.map +1 -0
- package/dist/{capability-runner-Be_-PLR1.d.ts → capability-runner-BUBa6Ake.d.ts} +40 -15
- package/dist/chat/index.d.ts +14 -11
- package/dist/chat/index.js +11 -7
- package/dist/chat/index.js.map +1 -1
- package/dist/chat-sdk/index.d.ts +3 -3
- package/dist/chat-sdk/index.js +1 -1
- package/dist/client-BxdkCuVy.d.ts +411 -0
- package/dist/client-C9DAjZIY.js +518 -0
- package/dist/client-C9DAjZIY.js.map +1 -0
- package/dist/{client-jagG8a9_.js → client-fyQrLGkD.js} +2 -2
- package/dist/{client-jagG8a9_.js.map → client-fyQrLGkD.js.map} +1 -1
- package/dist/client.d.ts +27 -283
- package/dist/client.js +2 -274
- package/dist/current-agent-C4w86kcN.d.ts +93 -0
- package/dist/{current-agent-Da_C9a3b.d.ts → durable-object-lifecycle-ei7M7lqB.d.ts} +13 -122
- package/dist/index-Dlgtd3Mj.d.ts +78 -0
- package/dist/index.d.ts +59 -59
- package/dist/index.js +2 -2
- package/dist/internal_context.d.ts +1 -1
- package/dist/lifecycle/index.d.ts +19 -17
- package/dist/lifecycle/index.js +2 -2
- package/dist/{lifecycle-CMRGjZdw.js → lifecycle-Mm_jQh7r.js} +36 -21
- package/dist/lifecycle-Mm_jQh7r.js.map +1 -0
- package/dist/mcp/client/index.d.ts +20 -20
- package/dist/mcp/client/index.js +1 -1
- package/dist/mcp/index.d.ts +35 -35
- package/dist/mcp/index.js +2 -2
- package/dist/queue/index.d.ts +123 -0
- package/dist/queue/index.js +2 -0
- package/dist/queue-BC9F5cUo.js +406 -0
- package/dist/queue-BC9F5cUo.js.map +1 -0
- package/dist/react.d.ts +33 -15
- package/dist/react.js +31 -8
- package/dist/react.js.map +1 -1
- package/dist/{retries-D9Ds-1lz.d.ts → retries-D9B2UCq3.d.ts} +9 -3
- package/dist/retries.d.ts +5 -3
- package/dist/retries.js +9 -1
- package/dist/retries.js.map +1 -1
- package/dist/routing/index.d.ts +2 -2
- package/dist/routing/index.js +1 -1
- package/dist/{scheduler-Dwh85ZGl.d.ts → scheduler-BGq6M5Kd.d.ts} +6 -6
- package/dist/{scheduler-DD9NdYbF.js → scheduler-D_KHqxBa.js} +4 -11
- package/dist/scheduler-D_KHqxBa.js.map +1 -0
- package/dist/schedules/index.d.ts +1 -1
- package/dist/schedules/index.js +1 -1
- package/dist/serializable.d.ts +1 -1
- package/dist/sessions/index.d.ts +1 -1
- package/dist/sessions/index.js +1 -1
- package/dist/{src-DlSHshb2.js → src-BNU3ZiJM.js} +126 -393
- package/dist/src-BNU3ZiJM.js.map +1 -0
- package/dist/state/index.d.ts +6 -0
- package/dist/state/index.js +137 -0
- package/dist/state/index.js.map +1 -0
- package/dist/streams/index.d.ts +1 -1
- package/dist/streams/index.js +1 -1
- package/dist/{streams-D6tJ0NN9.d.ts → streams-BcE8JP2X.d.ts} +2 -2
- package/dist/{streams-DZKgAj9b.js → streams-CCPRV6dt.js} +2 -2
- package/dist/{streams-DZKgAj9b.js.map → streams-CCPRV6dt.js.map} +1 -1
- package/dist/sub-routing.d.ts +12 -12
- package/dist/tasks/index.d.ts +1 -1
- package/dist/tasks/index.js +1 -1
- package/dist/{tasks-ylZgBjhj.js → tasks-D4nLqVSI.js} +3 -3
- package/dist/{tasks-ylZgBjhj.js.map → tasks-D4nLqVSI.js.map} +1 -1
- package/dist/{tasks-BRJ5zgya.d.ts → tasks-V2lCT8ZV.d.ts} +5 -5
- package/dist/transport-protocol-BxyngQ_R.js +25 -0
- package/dist/transport-protocol-BxyngQ_R.js.map +1 -0
- package/dist/transport-protocol-ZTew5Wso.d.ts +27 -0
- package/dist/types-DCD6BNZH.d.ts +70 -0
- package/dist/voice/index.d.ts +2 -2
- package/dist/websockets/index.d.ts +178 -69
- package/dist/websockets/index.js +3 -2
- package/dist/websockets-D7IzWbZb.js +1005 -0
- package/dist/websockets-D7IzWbZb.js.map +1 -0
- package/dist/workflow-types.d.ts +25 -25
- package/dist/workflows.d.ts +21 -21
- package/dist/workflows.js +1 -1
- package/docs/agent-class.md +2 -2
- package/docs/client-sdk.md +30 -0
- package/docs/index.md +1 -1
- package/docs/lifecycle.md +97 -22
- package/docs/queue.md +135 -241
- package/docs/retries.md +1 -1
- package/docs/sub-agents.md +3 -3
- package/package.json +12 -2
- package/dist/capability-BjSKYpzg.js.map +0 -1
- package/dist/client.js.map +0 -1
- package/dist/lifecycle-CMRGjZdw.js.map +0 -1
- package/dist/scheduler-DD9NdYbF.js.map +0 -1
- package/dist/src-DlSHshb2.js.map +0 -1
- package/dist/websockets-DUfRHPRq.js +0 -502
- package/dist/websockets-DUfRHPRq.js.map +0 -1
package/docs/queue.md
CHANGED
|
@@ -1,218 +1,157 @@
|
|
|
1
|
-
# Queue
|
|
1
|
+
# Queue
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Durable background work for an Agent or any Lifecycle Object. A queued item
|
|
4
|
+
persists a callback name and a payload, runs from the Durable Object's alarm
|
|
5
|
+
loop one item at a time in push order, retries when the callback throws, and
|
|
6
|
+
survives the object leaving memory.
|
|
4
7
|
|
|
5
|
-
##
|
|
8
|
+
## Queue Lifecycle capability
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
> **Experimental.** The `Queue` capability and the `agents/lifecycle` surface
|
|
11
|
+
> it builds on may change between releases. Agent's established queue methods
|
|
12
|
+
> (`this.queue()` and friends) are stable.
|
|
8
13
|
|
|
9
|
-
|
|
14
|
+
`Queue` is a reusable Lifecycle capability. A plain Lifecycle Object can
|
|
15
|
+
install it without extending `Agent`:
|
|
10
16
|
|
|
11
17
|
```typescript
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
callback: keyof Agent<unknown>; // Name of the method to call
|
|
16
|
-
created_at: number; // Timestamp when the task was created
|
|
17
|
-
retry?: RetryOptions; // Retry options (if configured)
|
|
18
|
-
};
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## Core Methods
|
|
22
|
-
|
|
23
|
-
### queue()
|
|
24
|
-
|
|
25
|
-
Adds a task to the queue for future execution.
|
|
26
|
-
|
|
27
|
-
```typescript
|
|
28
|
-
async queue<T = unknown>(
|
|
29
|
-
callback: keyof this,
|
|
30
|
-
payload: T,
|
|
31
|
-
options?: { retry?: RetryOptions }
|
|
32
|
-
): Promise<string>
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
**Parameters:**
|
|
36
|
-
|
|
37
|
-
- `callback`: The name of the method to call when processing the task
|
|
38
|
-
- `payload`: Data to pass to the callback method
|
|
39
|
-
- `options.retry`: Optional retry configuration. See [Retries](./retries.md) for details.
|
|
40
|
-
|
|
41
|
-
**Returns:** The unique ID of the queued task
|
|
42
|
-
|
|
43
|
-
**Example:**
|
|
18
|
+
import { DurableObject } from "cloudflare:workers";
|
|
19
|
+
import { Lifecycle } from "agents/lifecycle";
|
|
20
|
+
import { Queue, type QueueItem } from "agents/queue";
|
|
44
21
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
22
|
+
export class UploadObject extends DurableObject<Env> {
|
|
23
|
+
readonly queue = new Queue({
|
|
24
|
+
callbacks: {
|
|
25
|
+
processUpload: (
|
|
26
|
+
payload: { fileName: string },
|
|
27
|
+
item: QueueItem<{ fileName: string }>
|
|
28
|
+
) => {
|
|
29
|
+
console.log(item.id, payload.fileName);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
});
|
|
51
33
|
|
|
52
|
-
|
|
53
|
-
// Queue an email processing task
|
|
54
|
-
const taskId = await this.queue("processEmail", {
|
|
55
|
-
email: "user@example.com",
|
|
56
|
-
subject: "Welcome!"
|
|
57
|
-
});
|
|
34
|
+
readonly lifecycle = Lifecycle.install(this).use(this.queue);
|
|
58
35
|
|
|
59
|
-
|
|
36
|
+
async enqueueUpload(fileName: string): Promise<string> {
|
|
37
|
+
const item = await this.queue.push("processUpload", { fileName });
|
|
38
|
+
return item.id;
|
|
60
39
|
}
|
|
61
40
|
}
|
|
62
41
|
```
|
|
63
42
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
```typescript
|
|
69
|
-
dequeue(id: string): void
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
**Parameters:**
|
|
73
|
-
|
|
74
|
-
- `id`: The ID of the task to remove
|
|
75
|
-
|
|
76
|
-
**Example:**
|
|
77
|
-
|
|
78
|
-
```typescript
|
|
79
|
-
// Remove a specific task
|
|
80
|
-
agent.dequeue("abc123def");
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### dequeueAll()
|
|
84
|
-
|
|
85
|
-
Removes all tasks from the queue.
|
|
86
|
-
|
|
87
|
-
```typescript
|
|
88
|
-
dequeueAll(): void
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
**Example:**
|
|
92
|
-
|
|
93
|
-
```typescript
|
|
94
|
-
// Clear the entire queue
|
|
95
|
-
agent.dequeueAll();
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
### dequeueAllByCallback()
|
|
99
|
-
|
|
100
|
-
Removes all tasks that match a specific callback method.
|
|
101
|
-
|
|
102
|
-
```typescript
|
|
103
|
-
dequeueAllByCallback(callback: string): void
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
**Parameters:**
|
|
107
|
-
|
|
108
|
-
- `callback`: Name of the callback method
|
|
109
|
-
|
|
110
|
-
**Example:**
|
|
111
|
-
|
|
112
|
-
```typescript
|
|
113
|
-
// Remove all email processing tasks
|
|
114
|
-
agent.dequeueAllByCallback("processEmail");
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### getQueue()
|
|
118
|
-
|
|
119
|
-
Retrieves a specific queued task by ID.
|
|
120
|
-
|
|
121
|
-
```typescript
|
|
122
|
-
getQueue(id: string): QueueItem<string> | undefined
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
**Parameters:**
|
|
43
|
+
Queue owns no storage of its own. Each item is one job in the Lifecycle job
|
|
44
|
+
queue, due immediately; Lifecycle owns the physical Durable Object alarm, the
|
|
45
|
+
alarm event loop, retry policy, the deadman pre-arm, and the memory-limit
|
|
46
|
+
circuit breaker. See [Durable Object lifecycle](./lifecycle.md#the-job-queue).
|
|
126
47
|
|
|
127
|
-
|
|
48
|
+
Queue's API is small: callbacks are registered by name in the constructor,
|
|
49
|
+
`push()` creates items typed against that registration, and `get()`,
|
|
50
|
+
`list()`, `cancel()`, and `cancelAll()` manage them. All of these are
|
|
51
|
+
asynchronous and work inside routed sub-agents.
|
|
128
52
|
|
|
129
|
-
|
|
53
|
+
Queue Lifecycle hooks run without ambient host context. Registered callbacks
|
|
54
|
+
are user code, so they run inside the host invocation context with the
|
|
55
|
+
Lifecycle Object available through `getCurrentAgent()`.
|
|
130
56
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
**Example:**
|
|
134
|
-
|
|
135
|
-
```typescript
|
|
136
|
-
const task = agent.getQueue("abc123def");
|
|
137
|
-
if (task) {
|
|
138
|
-
console.log(`Task callback: ${task.callback}`);
|
|
139
|
-
console.log(`Task payload:`, task.payload);
|
|
140
|
-
}
|
|
141
|
-
```
|
|
57
|
+
### `new Queue(options?)`
|
|
142
58
|
|
|
143
|
-
|
|
59
|
+
| Option | Description |
|
|
60
|
+
| ----------- | ------------------------------------------------------------------------------------------------------------- |
|
|
61
|
+
| `callbacks` | Named callbacks `(payload, item) => unknown`. Register in a field initializer so names re-bind on every wake. |
|
|
62
|
+
| `retry` | Default retry policy: `{ maxAttempts: 3, baseDelayMs: 100, maxDelayMs: 3000 }`. See [Retries](./retries.md). |
|
|
63
|
+
| `onError` | Observes an item's terminal failure after its attempts are exhausted. Runs without host context. |
|
|
144
64
|
|
|
145
|
-
|
|
65
|
+
### `push(callback, payload?, options?)`
|
|
146
66
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
67
|
+
Queues one item and returns the `QueueItem`. The item is durable before the
|
|
68
|
+
returned promise resolves, and it runs from the next alarm, so the caller
|
|
69
|
+
never waits for the callback.
|
|
150
70
|
|
|
151
|
-
|
|
71
|
+
- `options.retry` overrides the Queue's retry policy for this item.
|
|
72
|
+
- `options.id` sets a stable id. A push with an existing id replaces that
|
|
73
|
+
item in place, keeping its position in the queue (and superseding a
|
|
74
|
+
dispatch of it still in flight); use it for idempotent enqueues keyed by
|
|
75
|
+
your own identifier.
|
|
152
76
|
|
|
153
|
-
|
|
154
|
-
|
|
77
|
+
Once the Lifecycle has started, the item row is written synchronously before
|
|
78
|
+
`push()` returns its promise, so a push made in the same synchronous block as
|
|
79
|
+
your own SQL writes commits atomically with them.
|
|
155
80
|
|
|
156
|
-
|
|
81
|
+
### `get(id)`, `list(criteria?)`, `cancel(id)`, `cancelAll(callback?)`
|
|
157
82
|
|
|
158
|
-
|
|
83
|
+
`list()` returns pending items in push order; `{ callback }` filters by
|
|
84
|
+
callback name. `cancel()` returns whether an item was removed; `cancelAll()`
|
|
85
|
+
returns how many were.
|
|
159
86
|
|
|
160
|
-
|
|
87
|
+
### QueueItem
|
|
161
88
|
|
|
162
89
|
```typescript
|
|
163
|
-
|
|
164
|
-
|
|
90
|
+
type QueueItem<T = unknown> = {
|
|
91
|
+
id: string; // Unique identifier
|
|
92
|
+
callback: string; // Callback name
|
|
93
|
+
payload: T; // Data passed to the callback
|
|
94
|
+
createdAt: number; // Unix timestamp in seconds
|
|
95
|
+
retry?: RetryOptions; // Resolved retry policy
|
|
96
|
+
};
|
|
165
97
|
```
|
|
166
98
|
|
|
167
|
-
##
|
|
168
|
-
|
|
169
|
-
1. **Validation**: When calling `queue()`, the method validates that the callback exists as a function on the agent
|
|
170
|
-
2. **Automatic Processing**: After queuing, the system automatically attempts to flush the queue
|
|
171
|
-
3. **FIFO Order**: Tasks are processed in the order they were created (`created_at` timestamp)
|
|
172
|
-
4. **Context Preservation**: Each queued task runs with the same agent context (connection, request, email)
|
|
173
|
-
5. **Automatic Retries**: If a callback fails, it is retried with exponential backoff (configurable per task)
|
|
174
|
-
6. **Automatic Dequeue**: Tasks are removed from the queue after successful execution or after all retry attempts are exhausted
|
|
175
|
-
7. **Error Handling**: If a callback method does not exist at execution time, an error is logged and the task is skipped
|
|
176
|
-
8. **Persistence**: Tasks are stored in the `cf_agents_queues` table and survive agent restarts
|
|
99
|
+
## Using the queue through Agent
|
|
177
100
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
When defining callback methods for queued tasks, they must follow this signature:
|
|
181
|
-
|
|
182
|
-
```typescript
|
|
183
|
-
async callbackMethod(payload: unknown, queueItem: QueueItem<string>): Promise<void>
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
**Example:**
|
|
101
|
+
`Agent` installs a `Queue` and resolves callback names to its own methods, so
|
|
102
|
+
`this.queue("methodName", payload)` keeps working with no registration:
|
|
187
103
|
|
|
188
104
|
```typescript
|
|
189
105
|
class MyAgent extends Agent {
|
|
190
|
-
async
|
|
191
|
-
payload: {
|
|
192
|
-
|
|
106
|
+
async processEmail(
|
|
107
|
+
payload: { email: string; subject: string },
|
|
108
|
+
item: QueueItem<{ email: string; subject: string }>
|
|
193
109
|
) {
|
|
194
|
-
console.log(`Processing
|
|
195
|
-
console.log(
|
|
196
|
-
`Sending notification to user ${payload.userId}: ${payload.message}`
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
// Your notification logic here
|
|
200
|
-
await this.notificationService.send(payload.userId, payload.message);
|
|
110
|
+
console.log(`Processing ${item.id}: ${payload.subject}`);
|
|
201
111
|
}
|
|
202
112
|
|
|
203
|
-
async
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
message: "Welcome to our platform!"
|
|
113
|
+
async onMessage(message: string) {
|
|
114
|
+
const id = await this.queue("processEmail", {
|
|
115
|
+
email: "user@example.com",
|
|
116
|
+
subject: "Welcome!"
|
|
208
117
|
});
|
|
118
|
+
console.log(`Queued task ${id}`);
|
|
209
119
|
}
|
|
210
120
|
}
|
|
211
121
|
```
|
|
212
122
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
123
|
+
| Method | Description |
|
|
124
|
+
| ------------------------------------ | ------------------------------------------------------------------- |
|
|
125
|
+
| `queue(callback, payload, options?)` | Queue a method by name. Returns the item id. Accepts `retry`, `id`. |
|
|
126
|
+
| `dequeue(id)` | Remove a pending item. Resolves to whether one was removed. |
|
|
127
|
+
| `dequeueAll()` | Remove every pending item. Resolves to the count. |
|
|
128
|
+
| `dequeueAllByCallback(callback)` | Remove every pending item for one callback. Resolves to the count. |
|
|
129
|
+
| `getQueue(id)` | Read a pending item. |
|
|
130
|
+
| `getQueues(key, value)` | Pending items whose payload has `key` equal to `value`. |
|
|
131
|
+
|
|
132
|
+
## How queue processing works
|
|
133
|
+
|
|
134
|
+
1. **Validation**: `queue()` checks the callback exists and validates any
|
|
135
|
+
retry options. Invalid input throws immediately.
|
|
136
|
+
2. **Durable first**: the item is a row in the `cf_agents_jobs` job queue
|
|
137
|
+
before `queue()` resolves. Items created before this capability existed
|
|
138
|
+
are migrated out of the old `cf_agents_queues` table on the next start.
|
|
139
|
+
3. **Alarm-driven**: every push re-arms the Durable Object alarm. The
|
|
140
|
+
Lifecycle event loop runs due items in push order, awaiting each callback
|
|
141
|
+
before starting the next, in a fresh invocation rather than the request
|
|
142
|
+
that queued them.
|
|
143
|
+
4. **Retries**: a throwing callback is retried with exponential backoff per
|
|
144
|
+
its retry policy, emitting `queue:retry` on each extra attempt.
|
|
145
|
+
5. **Terminal failure**: after the last attempt the item is dropped, a
|
|
146
|
+
`queue:error` event is emitted, and Agent's `onError` (or the Queue's
|
|
147
|
+
`onError` option) observes the error.
|
|
148
|
+
6. **Recovery**: an isolate that dies mid-callback wakes again on the
|
|
149
|
+
Lifecycle deadman alarm and resumes the queue, so callbacks should be
|
|
150
|
+
idempotent.
|
|
151
|
+
|
|
152
|
+
## Use cases
|
|
153
|
+
|
|
154
|
+
Queue work that must not block the current request or WebSocket message:
|
|
216
155
|
|
|
217
156
|
```typescript
|
|
218
157
|
class DataProcessor extends Agent {
|
|
@@ -221,52 +160,31 @@ class DataProcessor extends Agent {
|
|
|
221
160
|
await this.notifyUser(data.userId, results);
|
|
222
161
|
}
|
|
223
162
|
|
|
224
|
-
async onDataUpload(uploadData:
|
|
225
|
-
// Queue the processing instead of doing it synchronously
|
|
163
|
+
async onDataUpload(uploadData: { id: string; userId: string }) {
|
|
226
164
|
await this.queue("processLargeDataset", {
|
|
227
165
|
datasetId: uploadData.id,
|
|
228
166
|
userId: uploadData.userId
|
|
229
167
|
});
|
|
230
|
-
|
|
231
168
|
return { message: "Data upload received, processing started" };
|
|
232
169
|
}
|
|
233
170
|
}
|
|
234
171
|
```
|
|
235
172
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
```typescript
|
|
239
|
-
class ReminderAgent extends Agent {
|
|
240
|
-
async sendReminder(data: { userId: string; message: string }) {
|
|
241
|
-
await this.emailService.send(data.userId, data.message);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
async scheduleReminder(userId: string, message: string, delayMs: number) {
|
|
245
|
-
// Note: For true delayed execution, combine with the scheduling system
|
|
246
|
-
// This example shows queueing for later processing
|
|
247
|
-
await this.queue("sendReminder", { userId, message });
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
### Batch Operations
|
|
173
|
+
Split large requests into batches that run one after another:
|
|
253
174
|
|
|
254
175
|
```typescript
|
|
255
176
|
class BatchProcessor extends Agent {
|
|
256
|
-
async processBatch(data: { items:
|
|
177
|
+
async processBatch(data: { items: unknown[]; batchId: string }) {
|
|
257
178
|
for (const item of data.items) {
|
|
258
179
|
await this.processItem(item);
|
|
259
180
|
}
|
|
260
|
-
console.log(`Completed batch ${data.batchId}`);
|
|
261
181
|
}
|
|
262
182
|
|
|
263
|
-
async onLargeRequest(items:
|
|
264
|
-
// Split large requests into smaller batches
|
|
183
|
+
async onLargeRequest(items: unknown[]) {
|
|
265
184
|
const batchSize = 10;
|
|
266
185
|
for (let i = 0; i < items.length; i += batchSize) {
|
|
267
|
-
const batch = items.slice(i, i + batchSize);
|
|
268
186
|
await this.queue("processBatch", {
|
|
269
|
-
items:
|
|
187
|
+
items: items.slice(i, i + batchSize),
|
|
270
188
|
batchId: `batch-${i / batchSize + 1}`
|
|
271
189
|
});
|
|
272
190
|
}
|
|
@@ -274,56 +192,32 @@ class BatchProcessor extends Agent {
|
|
|
274
192
|
}
|
|
275
193
|
```
|
|
276
194
|
|
|
277
|
-
|
|
195
|
+
For work that should run at a specific time or on a schedule, use
|
|
196
|
+
[Scheduling](./scheduling.md) instead. Both ride the same Lifecycle job queue.
|
|
278
197
|
|
|
279
|
-
|
|
280
|
-
2. **Idempotent Operations**: Design callback methods to be safe to retry
|
|
281
|
-
3. **Error Handling**: Include proper error handling in callback methods
|
|
282
|
-
4. **Monitoring**: Use logging to track queue processing
|
|
283
|
-
5. **Cleanup**: Regularly clean up completed or failed tasks if needed
|
|
198
|
+
## Best practices
|
|
284
199
|
|
|
285
|
-
|
|
200
|
+
1. **Keep payloads small**: payloads are JSON-serialized into the job row.
|
|
201
|
+
2. **Idempotent callbacks**: an item can run again after an isolate reset.
|
|
202
|
+
3. **Bounded callbacks**: the event loop awaits each callback, so a long one
|
|
203
|
+
delays every other job on the object. Detach unbounded work and return.
|
|
204
|
+
4. **Stable ids for dedupe**: pass `id` when the same logical task may be
|
|
205
|
+
queued more than once.
|
|
286
206
|
|
|
287
|
-
|
|
207
|
+
## Error handling and retries
|
|
288
208
|
|
|
289
209
|
```typescript
|
|
290
|
-
// Retry up to 5 times with custom backoff
|
|
291
210
|
await this.queue("reliableTask", payload, {
|
|
292
211
|
retry: { maxAttempts: 5, baseDelayMs: 500 }
|
|
293
212
|
});
|
|
294
213
|
```
|
|
295
214
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
class RobustAgent extends Agent {
|
|
300
|
-
async reliableTask(payload: { data: string }, queueItem: QueueItem<string>) {
|
|
301
|
-
try {
|
|
302
|
-
await this.doSomethingRisky(payload);
|
|
303
|
-
} catch (error) {
|
|
304
|
-
console.error(`Task ${queueItem.id} failed:`, error);
|
|
305
|
-
// The retry system will catch this error and retry automatically
|
|
306
|
-
throw error;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
See [Retries](./retries.md) for full documentation on retry options and patterns.
|
|
313
|
-
|
|
314
|
-
## Integration with Other Features
|
|
315
|
-
|
|
316
|
-
The queue system works seamlessly with other Agent SDK features:
|
|
317
|
-
|
|
318
|
-
- **State Management**: Access agent state within queued callbacks
|
|
319
|
-
- **Scheduling**: Combine with `schedule()` for time-based queue processing
|
|
320
|
-
- **Retries**: Built-in retry with exponential backoff. See [Retries](./retries.md).
|
|
321
|
-
- **Context**: Queued tasks maintain the original request context
|
|
322
|
-
- **Database**: Uses the same database as other agent data
|
|
215
|
+
See [Retries](./retries.md) for full documentation on retry options, and
|
|
216
|
+
[Observability](./observability.md) for the `queue:create`, `queue:retry`,
|
|
217
|
+
and `queue:error` events.
|
|
323
218
|
|
|
324
219
|
## Limitations
|
|
325
220
|
|
|
326
|
-
-
|
|
327
|
-
- No priority system (FIFO only)
|
|
328
|
-
-
|
|
329
|
-
- Failed tasks are dequeued after all retry attempts are exhausted (no dead-letter queue)
|
|
221
|
+
- Items run sequentially, not in parallel.
|
|
222
|
+
- No priority system (FIFO only).
|
|
223
|
+
- Failed items are dropped after all retry attempts (no dead-letter queue).
|
package/docs/retries.md
CHANGED
|
@@ -188,7 +188,7 @@ await this.queue("processWebhook", webhookData, {
|
|
|
188
188
|
});
|
|
189
189
|
```
|
|
190
190
|
|
|
191
|
-
If the callback throws, it is retried before the
|
|
191
|
+
If the callback throws, it is retried before the item is removed. After all attempts are exhausted, the item is dropped, a `queue:error` event is emitted, and the error is logged.
|
|
192
192
|
|
|
193
193
|
## Validation
|
|
194
194
|
|
package/docs/sub-agents.md
CHANGED
|
@@ -4,7 +4,7 @@ Dynamic agents are child Durable Objects **colocated under and supervised by** a
|
|
|
4
4
|
|
|
5
5
|
Use dynamic agents for code whose **class or lifecycle the parent owns**: dynamically-loaded or AI-generated code that has no wrangler binding, per-run tool agents, sandboxed components that need isolated storage plus supervised abort/restart. That is what the runtime built facets for.
|
|
6
6
|
|
|
7
|
-
Do **not** use dynamic agents to model an open-ended set of independent peers — many chats, documents, or sessions per user. Those want one top-level Durable Object each plus a per-user index; see [When to use dynamic agents](#when-to-use-dynamic-agents) and [`examples/next/
|
|
7
|
+
Do **not** use dynamic agents to model an open-ended set of independent peers — many chats, documents, or sessions per user. Those want one top-level Durable Object each plus a per-user index; see [When to use dynamic agents](#when-to-use-dynamic-agents) and [`examples/next/routing`](https://github.com/cloudflare/agents/tree/main/examples/next/routing).
|
|
8
8
|
|
|
9
9
|
If you want a parent chat agent to dispatch another chat-capable agent during a
|
|
10
10
|
single turn and render that child's progress inline, use [Agent Tools](./agent-tools.md).
|
|
@@ -471,13 +471,13 @@ await user.recordChatActivity(chatId, { title, lastMessage, seq });
|
|
|
471
471
|
|
|
472
472
|
`RoutedAgents` packages the hub side of this pattern: a durable ID-to-Agent catalog plus request and WebSocket forwarding under one route segment. See [Routing to independent Agents](./routing.md#routing-to-independent-agents).
|
|
473
473
|
|
|
474
|
-
Each chat gets its own alarms, placement, and storage budget; deletion is one `chats.delete(id)` call; and "search across all my chats" reads only the index. The example treats that index as a best-effort derived projection: a failed push leaves it stale until the next message, and a push for a deleted chat is refused. See [`examples/next/
|
|
474
|
+
Each chat gets its own alarms, placement, and storage budget; deletion is one `chats.delete(id)` call; and "search across all my chats" reads only the index. The example treats that index as a best-effort derived projection: a failed push leaves it stale until the next message, and a push for a deleted chat is refused. See [`examples/next/routing`](https://github.com/cloudflare/agents/tree/main/examples/next/routing) for the pattern built on `RoutedAgents`, with tests. Idempotency and repair belong to the production design in [`design/rfc-user-chat-durable-objects.md`](https://github.com/cloudflare/agents/blob/main/design/rfc-user-chat-durable-objects.md).
|
|
475
475
|
|
|
476
476
|
## Examples
|
|
477
477
|
|
|
478
478
|
- [`examples/next/dynamic-agents`](https://github.com/cloudflare/agents/tree/main/examples/next/dynamic-agents) — the headline use case: a supervisor stores user-submitted Durable Object code, loads it via Worker Loader, and runs it as facets with isolated storage, supervised abort, and code upgrades over stable state.
|
|
479
479
|
- [`examples/agents-as-tools`](https://github.com/cloudflare/agents/tree/main/examples/agents-as-tools) — per-run child agents as tools with inline streaming.
|
|
480
|
-
- [`examples/multi-ai-chat`](https://github.com/cloudflare/agents/tree/main/examples/multi-ai-chat) — a multi-session chat app built on facet children under one `Inbox`. It works and demonstrates the routing surface, but for many long-lived chats per user prefer the top-level-DO-per-chat pattern in [`examples/next/
|
|
480
|
+
- [`examples/multi-ai-chat`](https://github.com/cloudflare/agents/tree/main/examples/multi-ai-chat) — a multi-session chat app built on facet children under one `Inbox`. It works and demonstrates the routing surface, but for many long-lived chats per user prefer the top-level-DO-per-chat pattern in [`examples/next/routing`](https://github.com/cloudflare/agents/tree/main/examples/next/routing) — see [When to use dynamic agents](#when-to-use-dynamic-agents).
|
|
481
481
|
|
|
482
482
|
## Related
|
|
483
483
|
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"durable objects"
|
|
10
10
|
],
|
|
11
11
|
"type": "module",
|
|
12
|
-
"version": "0.
|
|
12
|
+
"version": "0.24.0",
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"repository": {
|
|
15
15
|
"directory": "packages/agents",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"just-bash": "^3.0.2",
|
|
53
53
|
"react": "^19.2.7",
|
|
54
54
|
"teleproto": "^1.228.5",
|
|
55
|
-
"valibot": "1.4.
|
|
55
|
+
"valibot": "1.4.2",
|
|
56
56
|
"zod": "^4.5.4"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
@@ -200,11 +200,21 @@
|
|
|
200
200
|
"import": "./dist/websockets/index.js",
|
|
201
201
|
"require": "./dist/websockets/index.js"
|
|
202
202
|
},
|
|
203
|
+
"./state": {
|
|
204
|
+
"types": "./dist/state/index.d.ts",
|
|
205
|
+
"import": "./dist/state/index.js",
|
|
206
|
+
"require": "./dist/state/index.js"
|
|
207
|
+
},
|
|
203
208
|
"./schedule": {
|
|
204
209
|
"types": "./dist/schedule.d.ts",
|
|
205
210
|
"import": "./dist/schedule.js",
|
|
206
211
|
"require": "./dist/schedule.js"
|
|
207
212
|
},
|
|
213
|
+
"./queue": {
|
|
214
|
+
"types": "./dist/queue/index.d.ts",
|
|
215
|
+
"import": "./dist/queue/index.js",
|
|
216
|
+
"require": "./dist/queue/index.js"
|
|
217
|
+
},
|
|
208
218
|
"./schedules": {
|
|
209
219
|
"types": "./dist/schedules/index.d.ts",
|
|
210
220
|
"import": "./dist/schedules/index.js",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"capability-BjSKYpzg.js","names":[],"sources":["../src/lifecycle/capability.ts"],"sourcesContent":["import type {\n CapabilityStartContext,\n DurableObjectCapability\n} from \"./capability-runner\";\nimport type { Connection } from \"./types\";\nimport type { LifecycleJobs } from \"./job-queue\";\n\n/** Opaque address understood by a Lifecycle routing transport. */\nexport type LifecycleRouteAddress = {\n /** Stable equality and storage key. */\n readonly key: string;\n /** Transport-owned serialized address. */\n readonly data: string;\n};\n\n/** Context supplied with a routed capability message. */\nexport type LifecycleRouteContext = {\n /** Address of the sending Lifecycle, or undefined for an unrouted root. */\n readonly source: LifecycleRouteAddress | undefined;\n /** Capability-owned message payload. */\n readonly payload: unknown;\n};\n\n/** Best-effort telemetry available to every Lifecycle capability. */\nexport type LifecycleEvents = {\n /** Publish an event under this capability's stable identity. */\n readonly emit: (type: string, payload: unknown) => void;\n};\n\n/** Routing available to every Lifecycle capability. */\nexport type LifecycleRoutes = {\n /** This Lifecycle's transport address, or undefined at the route root. */\n readonly source: LifecycleRouteAddress | undefined;\n /** Route a capability-owned message to the root Lifecycle. */\n readonly toRoot: (payload: unknown) => Promise<unknown>;\n /** Route a capability-owned message to another Lifecycle. */\n readonly to: (\n target: LifecycleRouteAddress,\n payload: unknown\n ) => Promise<unknown>;\n};\n\n/**\n * Ambient scope a capability supplies when entering host context on\n * behalf of a live connection or request.\n */\nexport type LifecycleHostContextScope = {\n /** The connection the callback runs on behalf of, when there is one. */\n readonly connection?: Connection;\n /** The request the callback runs on behalf of, when there is one. */\n readonly request?: Request;\n};\n\n/**\n * The platform's hibernatable-socket surface, exposed narrowly so a\n * capability that owns connections (e.g. WebSockets) can accept and\n * enumerate them without holding the whole `DurableObjectState`.\n * These are workerd API names, not Lifecycle modeling sockets.\n */\nexport type LifecycleSockets = {\n /** Accept a socket into hibernation under the given tags. */\n readonly accept: (ws: WebSocket, tags: string[]) => void;\n /** Every hibernated socket on the object, optionally by tag. */\n readonly get: (tag?: string) => WebSocket[];\n};\n\n/**\n * Standard services granted to every installed Lifecycle capability.\n *\n * @experimental The API surface may change before stabilizing.\n */\nexport type LifecycleServices = {\n readonly storage: DurableObjectStorage;\n readonly sockets: LifecycleSockets;\n readonly ready: () => Promise<void>;\n /** True while capability and host startup hooks are still running. */\n readonly starting: () => boolean;\n /**\n * This capability's scoped access to the Lifecycle-owned work queue.\n * Pushed items are dispatched to `onJob` when due; every queue mutation\n * re-arms the physical alarm automatically.\n */\n readonly jobs: LifecycleJobs;\n /**\n * Keep work this capability hands off at a bounded `onJob` return inside\n * the current alarm's memory-limit breaker domain (#1825). Returns false,\n * tracking nothing, outside an alarm invocation.\n */\n readonly trackAlarmWork: (work: Promise<unknown>) => boolean;\n /**\n * Run a capability-held user callback inside the host invocation context.\n * Capability hooks run outside host context; this is the one boundary for\n * entering it, and a host composition root may substitute its own wrapper\n * (Agent adds tracing span scope). Pass `scope` to make a live\n * connection or request ambient for the callback.\n */\n readonly runInHostContext: (\n fn: () => unknown,\n scope?: LifecycleHostContextScope\n ) => Promise<unknown>;\n readonly events: LifecycleEvents;\n readonly routes: LifecycleRoutes;\n};\n\nconst installedServices = new WeakMap<object, LifecycleServices>();\n\n/**\n * Base class for capabilities that consume standard Lifecycle services.\n *\n * @experimental The API surface may change before stabilizing.\n */\nexport abstract class LifecycleCapability<Props extends object = object> {\n readonly capabilityId: string;\n\n protected constructor(capabilityId: string) {\n if (capabilityId.trim() === \"\") {\n throw new Error(\"Lifecycle capability IDs must be non-empty\");\n }\n this.capabilityId = capabilityId;\n }\n\n /** Default startup hook; capabilities override when they own startup work. */\n onStart(_context: CapabilityStartContext<Props>): void {}\n\n /** Standard services when installed, or undefined in isolated unit tests. */\n protected get lifecycleServices(): LifecycleServices | undefined {\n return installedServices.get(this);\n }\n\n /** Standard services supplied when Lifecycle installs this capability. */\n protected get lifecycle(): LifecycleServices {\n const services = this.lifecycleServices;\n if (!services) {\n throw new Error(\n `${this.constructor.name} must be installed with Lifecycle.use() before use`\n );\n }\n return services;\n }\n}\n\n/**\n * @internal Bind the standard service surface to one capability instance.\n * `Lifecycle.use()` calls this during installation. Test a capability by\n * installing it on a minimal Durable Object with a real Lifecycle rather\n * than binding fake services.\n */\nexport function bindLifecycleCapability(\n capability: LifecycleCapability,\n services: LifecycleServices\n): void {\n installedServices.set(capability, services);\n}\n\n/** @internal Read a capability ID without exposing installation internals. */\nexport function lifecycleCapabilityId(\n capability: DurableObjectCapability\n): string | undefined {\n return capability instanceof LifecycleCapability\n ? capability.capabilityId\n : undefined;\n}\n"],"mappings":";AAwGA,MAAM,oCAAoB,IAAI,QAAmC;;;;;;AAOjE,IAAsB,sBAAtB,MAAyE;CAGvE,YAAsB,cAAsB;EAC1C,IAAI,aAAa,KAAK,MAAM,IAC1B,MAAM,IAAI,MAAM,4CAA4C;EAE9D,KAAK,eAAe;CACtB;;CAGA,QAAQ,UAA+C,CAAC;;CAGxD,IAAc,oBAAmD;EAC/D,OAAO,kBAAkB,IAAI,IAAI;CACnC;;CAGA,IAAc,YAA+B;EAC3C,MAAM,WAAW,KAAK;EACtB,IAAI,CAAC,UACH,MAAM,IAAI,MACR,GAAG,KAAK,YAAY,KAAK,mDAC3B;EAEF,OAAO;CACT;AACF;;;;;;;AAQA,SAAgB,wBACd,YACA,UACM;CACN,kBAAkB,IAAI,YAAY,QAAQ;AAC5C;;AAGA,SAAgB,sBACd,YACoB;CACpB,OAAO,sBAAsB,sBACzB,WAAW,eACX,KAAA;AACN"}
|
package/dist/client.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":[],"sources":["../src/client.ts"],"sourcesContent":["import {\n type PartyFetchOptions,\n PartySocket,\n type PartySocketOptions\n} from \"partysocket\";\nimport type { Agent, RPCRequest, RPCResponse } from \"./\";\nimport type {\n ClientParameters,\n Method,\n RPCMethod,\n SerializableReturnValue,\n SerializableValue\n} from \"./serializable\";\nimport { MessageType } from \"./types\";\nimport { camelCaseToKebabCase, isInternalJsStubProp } from \"./utils\";\n\nexport class AgentConnectionError extends Error {\n code: number;\n reason: string;\n wasClean: boolean;\n\n constructor(event: CloseEvent) {\n const reason = event.reason || `WebSocket closed with code ${event.code}`;\n super(`Agent connection closed: ${reason}`);\n this.name = \"AgentConnectionError\";\n this.code = event.code;\n this.reason = event.reason;\n this.wasClean = event.wasClean;\n }\n}\n\nexport function isTerminalCloseEvent(event: CloseEvent): boolean {\n return event.code === 1008 || (event.code >= 4000 && event.code <= 4999);\n}\n\ntype TerminalReconnectOptions = {\n shouldReconnectOnClose?: (event: CloseEvent) => boolean;\n};\n\n/**\n * Options for creating an AgentClient\n */\nexport type AgentClientOptions<State = unknown> = Omit<\n PartySocketOptions,\n \"party\" | \"room\"\n> &\n TerminalReconnectOptions & {\n /** Name of the agent to connect to (ignored if basePath is set) */\n agent: string;\n /** Name of the specific Agent instance (ignored if basePath is set) */\n name?: string;\n /**\n * Full URL path - bypasses agent/name URL construction.\n * When set, the client connects to this path directly.\n * Server must handle routing manually (e.g., with getAgentByName + fetch).\n * @example\n * // Client connects to /user, server routes based on session\n * useAgent({ agent: \"UserAgent\", basePath: \"user\" })\n */\n basePath?: string;\n /** Called when the Agent's state is updated */\n onStateUpdate?: (state: State, source: \"server\" | \"client\") => void;\n /** Called when a state update fails (e.g., connection is readonly) */\n onStateUpdateError?: (error: string) => void;\n /**\n * Called when the server sends the agent's identity on connect.\n * Useful when using basePath, as the actual instance name is determined server-side.\n * @param name The actual agent instance name\n * @param agent The agent class name (kebab-case)\n */\n onIdentity?: (name: string, agent: string) => void;\n /**\n * Called when identity changes on reconnect (different instance than before).\n * If not provided and identity changes, a warning will be logged.\n * @param oldName Previous instance name\n * @param newName New instance name\n * @param oldAgent Previous agent class name\n * @param newAgent New agent class name\n */\n onIdentityChange?: (\n oldName: string,\n newName: string,\n oldAgent: string,\n newAgent: string\n ) => void;\n /**\n * Additional path to append to the URL.\n * Works with both standard routing and basePath.\n * @example\n * // With basePath: /user/settings\n * { basePath: \"user\", path: \"settings\" }\n * // Standard: /agents/my-agent/room/settings\n * { agent: \"MyAgent\", name: \"room\", path: \"settings\" }\n */\n path?: string;\n /**\n * Default timeout (in milliseconds) applied to non-streaming `call()`s\n * that don't pass an explicit `timeout`. Defaults to 30 000 ms.\n * Set to `0` to disable. Streaming calls never get a default timeout.\n */\n defaultCallTimeout?: number;\n /** Called when the connection closes with a terminal code and will not reconnect. */\n onConnectionError?: (error: AgentConnectionError) => void;\n };\n\n/**\n * Default timeout (in milliseconds) applied to non-streaming RPC calls\n * that don't pass an explicit `timeout`. Acts as a backstop so calls\n * whose response is lost (e.g. the connection drops mid-flight) reject\n * instead of hanging forever. Override per client via\n * `defaultCallTimeout`, or per call via `timeout` (0 disables).\n */\nexport const DEFAULT_CALL_TIMEOUT_MS = 30_000;\n\n/**\n * Options for streaming RPC calls\n */\nexport type StreamOptions = {\n /** Called when a chunk of data is received */\n onChunk?: (chunk: unknown) => void;\n /** Called when the stream ends */\n onDone?: (finalChunk: unknown) => void;\n /** Called when an error occurs */\n onError?: (error: string) => void;\n};\n\n/**\n * Options for RPC calls\n */\nexport type CallOptions = {\n /** Timeout in milliseconds. If the call doesn't complete within this time, it will be rejected. */\n timeout?: number;\n /** Streaming options for handling streaming responses */\n stream?: StreamOptions;\n};\n\n/**\n * Options for the agentFetch function\n */\nexport type AgentClientFetchOptions = Omit<\n PartyFetchOptions,\n \"party\" | \"room\"\n> & {\n /** Name of the agent to connect to (ignored if basePath is set) */\n agent: string;\n /** Name of the specific Agent instance (ignored if basePath is set) */\n name?: string;\n /**\n * Full URL path - bypasses agent/name URL construction.\n * When set, the request is made to this path directly.\n */\n basePath?: string;\n};\n\n// ---- Shared RPC Type Utilities ----\n\ntype AllOptional<T> = T extends [infer A, ...infer R]\n ? undefined extends A\n ? AllOptional<R>\n : false\n : true;\n\nexport type RPCMethods<T> = {\n [K in keyof T as T[K] extends RPCMethod<T[K]> ? K : never]: RPCMethod<T[K]>;\n};\n\ntype OptionalParametersMethod<T extends RPCMethod> =\n AllOptional<ClientParameters<T>> extends true ? T : never;\n\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any -- generic agent type constraint\nexport type AgentMethods<T> = Omit<RPCMethods<T>, keyof Agent<any, any>>;\n\nexport type OptionalAgentMethods<T> = {\n [K in keyof AgentMethods<T> as AgentMethods<T>[K] extends OptionalParametersMethod<\n AgentMethods<T>[K]\n >\n ? K\n : never]: OptionalParametersMethod<AgentMethods<T>[K]>;\n};\n\nexport type RequiredAgentMethods<T> = Omit<\n AgentMethods<T>,\n keyof OptionalAgentMethods<T>\n>;\n\nexport type AgentPromiseReturnType<T, K extends keyof AgentMethods<T>> =\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any -- generic promise return type\n ReturnType<AgentMethods<T>[K]> extends Promise<any>\n ? ReturnType<AgentMethods<T>[K]>\n : Promise<ReturnType<AgentMethods<T>[K]>>;\n\nexport type AgentStub<T> = {\n [K in keyof AgentMethods<T>]: (\n ...args: ClientParameters<AgentMethods<T>[K]>\n ) => AgentPromiseReturnType<AgentMethods<T>, K>;\n};\n\nexport type UntypedAgentStub = Record<string, Method>;\n\ntype AgentClientStub<AgentT> = keyof AgentMethods<AgentT> extends never\n ? UntypedAgentStub\n : AgentStub<AgentT>;\n\ntype OptionalArgsAgentClientCall<AgentT> = <\n K extends keyof OptionalAgentMethods<AgentT>\n>(\n method: K,\n args?: ClientParameters<OptionalAgentMethods<AgentT>[K]>,\n options?: CallOptions | StreamOptions\n) => AgentPromiseReturnType<AgentT, K>;\n\ntype RequiredArgsAgentClientCall<AgentT> = <\n K extends keyof RequiredAgentMethods<AgentT>\n>(\n method: K,\n args: ClientParameters<RequiredAgentMethods<AgentT>[K]>,\n options?: CallOptions | StreamOptions\n) => AgentPromiseReturnType<AgentT, K>;\n\ntype TypedAgentClientCall<AgentT> = OptionalArgsAgentClientCall<AgentT> &\n RequiredArgsAgentClientCall<AgentT>;\n\ntype UntypedAgentClientCall = {\n <T extends SerializableReturnValue>(\n method: string,\n args?: SerializableValue[],\n options?: CallOptions | StreamOptions\n ): Promise<T>;\n <T = unknown>(\n method: string,\n args?: unknown[],\n options?: CallOptions | StreamOptions\n ): Promise<T>;\n};\n\ntype AgentClientCall<AgentT> = keyof AgentMethods<AgentT> extends never\n ? UntypedAgentClientCall\n : TypedAgentClientCall<AgentT>;\n\n/**\n * Creates a proxy that wraps RPC method calls.\n * Internal JS methods (toJSON, then, etc.) return undefined to avoid\n * triggering RPC calls during serialization (e.g., console.log)\n */\nexport function createStubProxy<T = Record<string, Method>>(\n call: (method: string, args: unknown[]) => unknown\n): T {\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any -- proxy needs any for dynamic method access\n return new Proxy<any>(\n {},\n {\n get: (_target, method) => {\n if (isInternalJsStubProp(method)) {\n return undefined;\n }\n return (...args: unknown[]) => call(method as string, args);\n }\n }\n );\n}\n\n/**\n * WebSocket client for connecting to an Agent\n */\nexport class AgentClient<\n AgentT = unknown,\n State = AgentT extends { get state(): infer S } ? S : AgentT\n> extends PartySocket {\n /**\n * @deprecated Use agentFetch instead\n */\n static fetch(_opts: PartyFetchOptions): Promise<Response> {\n throw new Error(\n \"AgentClient.fetch is not implemented, use agentFetch instead\"\n );\n }\n agent: string;\n name: string;\n call: AgentClientCall<AgentT>;\n stub: AgentClientStub<AgentT>;\n\n /**\n * The current agent state, updated on server broadcasts and client setState calls.\n * Starts as undefined until the first state message is received from the server.\n */\n state: State | undefined = undefined;\n\n /**\n * Whether the client has received identity from the server.\n * Becomes true after the first identity message is received.\n * Resets to false on connection close.\n */\n identified = false;\n\n /**\n * Terminal connection error, if the server closed the socket with a code\n * that should not be retried automatically.\n */\n connectionError: AgentConnectionError | null = null;\n\n /**\n * Promise that resolves when identity has been received from the server.\n * Useful for waiting before making calls that depend on knowing the instance.\n * Resets on connection close so it can be awaited again after reconnect.\n */\n get ready(): Promise<void> {\n return this._readyPromise;\n }\n\n private options: AgentClientOptions<State>;\n private _pendingCalls = new Map<\n string,\n {\n resolve: (value: unknown) => void;\n reject: (error: Error) => void;\n stream?: StreamOptions;\n /**\n * Whether the request was actually transmitted to the server.\n * Requests issued while disconnected sit in PartySocket's internal\n * buffer (transmitted: false) and are flushed on the next open —\n * they survive a transient close instead of being rejected, since\n * the server never saw them and re-delivery can't double-execute.\n */\n transmitted: boolean;\n }\n >();\n private _readyPromise!: Promise<void>;\n private _resolveReady!: () => void;\n private _previousName: string | null = null;\n private _previousAgent: string | null = null;\n\n private _resetReady() {\n this._readyPromise = new Promise((resolve) => {\n this._resolveReady = resolve;\n });\n }\n\n constructor(options: AgentClientOptions<State>) {\n const agentNamespace = camelCaseToKebabCase(options.agent);\n const shouldReconnectOnClose = options.shouldReconnectOnClose;\n const classifyReconnect = (event: CloseEvent) =>\n (shouldReconnectOnClose?.(event) ?? true) && !isTerminalCloseEvent(event);\n\n // If basePath is provided, use it directly; otherwise construct from agent/name\n const socketOptions = options.basePath\n ? {\n basePath: options.basePath,\n path: options.path,\n ...options,\n shouldReconnectOnClose: classifyReconnect\n }\n : {\n party: agentNamespace,\n prefix: \"agents\",\n room: options.name || \"default\",\n path: options.path,\n ...options,\n shouldReconnectOnClose: classifyReconnect\n };\n\n super(socketOptions);\n this.agent = agentNamespace;\n this.name = options.name || \"default\";\n this.options = options;\n\n // Initialize ready promise\n this._resetReady();\n\n this.addEventListener(\"message\", (event) => {\n if (typeof event.data === \"string\") {\n let parsedMessage: Record<string, unknown>;\n try {\n parsedMessage = JSON.parse(event.data);\n } catch (_error) {\n // silently ignore invalid messages for now\n // TODO: log errors with log levels\n return;\n }\n if (parsedMessage.type === MessageType.CF_AGENT_IDENTITY) {\n const oldName = this._previousName;\n const oldAgent = this._previousAgent;\n const newName = parsedMessage.name as string;\n const newAgent = parsedMessage.agent as string;\n\n // Resolve ready/identified\n this.identified = true;\n this._resolveReady();\n\n // Detect identity change on reconnect\n if (\n oldName !== null &&\n oldAgent !== null &&\n (oldName !== newName || oldAgent !== newAgent)\n ) {\n if (this.options.onIdentityChange) {\n this.options.onIdentityChange(\n oldName,\n newName,\n oldAgent,\n newAgent\n );\n } else {\n const agentChanged = oldAgent !== newAgent;\n const nameChanged = oldName !== newName;\n let changeDescription = \"\";\n if (agentChanged && nameChanged) {\n changeDescription = `agent \"${oldAgent}\" → \"${newAgent}\", instance \"${oldName}\" → \"${newName}\"`;\n } else if (agentChanged) {\n changeDescription = `agent \"${oldAgent}\" → \"${newAgent}\"`;\n } else {\n changeDescription = `instance \"${oldName}\" → \"${newName}\"`;\n }\n console.warn(\n `[agents] Identity changed on reconnect: ${changeDescription}. ` +\n \"This can happen with server-side routing (e.g., basePath with getAgentByName) \" +\n \"where the instance is determined by auth/session. \" +\n \"Provide onIdentityChange callback to handle this explicitly, \" +\n \"or ignore if this is expected for your routing pattern.\"\n );\n }\n }\n\n // Always update from server identity (server is authoritative)\n this._previousName = newName;\n this._previousAgent = newAgent;\n this.name = newName;\n this.agent = newAgent;\n\n // Call onIdentity callback\n this.options.onIdentity?.(newName, newAgent);\n return;\n }\n if (parsedMessage.type === MessageType.CF_AGENT_STATE) {\n this.state = parsedMessage.state as State;\n this.options.onStateUpdate?.(parsedMessage.state as State, \"server\");\n return;\n }\n if (parsedMessage.type === MessageType.CF_AGENT_STATE_ERROR) {\n this.options.onStateUpdateError?.(parsedMessage.error as string);\n return;\n }\n if (parsedMessage.type === MessageType.RPC) {\n const response = parsedMessage as RPCResponse;\n const pending = this._pendingCalls.get(response.id);\n if (!pending) {\n console.warn(\n `[AgentClient] Discarded an RPC response with no matching pending call (id \"${response.id}\"). ` +\n \"The call likely timed out or was rejected when its connection closed before the response arrived.\"\n );\n return;\n }\n\n if (!response.success) {\n pending.reject(new Error(response.error));\n this._pendingCalls.delete(response.id);\n pending.stream?.onError?.(response.error);\n return;\n }\n\n // Handle streaming responses\n if (\"done\" in response) {\n if (response.done) {\n pending.resolve(response.result);\n this._pendingCalls.delete(response.id);\n pending.stream?.onDone?.(response.result);\n } else {\n pending.stream?.onChunk?.(response.result);\n }\n } else {\n // Non-streaming response\n pending.resolve(response.result);\n this._pendingCalls.delete(response.id);\n }\n }\n }\n });\n\n // PartySocket flushes its internal message buffer right before\n // dispatching \"open\" — anything queued has now been transmitted.\n this.addEventListener(\"open\", () => {\n this.connectionError = null;\n for (const pending of this._pendingCalls.values()) {\n pending.transmitted = true;\n }\n });\n\n // Clean up pending calls and reset ready state when connection closes\n this.addEventListener(\"close\", (event) => {\n const terminalClose = isTerminalCloseEvent(event);\n // Reset ready state for next connection\n this.identified = false;\n this._resetReady();\n\n if (this.shouldReconnect) {\n // Transient disconnect: reject calls whose request was already\n // transmitted — their response can never arrive. Buffered calls\n // stay pending; PartySocket re-sends them on reconnect.\n this._rejectPendingCalls(\"Connection closed\", {\n onlyTransmitted: true\n });\n } else {\n // Permanent close (close() called or retries exhausted): nothing\n // will ever flush the buffer, so reject everything.\n this._rejectPendingCalls(\"Connection closed\");\n if (terminalClose) {\n const error = new AgentConnectionError(event);\n this.connectionError = error;\n this.options.onConnectionError?.(error);\n }\n }\n });\n\n this.call = this._callImpl.bind(this) as AgentClientCall<AgentT>;\n this.stub = createStubProxy((method, args) =>\n this._callImpl(method, args)\n ) as AgentClientStub<AgentT>;\n }\n\n /**\n * Reject pending RPC calls with the given reason.\n * With `onlyTransmitted`, calls still sitting in the send buffer are\n * kept pending (they'll be flushed on reconnect).\n */\n private _rejectPendingCalls(\n reason: string,\n { onlyTransmitted = false }: { onlyTransmitted?: boolean } = {}\n ) {\n const error = new Error(reason);\n for (const [id, pending] of this._pendingCalls) {\n if (onlyTransmitted && !pending.transmitted) continue;\n this._pendingCalls.delete(id);\n pending.reject(error);\n pending.stream?.onError?.(reason);\n }\n }\n\n setState(state: State) {\n this.send(JSON.stringify({ state, type: MessageType.CF_AGENT_STATE }));\n this.state = state;\n this.options.onStateUpdate?.(state, \"client\");\n }\n\n /**\n * Close the connection and immediately reject all pending RPC calls.\n * This provides immediate feedback on intentional close rather than\n * waiting for the WebSocket close handshake to complete.\n *\n * Note: Any calls made after `close()` will be rejected when the\n * underlying WebSocket close event fires.\n */\n close(code?: number, reason?: string) {\n // Immediately reject all pending calls on intentional close\n this._rejectPendingCalls(\"Connection closed\");\n\n // Then close the underlying socket\n super.close(code, reason);\n }\n\n /**\n * Call a method on the Agent.\n * When AgentT is provided, method names are inferred from the agent's methods.\n * Falls back to untyped string-based calls when AgentT is not provided.\n */\n private async _callImpl(\n method: string,\n args: unknown[] = [],\n options?: CallOptions | StreamOptions\n ): Promise<unknown> {\n if (this.connectionError && this.readyState === this.CLOSED) {\n throw new Error(\"Connection closed\");\n }\n\n return new Promise((resolve, reject) => {\n const id = crypto.randomUUID();\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n // Detect legacy format: { onChunk?, onDone?, onError? } vs new format: { timeout?, stream? }\n const isLegacyFormat =\n options &&\n (\"onChunk\" in options || \"onDone\" in options || \"onError\" in options);\n const streamOptions = isLegacyFormat\n ? (options as StreamOptions)\n : (options as CallOptions | undefined)?.stream;\n const timeout = isLegacyFormat\n ? undefined\n : (options as CallOptions | undefined)?.timeout;\n\n // Apply the default timeout as a backstop for non-streaming calls\n // so a lost response rejects instead of hanging forever. An\n // explicit `timeout` (including 0 = disabled) always wins.\n const effectiveTimeout =\n timeout !== undefined\n ? timeout\n : streamOptions\n ? undefined\n : (this.options.defaultCallTimeout ?? DEFAULT_CALL_TIMEOUT_MS);\n\n if (effectiveTimeout) {\n timeoutId = setTimeout(() => {\n const pending = this._pendingCalls.get(id);\n this._pendingCalls.delete(id);\n const errorMessage = `RPC call to ${method} timed out after ${effectiveTimeout}ms`;\n pending?.stream?.onError?.(errorMessage);\n reject(new Error(errorMessage));\n }, effectiveTimeout);\n }\n\n this._pendingCalls.set(id, {\n reject: (e: Error) => {\n if (timeoutId) clearTimeout(timeoutId);\n reject(e);\n },\n resolve: (value: unknown) => {\n if (timeoutId) clearTimeout(timeoutId);\n resolve(value);\n },\n stream: streamOptions,\n // If the socket is open, send() below transmits synchronously;\n // otherwise the request is buffered until the next open event\n // (the \"open\" listener then marks it transmitted).\n transmitted: this.readyState === this.OPEN\n });\n\n const request: RPCRequest = {\n args,\n id,\n method,\n type: MessageType.RPC\n };\n\n this.send(JSON.stringify(request));\n });\n }\n}\n\n/**\n * Make an HTTP request to an Agent\n * @param opts Connection options\n * @param init Request initialization options\n * @returns Promise resolving to a Response\n */\nexport function agentFetch(opts: AgentClientFetchOptions, init?: RequestInit) {\n const agentNamespace = camelCaseToKebabCase(opts.agent);\n\n // If basePath is provided, use it directly; otherwise construct from agent/name\n // When basePath is set, room/party aren't used by PartySocket (basePath replaces the URL)\n if (opts.basePath) {\n return PartySocket.fetch(\n { basePath: opts.basePath, ...opts } as unknown as PartyFetchOptions,\n init\n );\n }\n\n return PartySocket.fetch(\n {\n party: agentNamespace,\n prefix: \"agents\",\n room: opts.name || \"default\",\n ...opts\n },\n init\n );\n}\n"],"mappings":"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"}
|