agents 0.20.0 → 0.21.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.
Files changed (87) hide show
  1. package/README.md +50 -12
  2. package/dist/{agent-tool-types-Btk9ETS-.d.ts → agent-tool-types-CzGGB-20.d.ts} +403 -115
  3. package/dist/agent-tool-types.d.ts +1 -1
  4. package/dist/{agent-tools-UuScsJg3.d.ts → agent-tools-zR2d5uij.d.ts} +2 -2
  5. package/dist/agent-tools.d.ts +24 -8
  6. package/dist/agent-tools.js +14 -6
  7. package/dist/agent-tools.js.map +1 -1
  8. package/dist/browser/ai.d.ts +5 -3
  9. package/dist/browser/ai.js +86 -7
  10. package/dist/browser/ai.js.map +1 -1
  11. package/dist/browser/index.d.ts +1 -1
  12. package/dist/browser/index.js +1 -1
  13. package/dist/browser/tanstack-ai.js +13 -1
  14. package/dist/browser/tanstack-ai.js.map +1 -1
  15. package/dist/chat/index.d.ts +31 -2
  16. package/dist/chat/index.js +57 -2
  17. package/dist/chat/index.js.map +1 -1
  18. package/dist/chat/react.d.ts +5 -179
  19. package/dist/chat/react.js +18 -555
  20. package/dist/chat/react.js.map +1 -1
  21. package/dist/chat/transport.d.ts +10 -0
  22. package/dist/chat/transport.js +2 -0
  23. package/dist/chat-sdk/index.d.ts +1 -1
  24. package/dist/client.d.ts +1 -1
  25. package/dist/cloudflare-BduZwmYK.js +204 -0
  26. package/dist/cloudflare-BduZwmYK.js.map +1 -0
  27. package/dist/{connector-v2M1zlZp.d.ts → connector-CkQD4MK3.d.ts} +20 -3
  28. package/dist/{connector-KEJnl6e5.js → connector-CptFKzRh.js} +158 -40
  29. package/dist/connector-CptFKzRh.js.map +1 -0
  30. package/dist/{handler-stateless-8hQN_kC3.js → handler-stateless-CIkKPETH.js} +20 -69
  31. package/dist/handler-stateless-CIkKPETH.js.map +1 -0
  32. package/dist/index.d.ts +21 -11
  33. package/dist/index.js +31 -21
  34. package/dist/index.js.map +1 -1
  35. package/dist/mcp/client.d.ts +20 -20
  36. package/dist/mcp/index.d.ts +35 -35
  37. package/dist/mcp/index.js +32 -98
  38. package/dist/mcp/index.js.map +1 -1
  39. package/dist/mcp/server.js +1 -1
  40. package/dist/observability/ai/index.d.ts +10 -131
  41. package/dist/observability/ai/index.js +209 -444
  42. package/dist/observability/ai/index.js.map +1 -1
  43. package/dist/{wire-types-CU9rLoeS.js → protocol-Dqc2MQxo.js} +2 -46
  44. package/dist/protocol-Dqc2MQxo.js.map +1 -0
  45. package/dist/react.d.ts +1 -1
  46. package/dist/react.js +5 -8
  47. package/dist/react.js.map +1 -1
  48. package/dist/serializable.d.ts +1 -1
  49. package/dist/sub-routing.d.ts +18 -6
  50. package/dist/sub-routing.js +92 -2
  51. package/dist/sub-routing.js.map +1 -1
  52. package/dist/wire-types-CnMt6_HR.js +47 -0
  53. package/dist/wire-types-CnMt6_HR.js.map +1 -0
  54. package/dist/workflow-types.d.ts +25 -25
  55. package/dist/workflow-types.js.map +1 -1
  56. package/dist/workflows.d.ts +22 -22
  57. package/dist/ws-chat-transport-CIoOBbO7.js +561 -0
  58. package/dist/ws-chat-transport-CIoOBbO7.js.map +1 -0
  59. package/dist/ws-chat-transport-UNRIS2xl.d.ts +184 -0
  60. package/docs/adding-to-existing-project.md +4 -2
  61. package/docs/agent-class.md +1 -1
  62. package/docs/agent-tools.md +29 -0
  63. package/docs/browse-the-web.md +16 -1
  64. package/docs/chat-agents.md +3 -1
  65. package/docs/client-sdk.md +12 -8
  66. package/docs/configuration.md +7 -1
  67. package/docs/cross-domain-authentication.md +7 -35
  68. package/docs/email.md +2 -13
  69. package/docs/human-in-the-loop.md +15 -12
  70. package/docs/long-running-agents.md +11 -11
  71. package/docs/mcp-client.md +1 -1
  72. package/docs/mcp-servers.md +7 -1
  73. package/docs/mcp-transports.md +18 -12
  74. package/docs/migration-to-ai-sdk-v5.md +2 -2
  75. package/docs/migration-to-ai-sdk-v6.md +5 -1
  76. package/docs/observability.md +24 -67
  77. package/docs/routing.md +27 -0
  78. package/docs/sub-agents.md +47 -2
  79. package/docs/webhooks.md +109 -136
  80. package/docs/workflows.md +9 -1
  81. package/package.json +19 -10
  82. package/dist/cloudflare-BldFV0Pa.js +0 -117
  83. package/dist/cloudflare-BldFV0Pa.js.map +0 -1
  84. package/dist/connector-KEJnl6e5.js.map +0 -1
  85. package/dist/handler-stateless-8hQN_kC3.js.map +0 -1
  86. package/dist/wire-types-CU9rLoeS.js.map +0 -1
  87. package/dist/workflow-types-Baz_PO5v.d.ts +0 -280
@@ -1,560 +1,10 @@
1
- import { i as transition, r as STREAM_RESUME_NONE_REASONS } from "../wire-types-CU9rLoeS.js";
1
+ import { n as STREAM_RESUME_NONE_REASONS, r as transition } from "../protocol-Dqc2MQxo.js";
2
+ import "../wire-types-CnMt6_HR.js";
3
+ import { t as WebSocketChatTransport } from "../ws-chat-transport-CIoOBbO7.js";
2
4
  import { getToolName, isToolUIPart } from "ai";
3
5
  import { nanoid } from "nanoid";
4
6
  import { use, useCallback, useEffect, useMemo, useRef, useState } from "react";
5
7
  import { useChat } from "@ai-sdk/react";
6
- //#region src/chat/ws-chat-transport.ts
7
- /**
8
- * Short safety-net timeout for a resume probe when the server has said nothing.
9
- * Under normal operation the server answers a `STREAM_RESUME_REQUEST` with
10
- * `STREAM_RESUMING`, `STREAM_RESUME_NONE`, or `STREAM_PENDING` well before this.
11
- */
12
- const RESUME_PROBE_TIMEOUT_MS = 5e3;
13
- /**
14
- * Extended backstop applied once the server says a turn is pending
15
- * (`STREAM_PENDING`, #1784). The pre-stream window (queueing, MCP setup,
16
- * debounce, model latency) can exceed the short probe timeout, and the server
17
- * guarantees a follow-up `STREAM_RESUMING` or `STREAM_RESUME_NONE` — so we wait
18
- * much longer (refreshed on every keep-waiting frame) but still cap it so a
19
- * dropped follow-up degrades to a null resolve instead of hanging forever.
20
- */
21
- const RESUME_PENDING_TIMEOUT_MS = 6e4;
22
- /**
23
- * ChatTransport that sends messages over WebSocket and returns a
24
- * ReadableStream<UIMessageChunk> that the AI SDK's useChat consumes directly.
25
- * No fake fetch, no Response reconstruction, no double SSE parsing.
26
- */
27
- var WebSocketChatTransport = class {
28
- constructor(options) {
29
- this._resumeResolver = null;
30
- this._resumeNoneResolver = null;
31
- this._onStreamPending = null;
32
- this._retryResumeProbe = null;
33
- this._expectToolContinuation = false;
34
- this._abortToolContinuation = null;
35
- this._activeServerTurnId = null;
36
- this._cancelAttachedStream = null;
37
- this._detachResumeStream = null;
38
- this.agent = options.agent;
39
- this.prepareBody = options.prepareBody;
40
- this.activeRequestIds = options.activeRequestIds;
41
- this.cancelOnClientAbort = options.cancelOnClientAbort ?? false;
42
- }
43
- /**
44
- * Point the singleton transport at a new Agent connection. A pending resolver
45
- * belongs to the old Chat/socket generation and must settle before messages
46
- * from the replacement connection can be consumed (#1914 review).
47
- */
48
- setAgent(agent) {
49
- if (this.agent === agent) return;
50
- this.resetResumeState();
51
- this.agent = agent;
52
- }
53
- setCancelOnClientAbort(cancelOnClientAbort) {
54
- this.cancelOnClientAbort = cancelOnClientAbort;
55
- }
56
- /**
57
- * Explicitly cancel the active server turn, if any.
58
- * This is separate from generic client-side abort/cancel lifecycle so
59
- * clients can detach locally without stopping server work.
60
- */
61
- cancelActiveServerTurn() {
62
- const requestId = this._activeServerTurnId;
63
- let cancelledRequest = false;
64
- if (requestId) {
65
- this.sendCancelFrame(requestId);
66
- this._cancelAttachedStream?.();
67
- this.clearActiveServerTurn(requestId);
68
- cancelledRequest = true;
69
- }
70
- const cancelledToolContinuation = this.abortActiveToolContinuation();
71
- return cancelledRequest || cancelledToolContinuation;
72
- }
73
- sendCancelFrame(requestId) {
74
- try {
75
- this.agent.send(JSON.stringify({
76
- id: requestId,
77
- type: "cf_agent_chat_request_cancel"
78
- }));
79
- } catch {}
80
- }
81
- setActiveServerTurn(requestId, cancelAttachedStream) {
82
- this._activeServerTurnId = requestId;
83
- this._cancelAttachedStream = cancelAttachedStream;
84
- }
85
- clearActiveServerTurn(requestId) {
86
- if (this._activeServerTurnId === requestId) {
87
- this._activeServerTurnId = null;
88
- this._cancelAttachedStream = null;
89
- }
90
- }
91
- /**
92
- * Mark that the next reconnectToStream() call should attach to a
93
- * server-initiated tool continuation rather than a page-load resume.
94
- */
95
- expectToolContinuation() {
96
- this._expectToolContinuation = true;
97
- }
98
- /**
99
- * Abort the active client-side tool continuation stream, if one is attached
100
- * to a server request id.
101
- */
102
- abortActiveToolContinuation() {
103
- return this._abortToolContinuation?.() ?? false;
104
- }
105
- /**
106
- * True when the transport is waiting for a resume handshake.
107
- */
108
- isAwaitingResume() {
109
- return this._resumeResolver !== null || this._resumeNoneResolver !== null;
110
- }
111
- /**
112
- * Settle and detach the current handshake without interpreting it as a
113
- * server-idle response. Used when the owning hook/agent generation changes.
114
- */
115
- cancelPendingResume() {
116
- const resolveNone = this._resumeNoneResolver;
117
- if (!resolveNone) return false;
118
- return resolveNone({});
119
- }
120
- /**
121
- * Invalidate all client-side resume state for an obsolete hook/agent
122
- * generation without cancelling its durable server turn.
123
- */
124
- resetResumeState() {
125
- this._expectToolContinuation = false;
126
- this.cancelPendingResume();
127
- this._detachResumeStream?.();
128
- }
129
- /**
130
- * Re-send the active handshake request on the latest socket generation. This
131
- * preserves one AI SDK resume operation while recovering a request/reply lost
132
- * with the previous WebSocket.
133
- */
134
- retryPendingResume() {
135
- const retry = this._retryResumeProbe;
136
- if (!retry) return false;
137
- retry();
138
- return true;
139
- }
140
- /**
141
- * Called by onAgentMessage when it receives CF_AGENT_STREAM_RESUMING.
142
- * If reconnectToStream is waiting, this handles the resume handshake
143
- * (ACK + stream creation) and returns true. Otherwise returns false
144
- * so the caller can use its own fallback path.
145
- */
146
- handleStreamResuming(data) {
147
- if (!this._resumeResolver) return false;
148
- this._resumeResolver(data);
149
- return true;
150
- }
151
- /**
152
- * Called by onAgentMessage when it receives CF_AGENT_STREAM_RESUME_NONE.
153
- * If reconnectToStream is waiting, resolves the promise with null
154
- * immediately (no 5-second timeout). Returns true if handled.
155
- */
156
- handleStreamResumeNone(data = {}) {
157
- if (!this._resumeNoneResolver) return false;
158
- return this._resumeNoneResolver(data);
159
- }
160
- /**
161
- * Called by onAgentMessage when it receives CF_AGENT_STREAM_PENDING (#1784):
162
- * the server accepted a turn but its stream has not started yet. If a resume
163
- * path is awaiting, extend its probe timeout (so it keeps waiting for the
164
- * eventual STREAM_RESUMING / STREAM_RESUME_NONE instead of resolving null
165
- * after the short window). Returns true if a waiting path consumed it.
166
- */
167
- handleStreamPending() {
168
- if (!this._onStreamPending) return false;
169
- this._onStreamPending();
170
- return true;
171
- }
172
- /**
173
- * Called by the hook's shared message handler when a server turn finishes
174
- * outside the currently attached transport stream, such as after local-only
175
- * client cleanup.
176
- */
177
- handleServerTurnCompleted(requestId) {
178
- this.clearActiveServerTurn(requestId);
179
- }
180
- /**
181
- * Register a server turn that is being rendered outside a transport-owned
182
- * stream, such as the hook's fallback cross-tab/resume observer path.
183
- */
184
- observeServerTurn(requestId) {
185
- this.setActiveServerTurn(requestId, null);
186
- }
187
- async sendMessages(options) {
188
- const requestId = nanoid(8);
189
- const abortController = new AbortController();
190
- let completed = false;
191
- let requestSent = false;
192
- let extraBody = {};
193
- if (this.prepareBody) extraBody = await this.prepareBody({
194
- messages: options.messages,
195
- trigger: options.trigger,
196
- messageId: options.messageId
197
- });
198
- if (options.body) extraBody = {
199
- ...extraBody,
200
- ...options.body
201
- };
202
- const bodyPayload = JSON.stringify({
203
- messages: options.messages,
204
- trigger: options.trigger,
205
- ...extraBody
206
- });
207
- this.activeRequestIds?.add(requestId);
208
- const agent = this.agent;
209
- const activeIds = this.activeRequestIds;
210
- const finish = (action, keepId = false, clearServerTurn = true) => {
211
- if (completed) return;
212
- completed = true;
213
- if (clearServerTurn) this.clearActiveServerTurn(requestId);
214
- try {
215
- action();
216
- } catch {}
217
- if (!keepId) activeIds?.delete(requestId);
218
- abortController.abort();
219
- };
220
- const abortError = /* @__PURE__ */ new Error("Aborted");
221
- abortError.name = "AbortError";
222
- const cancelActiveRequest = () => {
223
- if (completed) return false;
224
- finish(() => streamController.error(abortError), true);
225
- return true;
226
- };
227
- this.setActiveServerTurn(requestId, cancelActiveRequest);
228
- const onAbort = () => {
229
- if (completed) return;
230
- if (this.cancelOnClientAbort) {
231
- if (requestSent) this.sendCancelFrame(requestId);
232
- finish(() => streamController.error(abortError), requestSent);
233
- } else finish(() => streamController.error(abortError), false, !requestSent);
234
- };
235
- let streamController;
236
- const stream = new ReadableStream({
237
- start(controller) {
238
- streamController = controller;
239
- const onMessage = (event) => {
240
- try {
241
- const data = JSON.parse(event.data);
242
- if (data.type !== "cf_agent_use_chat_response") return;
243
- if (data.id !== requestId) return;
244
- if (data.error) {
245
- finish(() => controller.error(new Error(data.body || "Stream error")));
246
- return;
247
- }
248
- if (data.body?.trim()) try {
249
- const chunk = JSON.parse(data.body);
250
- controller.enqueue(chunk);
251
- } catch {}
252
- if (data.done) finish(() => controller.close());
253
- } catch {}
254
- };
255
- const onClose = () => {
256
- finish(() => controller.close(), false, false);
257
- };
258
- agent.addEventListener("message", onMessage, { signal: abortController.signal });
259
- agent.addEventListener("close", onClose, { signal: abortController.signal });
260
- },
261
- cancel() {
262
- onAbort();
263
- }
264
- });
265
- if (options.abortSignal) {
266
- options.abortSignal.addEventListener("abort", onAbort, { once: true });
267
- if (options.abortSignal.aborted) onAbort();
268
- }
269
- if (completed) return stream;
270
- requestSent = true;
271
- agent.send(JSON.stringify({
272
- id: requestId,
273
- init: {
274
- method: "POST",
275
- body: bodyPayload
276
- },
277
- type: "cf_agent_use_chat_request"
278
- }));
279
- return stream;
280
- }
281
- async reconnectToStream(_options) {
282
- if (this.isAwaitingResume()) return null;
283
- if (this._expectToolContinuation) {
284
- this._expectToolContinuation = false;
285
- return this._createToolContinuationStream();
286
- }
287
- const activeIds = this.activeRequestIds;
288
- const probeId = nanoid(8);
289
- return new Promise((resolve) => {
290
- let resolved = false;
291
- let timeout;
292
- let resumeResolver = null;
293
- let resumeNoneResolver = null;
294
- let onStreamPending = null;
295
- let retryResumeProbe = null;
296
- const clearOwnedCallbacks = () => {
297
- if (resumeResolver && this._resumeResolver === resumeResolver) this._resumeResolver = null;
298
- if (resumeNoneResolver && this._resumeNoneResolver === resumeNoneResolver) this._resumeNoneResolver = null;
299
- if (onStreamPending && this._onStreamPending === onStreamPending) this._onStreamPending = null;
300
- if (retryResumeProbe && this._retryResumeProbe === retryResumeProbe) this._retryResumeProbe = null;
301
- };
302
- const done = (value) => {
303
- if (resolved) return;
304
- resolved = true;
305
- clearOwnedCallbacks();
306
- if (timeout) clearTimeout(timeout);
307
- resolve(value);
308
- };
309
- const armTimeout = (delay) => {
310
- if (timeout) clearTimeout(timeout);
311
- timeout = setTimeout(() => done(null), delay);
312
- };
313
- onStreamPending = () => {
314
- if (resolved) return;
315
- armTimeout(RESUME_PENDING_TIMEOUT_MS);
316
- };
317
- resumeNoneResolver = (data) => {
318
- if (data.probeId && data.probeId !== probeId) return false;
319
- done(null);
320
- return true;
321
- };
322
- resumeResolver = (data) => {
323
- const requestId = data.id;
324
- activeIds?.add(requestId);
325
- const stream = this._createResumeStream(requestId);
326
- this.agent.send(JSON.stringify({
327
- type: "cf_agent_stream_resume_ack",
328
- id: requestId
329
- }));
330
- done(stream);
331
- };
332
- retryResumeProbe = () => {
333
- if (resolved) return;
334
- armTimeout(RESUME_PROBE_TIMEOUT_MS);
335
- try {
336
- this.agent.send(JSON.stringify({
337
- type: "cf_agent_stream_resume_request",
338
- probeId
339
- }));
340
- } catch {}
341
- };
342
- this._onStreamPending = onStreamPending;
343
- this._resumeNoneResolver = resumeNoneResolver;
344
- this._resumeResolver = resumeResolver;
345
- this._retryResumeProbe = retryResumeProbe;
346
- retryResumeProbe();
347
- });
348
- }
349
- /**
350
- * Creates a deferred ReadableStream for client-side tool continuations.
351
- * The stream is returned immediately so AI SDK status becomes "submitted"
352
- * right after addToolOutput()/addToolApprovalResponse(), then it waits for
353
- * the server to announce the continuation via STREAM_RESUMING.
354
- */
355
- _createToolContinuationStream() {
356
- const agent = this.agent;
357
- const activeIds = this.activeRequestIds;
358
- const streamController = new AbortController();
359
- const abortError = /* @__PURE__ */ new Error("Aborted");
360
- abortError.name = "AbortError";
361
- let completed = false;
362
- let requestId = null;
363
- let readerController = null;
364
- const probeId = nanoid(8);
365
- let onResumeRef = null;
366
- let onResumeNoneRef = null;
367
- let onStreamPendingRef = null;
368
- let retryResumeProbeRef = null;
369
- let abortToolContinuationRef = null;
370
- let detachResumeStreamRef = null;
371
- const clearOwnedHandshake = () => {
372
- if (onResumeRef && this._resumeResolver === onResumeRef) this._resumeResolver = null;
373
- if (onResumeNoneRef && this._resumeNoneResolver === onResumeNoneRef) this._resumeNoneResolver = null;
374
- if (onStreamPendingRef && this._onStreamPending === onStreamPendingRef) this._onStreamPending = null;
375
- if (retryResumeProbeRef && this._retryResumeProbe === retryResumeProbeRef) this._retryResumeProbe = null;
376
- };
377
- const finish = (action, keepRequestId = false) => {
378
- if (completed) return;
379
- completed = true;
380
- if (this._abortToolContinuation === abortToolContinuationRef) this._abortToolContinuation = null;
381
- if (this._detachResumeStream === detachResumeStreamRef) this._detachResumeStream = null;
382
- clearOwnedHandshake();
383
- try {
384
- action();
385
- } catch {}
386
- if (requestId && !keepRequestId) activeIds?.delete(requestId);
387
- streamController.abort();
388
- };
389
- const transport = this;
390
- abortToolContinuationRef = () => {
391
- if (completed) return false;
392
- if (requestId === null) {
393
- finish(() => readerController?.error(abortError));
394
- return true;
395
- }
396
- try {
397
- agent.send(JSON.stringify({
398
- type: "cf_agent_chat_request_cancel",
399
- id: requestId
400
- }));
401
- } catch {}
402
- finish(() => readerController?.error(abortError), true);
403
- return true;
404
- };
405
- this._abortToolContinuation = abortToolContinuationRef;
406
- detachResumeStreamRef = () => {
407
- if (completed) return false;
408
- finish(() => readerController?.close());
409
- return true;
410
- };
411
- this._detachResumeStream = detachResumeStreamRef;
412
- return new ReadableStream({
413
- start(controller) {
414
- readerController = controller;
415
- let timeout;
416
- const armTimeout = (delay) => {
417
- if (timeout) clearTimeout(timeout);
418
- timeout = setTimeout(() => finish(() => controller.close()), delay);
419
- };
420
- const onResumeNone = (data) => {
421
- if (data.probeId && data.probeId !== probeId) return false;
422
- finish(() => controller.close());
423
- return true;
424
- };
425
- const onResume = (data) => {
426
- if (requestId) return;
427
- requestId = data.id;
428
- activeIds?.add(requestId);
429
- clearOwnedHandshake();
430
- if (timeout) clearTimeout(timeout);
431
- agent.send(JSON.stringify({
432
- type: "cf_agent_stream_resume_ack",
433
- id: requestId
434
- }));
435
- };
436
- const onStreamPending = () => {
437
- if (completed) return;
438
- armTimeout(RESUME_PENDING_TIMEOUT_MS);
439
- };
440
- const retryResumeProbe = () => {
441
- if (completed || requestId !== null) return;
442
- armTimeout(RESUME_PROBE_TIMEOUT_MS);
443
- try {
444
- transport.agent.send(JSON.stringify({
445
- type: "cf_agent_stream_resume_request",
446
- probeId
447
- }));
448
- } catch {}
449
- };
450
- onResumeRef = onResume;
451
- onResumeNoneRef = onResumeNone;
452
- onStreamPendingRef = onStreamPending;
453
- retryResumeProbeRef = retryResumeProbe;
454
- transport._resumeResolver = onResume;
455
- transport._resumeNoneResolver = onResumeNone;
456
- transport._onStreamPending = onStreamPending;
457
- transport._retryResumeProbe = retryResumeProbe;
458
- const onMessage = (event) => {
459
- try {
460
- const data = JSON.parse(event.data);
461
- if (data.type !== "cf_agent_use_chat_response" || requestId == null || data.id !== requestId) return;
462
- if (data.error) {
463
- finish(() => controller.error(new Error(data.body || "Stream error")));
464
- return;
465
- }
466
- if (data.body?.trim()) try {
467
- const chunk = JSON.parse(data.body);
468
- controller.enqueue(chunk);
469
- } catch {}
470
- if (data.done) finish(() => controller.close());
471
- } catch {}
472
- };
473
- const onClose = () => finish(() => controller.close());
474
- agent.addEventListener("message", onMessage, { signal: streamController.signal });
475
- agent.addEventListener("close", onClose, { signal: streamController.signal });
476
- retryResumeProbe();
477
- },
478
- cancel() {
479
- if (requestId && transport.cancelOnClientAbort) {
480
- transport.sendCancelFrame(requestId);
481
- finish(() => {}, true);
482
- } else finish(() => {});
483
- }
484
- });
485
- }
486
- /**
487
- * Creates a ReadableStream that receives resumed stream chunks
488
- * and forwards them to useChat as UIMessageChunk objects.
489
- */
490
- _createResumeStream(requestId) {
491
- const agent = this.agent;
492
- const activeIds = this.activeRequestIds;
493
- const chunkController = new AbortController();
494
- const abortError = /* @__PURE__ */ new Error("Aborted");
495
- abortError.name = "AbortError";
496
- let completed = false;
497
- let detachResumeStream = null;
498
- const finish = (action, keepId = false, clearServerTurn = true) => {
499
- if (completed) return;
500
- completed = true;
501
- if (clearServerTurn) this.clearActiveServerTurn(requestId);
502
- if (this._detachResumeStream === detachResumeStream) this._detachResumeStream = null;
503
- try {
504
- action();
505
- } catch {}
506
- if (!keepId) activeIds?.delete(requestId);
507
- chunkController.abort();
508
- };
509
- let streamController = null;
510
- const cancelActiveRequest = () => {
511
- if (completed) return false;
512
- finish(() => streamController?.error(abortError), true);
513
- return true;
514
- };
515
- this.setActiveServerTurn(requestId, cancelActiveRequest);
516
- const transport = this;
517
- detachResumeStream = () => {
518
- if (completed) return false;
519
- finish(() => streamController?.close());
520
- return true;
521
- };
522
- this._detachResumeStream = detachResumeStream;
523
- return new ReadableStream({
524
- start(controller) {
525
- streamController = controller;
526
- const onMessage = (event) => {
527
- try {
528
- const data = JSON.parse(event.data);
529
- if (data.type !== "cf_agent_use_chat_response") return;
530
- if (data.id !== requestId) return;
531
- if (data.error) {
532
- finish(() => controller.error(new Error(data.body || "Stream error")));
533
- return;
534
- }
535
- if (data.body?.trim()) try {
536
- const chunk = JSON.parse(data.body);
537
- controller.enqueue(chunk);
538
- } catch {}
539
- if (data.done) finish(() => controller.close());
540
- } catch {}
541
- };
542
- const onClose = () => {
543
- finish(() => controller.close(), false, false);
544
- };
545
- agent.addEventListener("message", onMessage, { signal: chunkController.signal });
546
- agent.addEventListener("close", onClose, { signal: chunkController.signal });
547
- },
548
- cancel() {
549
- if (transport.cancelOnClientAbort) {
550
- transport.sendCancelFrame(requestId);
551
- finish(() => {}, true);
552
- } else finish(() => {}, false, false);
553
- }
554
- });
555
- }
556
- };
557
- //#endregion
558
8
  //#region src/chat/react.tsx
559
9
  /**
560
10
  * One-shot deprecation warnings (warns once per key per session).
@@ -1386,8 +836,8 @@ function useAgentChat(options) {
1386
836
  }
1387
837
  }
1388
838
  } catch {}
1389
- if (data.done || data.replayComplete) pendingReplayResumeRequestIdsRef.current.delete(data.id);
1390
- if (data.done) {
839
+ if (data.done || data.error || data.replayComplete) pendingReplayResumeRequestIdsRef.current.delete(data.id);
840
+ if (data.done || data.error) {
1391
841
  if (streamStateRef.current.status === "observing" && streamStateRef.current.streamId === data.id) {
1392
842
  streamStateRef.current = { status: "idle" };
1393
843
  setIsServerStreaming(false);
@@ -1403,6 +853,19 @@ function useAgentChat(options) {
1403
853
  }
1404
854
  let chunkData;
1405
855
  if (data.replay && streamStateRef.current.status !== "observing" && !pendingReplayResumeRequestIdsRef.current.has(data.id)) return;
856
+ if (data.error) {
857
+ pendingReplayResumeRequestIdsRef.current.delete(data.id);
858
+ customTransport.handleServerTurnCompleted(data.id);
859
+ fallbackAckedResumeRequestIdsRef.current.delete(data.id);
860
+ setIsRecovering(false);
861
+ if (streamStateRef.current.status === "idle" || streamStateRef.current.streamId === data.id) {
862
+ const result = transition(streamStateRef.current, { type: "clear" });
863
+ streamStateRef.current = result.state;
864
+ setIsServerStreaming(result.isStreaming);
865
+ }
866
+ if (observedToolContinuationRequestIdRef.current === data.id) resetToolContinuation();
867
+ break;
868
+ }
1406
869
  if (data.body?.trim()) try {
1407
870
  chunkData = JSON.parse(data.body);
1408
871
  if (data.replay && !data.continuation && !resumingToolContinuationRef.current && observedToolContinuationRequestIdRef.current !== data.id && typeof chunkData.messageId === "string" && chunkData.type === "start") {