agents 0.20.1 → 0.22.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 (169) hide show
  1. package/README.md +51 -12
  2. package/dist/{agent-tool-types-BC-WFlsz.d.ts → agent-routing-CnkaHb-v.d.ts} +3305 -3138
  3. package/dist/agent-routing.d.ts +14 -0
  4. package/dist/agent-routing.js +187 -0
  5. package/dist/agent-routing.js.map +1 -0
  6. package/dist/agent-tool-types.d.ts +26 -26
  7. package/dist/{agent-tools-DeHe9Xov.d.ts → agent-tools-C0XJqWzB.d.ts} +7 -7
  8. package/dist/agent-tools.d.ts +44 -28
  9. package/dist/agent-tools.js +16 -7
  10. package/dist/agent-tools.js.map +1 -1
  11. package/dist/browser/ai.d.ts +5 -3
  12. package/dist/browser/ai.js +88 -8
  13. package/dist/browser/ai.js.map +1 -1
  14. package/dist/browser/index.d.ts +1 -1
  15. package/dist/browser/index.js +1 -1
  16. package/dist/browser/tanstack-ai.js +13 -1
  17. package/dist/browser/tanstack-ai.js.map +1 -1
  18. package/dist/capability-BjSKYpzg.js +42 -0
  19. package/dist/capability-BjSKYpzg.js.map +1 -0
  20. package/dist/capability-runner-CvHGZqUu.d.ts +150 -0
  21. package/dist/chat/index.d.ts +51 -15
  22. package/dist/chat/index.js +59 -4
  23. package/dist/chat/index.js.map +1 -1
  24. package/dist/chat/react.d.ts +19 -180
  25. package/dist/chat/react.js +93 -600
  26. package/dist/chat/react.js.map +1 -1
  27. package/dist/chat/transport.d.ts +10 -0
  28. package/dist/chat/transport.js +2 -0
  29. package/dist/chat-sdk/index.d.ts +4 -4
  30. package/dist/chat-sdk/index.js +1 -1
  31. package/dist/{client-zqKcsyFa.js → client-jagG8a9_.js} +129 -37
  32. package/dist/client-jagG8a9_.js.map +1 -0
  33. package/dist/client.d.ts +1 -1
  34. package/dist/client.js +1 -1
  35. package/dist/{cloudflare-BduZwmYK.js → cloudflare-Dzvc7V2N.js} +10 -3
  36. package/dist/{cloudflare-BduZwmYK.js.map → cloudflare-Dzvc7V2N.js.map} +1 -1
  37. package/dist/{connector-v2M1zlZp.d.ts → connector-CkQD4MK3.d.ts} +20 -3
  38. package/dist/{connector-KEJnl6e5.js → connector-CptFKzRh.js} +158 -40
  39. package/dist/connector-CptFKzRh.js.map +1 -0
  40. package/dist/current-agent-CuMErtly.d.ts +283 -0
  41. package/dist/current-agent-DhoDkSnH.js +51 -0
  42. package/dist/current-agent-DhoDkSnH.js.map +1 -0
  43. package/dist/diagnostics-CaBjfz4J.js +41 -0
  44. package/dist/diagnostics-CaBjfz4J.js.map +1 -0
  45. package/dist/{do-oauth-client-provider-VTZj2VtM.d.ts → do-oauth-client-provider-Tmf1vgKz.d.ts} +2 -2
  46. package/dist/durable-object-lifecycle-D6nNQJJd.js +862 -0
  47. package/dist/durable-object-lifecycle-D6nNQJJd.js.map +1 -0
  48. package/dist/{email-CL27preh.d.ts → email-7TatiTnl.d.ts} +38 -9
  49. package/dist/email-send.d.ts +15 -0
  50. package/dist/email-send.js +32 -0
  51. package/dist/email-send.js.map +1 -0
  52. package/dist/email.d.ts +14 -10
  53. package/dist/email.js.map +1 -1
  54. package/dist/experimental/memory/session/index.d.ts +3 -2
  55. package/dist/experimental/memory/session/index.js +15 -10
  56. package/dist/experimental/memory/session/index.js.map +1 -1
  57. package/dist/{handler-stateless-C_bo-Ytq.d.ts → handler-stateless-DxYpJ_XF.d.ts} +3 -3
  58. package/dist/{handler-stateless-CIkKPETH.js → handler-stateless-VvrWSAVA.js} +5 -5
  59. package/dist/handler-stateless-VvrWSAVA.js.map +1 -0
  60. package/dist/{index-BRnybD6X.d.ts → index-DcSAZKsB.d.ts} +19 -31
  61. package/dist/index.d.ts +102 -91
  62. package/dist/index.js +12 -7235
  63. package/dist/internal_context-BlxFEWfn.d.ts +19 -0
  64. package/dist/internal_context.d.ts +10 -4
  65. package/dist/internal_context.js +1 -10
  66. package/dist/{client-invoker-BNSZxAkv.d.ts → invoker-CG0_p_Wq.d.ts} +2 -2
  67. package/dist/{client-invoker-VNZ7X0nn.js → invoker-CHMnoxIA.js} +2 -2
  68. package/dist/invoker-CHMnoxIA.js.map +1 -0
  69. package/dist/lifecycle/index.d.ts +48 -0
  70. package/dist/lifecycle/index.js +4 -0
  71. package/dist/mcp/{do-oauth-client-provider.d.ts → client/do-oauth-client-provider.d.ts} +1 -1
  72. package/dist/mcp/{do-oauth-client-provider.js → client/do-oauth-client-provider.js} +1 -1
  73. package/dist/mcp/client/do-oauth-client-provider.js.map +1 -0
  74. package/dist/mcp/client/index.d.ts +42 -0
  75. package/dist/mcp/{client.js → client/index.js} +1 -1
  76. package/dist/mcp/{x402.d.ts → client/x402.d.ts} +2 -2
  77. package/dist/mcp/{x402.js → client/x402.js} +2 -2
  78. package/dist/mcp/client/x402.js.map +1 -0
  79. package/dist/mcp/index.d.ts +36 -36
  80. package/dist/mcp/index.js +14 -16
  81. package/dist/mcp/index.js.map +1 -1
  82. package/dist/mcp/{server.d.ts → server/index.d.ts} +1 -1
  83. package/dist/mcp/{server.js → server/index.js} +1 -1
  84. package/dist/observability/ai/index.d.ts +1 -130
  85. package/dist/observability/ai/index.js +88 -433
  86. package/dist/observability/ai/index.js.map +1 -1
  87. package/dist/observability/index.d.ts +4 -4
  88. package/dist/observability/index.js +3 -50
  89. package/dist/observability/index.js.map +1 -1
  90. package/dist/{wire-types-CU9rLoeS.js → protocol-B0nh6KNf.js} +20 -66
  91. package/dist/protocol-B0nh6KNf.js.map +1 -0
  92. package/dist/react.d.ts +4 -4
  93. package/dist/react.js +6 -9
  94. package/dist/react.js.map +1 -1
  95. package/dist/schedule.d.ts +25 -94
  96. package/dist/schedule.js +1 -98
  97. package/dist/schedule.js.map +1 -1
  98. package/dist/scheduler-CR9RHGos.js +857 -0
  99. package/dist/scheduler-CR9RHGos.js.map +1 -0
  100. package/dist/scheduler-DQoTGoAW.d.ts +224 -0
  101. package/dist/schedules/index.d.ts +22 -0
  102. package/dist/schedules/index.js +2 -0
  103. package/dist/schedules/parser.d.ts +79 -0
  104. package/dist/schedules/parser.js +103 -0
  105. package/dist/schedules/parser.js.map +1 -0
  106. package/dist/serializable.d.ts +1 -1
  107. package/dist/sql-error-CPY-GXyI.d.ts +12 -0
  108. package/dist/sql-error.d.ts +2 -0
  109. package/dist/sql-error.js +16 -0
  110. package/dist/sql-error.js.map +1 -0
  111. package/dist/src-5W6JNKVb.js +6613 -0
  112. package/dist/src-5W6JNKVb.js.map +1 -0
  113. package/dist/sub-routing.d.ts +18 -6
  114. package/dist/sub-routing.js +92 -2
  115. package/dist/sub-routing.js.map +1 -1
  116. package/dist/wire-types-CnMt6_HR.js +47 -0
  117. package/dist/wire-types-CnMt6_HR.js.map +1 -0
  118. package/dist/workflow-types.d.ts +25 -25
  119. package/dist/workflow-types.js.map +1 -1
  120. package/dist/workflows.d.ts +22 -22
  121. package/dist/workflows.js +2 -1
  122. package/dist/workflows.js.map +1 -1
  123. package/dist/ws-chat-transport-UNRIS2xl.d.ts +184 -0
  124. package/dist/ws-chat-transport-rWwta645.js +698 -0
  125. package/dist/ws-chat-transport-rWwta645.js.map +1 -0
  126. package/docs/adding-to-existing-project.md +4 -2
  127. package/docs/agent-class.md +29 -87
  128. package/docs/agent-tools.md +29 -0
  129. package/docs/browse-the-web.md +16 -1
  130. package/docs/chat-agents.md +16 -13
  131. package/docs/client-sdk.md +12 -8
  132. package/docs/configuration.md +7 -1
  133. package/docs/cross-domain-authentication.md +7 -35
  134. package/docs/durable-execution.md +1 -1
  135. package/docs/email.md +2 -13
  136. package/docs/http-websockets.md +1 -11
  137. package/docs/human-in-the-loop.md +16 -13
  138. package/docs/index.md +1 -0
  139. package/docs/lifecycle.md +323 -0
  140. package/docs/long-running-agents.md +13 -15
  141. package/docs/mcp-client.md +55 -0
  142. package/docs/mcp-servers.md +6 -0
  143. package/docs/mcp-transports.md +18 -12
  144. package/docs/migration-to-ai-sdk-v5.md +2 -2
  145. package/docs/migration-to-ai-sdk-v6.md +5 -1
  146. package/docs/observability.md +33 -77
  147. package/docs/resumable-streaming.md +1 -1
  148. package/docs/routing.md +27 -0
  149. package/docs/scheduling.md +175 -15
  150. package/docs/server-driven-messages.md +1 -1
  151. package/docs/sub-agents.md +47 -2
  152. package/docs/webhooks.md +109 -136
  153. package/docs/workflows.md +9 -1
  154. package/package.json +41 -23
  155. package/dist/cli/index.d.ts +0 -1
  156. package/dist/cli/index.js +0 -26
  157. package/dist/cli/index.js.map +0 -1
  158. package/dist/client-invoker-VNZ7X0nn.js.map +0 -1
  159. package/dist/client-zqKcsyFa.js.map +0 -1
  160. package/dist/connector-KEJnl6e5.js.map +0 -1
  161. package/dist/handler-stateless-CIkKPETH.js.map +0 -1
  162. package/dist/index.js.map +0 -1
  163. package/dist/internal_context-Dg4Cgjcu.d.ts +0 -37
  164. package/dist/internal_context.js.map +0 -1
  165. package/dist/mcp/client.d.ts +0 -42
  166. package/dist/mcp/do-oauth-client-provider.js.map +0 -1
  167. package/dist/mcp/x402.js.map +0 -1
  168. package/dist/wire-types-CU9rLoeS.js.map +0 -1
  169. package/dist/workflow-types-Baz_PO5v.d.ts +0 -280
@@ -1,559 +1,39 @@
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-B0nh6KNf.js";
2
+ import "../wire-types-CnMt6_HR.js";
3
+ import { t as WebSocketChatTransport } from "../ws-chat-transport-rWwta645.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
  /**
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.
9
+ * Picks the throttle from an explicit caller value, the deprecated alias, or
10
+ * the default in that order. `false` turns throttling off.
11
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;
12
+ function resolveChatThrottleMs(options) {
13
+ if (options.throttle === false) return void 0;
14
+ return options.throttle ?? options.experimental_throttle ?? 50;
15
+ }
22
16
  /**
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.
17
+ * The throttle spelled under both option names, or neither name when it is off.
18
+ *
19
+ * The two names are not interchangeable across the peer range. `@ai-sdk/react`
20
+ * v3 only reads `experimental_throttle`; v4 renamed it to `throttle` and reads
21
+ * `throttle ?? experimental_throttle`. Our peer range allows both majors, so
22
+ * sending one name would silently do nothing on the other. Unknown option keys
23
+ * are ignored by both, so sending both names is safe.
24
+ *
25
+ * Turning throttling off omits both names rather than inventing a numeric
26
+ * value. Both majors also treat an explicit `0` as unthrottled, so callers that
27
+ * pass `0` keep that value under both spellings.
26
28
  */
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
- };
29
+ function chatThrottleOptions(options) {
30
+ const ms = resolveChatThrottleMs(options);
31
+ if (ms === void 0) return {};
32
+ return {
33
+ experimental_throttle: ms,
34
+ throttle: ms
35
+ };
36
+ }
557
37
  //#endregion
558
38
  //#region src/chat/react.tsx
559
39
  /**
@@ -736,7 +216,7 @@ function detectToolsRequiringConfirmation(tools) {
736
216
  return Object.entries(tools).filter(([_name, tool]) => !tool.execute).map(([name]) => name);
737
217
  }
738
218
  function useAgentChat(options) {
739
- const { agent, getInitialMessages, messages: optionsInitialMessages, onToolCall, onData, experimental_automaticToolResolution, tools, toolsRequiringConfirmation: manualToolsRequiringConfirmation, autoContinueAfterToolResult = true, autoSendAfterAllConfirmationsResolved = true, resume = true, cancelOnClientAbort = false, syncMessagesToServer = true, body: bodyOption, prepareSendMessagesRequest, ...rest } = options;
219
+ const { agent, getInitialMessages, messages: optionsInitialMessages, onToolCall, onData, experimental_automaticToolResolution, tools, toolsRequiringConfirmation: manualToolsRequiringConfirmation, autoContinueAfterToolResult = true, autoSendAfterAllConfirmationsResolved = true, resume = true, cancelOnClientAbort = false, syncMessagesToServer = true, body: bodyOption, prepareSendMessagesRequest, throttle, experimental_throttle, ...rest } = options;
740
220
  if (manualToolsRequiringConfirmation) warnDeprecated("useAgentChat.toolsRequiringConfirmation", "The 'toolsRequiringConfirmation' option is deprecated. Use needsApproval on server-side tools instead. Will be removed in the next major version.");
741
221
  if (experimental_automaticToolResolution) warnDeprecated("useAgentChat.experimental_automaticToolResolution", "The 'experimental_automaticToolResolution' option is deprecated. Use the onToolCall callback instead. Will be removed in the next major version.");
742
222
  if (options.autoSendAfterAllConfirmationsResolved !== void 0) warnDeprecated("useAgentChat.autoSendAfterAllConfirmationsResolved", "The 'autoSendAfterAllConfirmationsResolved' option is deprecated. Use sendAutomaticallyWhen from AI SDK instead. Will be removed in the next major version.");
@@ -862,6 +342,10 @@ function useAgentChat(options) {
862
342
  const customTransport = customTransportRef.current;
863
343
  const useChatHelpers = useChat({
864
344
  ...rest,
345
+ ...chatThrottleOptions({
346
+ experimental_throttle,
347
+ throttle
348
+ }),
865
349
  onData,
866
350
  messages: initialMessages,
867
351
  transport: customTransport,
@@ -965,8 +449,6 @@ function useAgentChat(options) {
965
449
  const isResolvingToolsRef = useRef(false);
966
450
  const [toolResolutionTrigger, setToolResolutionTrigger] = useState(0);
967
451
  const [clientToolResults, setClientToolResults] = useState(/* @__PURE__ */ new Map());
968
- const messagesRef = useRef(chatMessages);
969
- messagesRef.current = chatMessages;
970
452
  const initialMessagesRef = useRef(initialMessages);
971
453
  initialMessagesRef.current = initialMessages;
972
454
  const seededInitialMessagesKeyRef = useRef(null);
@@ -986,7 +468,7 @@ function useAgentChat(options) {
986
468
  ]);
987
469
  const localResponseMessageIdsRef = useRef(/* @__PURE__ */ new Map());
988
470
  const protectedStreamingAssistantRef = useRef(null);
989
- const preserveProtectedStreamingAssistant = useCallback((messages) => {
471
+ const preserveProtectedStreamingAssistant = useCallback((messages, currentMessages) => {
990
472
  const protection = protectedStreamingAssistantRef.current;
991
473
  if (!protection) return [...messages];
992
474
  const protectedIndex = messages.findIndex((message) => message.id === protection.assistantId);
@@ -994,22 +476,20 @@ function useAgentChat(options) {
994
476
  protectedStreamingAssistantRef.current = null;
995
477
  return [...messages];
996
478
  }
997
- const protectedAssistant = messagesRef.current.find((message) => message.id === protection.assistantId) ?? messages.find((message) => message.id === protection.assistantId);
479
+ const protectedAssistant = currentMessages.find((message) => message.id === protection.assistantId) ?? messages.find((message) => message.id === protection.assistantId);
998
480
  if (!protectedAssistant) return [...messages];
999
481
  return [...messages.filter((message) => message.id !== protection.assistantId), protectedAssistant];
1000
482
  }, []);
1001
483
  const protectStreamingAssistantTail = useCallback(() => {
1002
484
  if (statusRef.current !== "streaming") return;
1003
- const assistantInfo = findLastAssistantMessage(messagesRef.current);
1004
- if (!assistantInfo) return;
1005
- if (protectedStreamingAssistantRef.current?.assistantId !== assistantInfo.message.id) protectedStreamingAssistantRef.current = {
1006
- assistantId: assistantInfo.message.id,
1007
- anchorMessageId: messagesRef.current[assistantInfo.index - 1]?.id ?? null
1008
- };
1009
- setMessages((prevMessages) => {
1010
- const protection = protectedStreamingAssistantRef.current;
1011
- if (!protection) return prevMessages;
1012
- return moveMessageToEnd(prevMessages, protection.assistantId);
485
+ setMessages((currentMessages) => {
486
+ const assistantInfo = findLastAssistantMessage(currentMessages);
487
+ if (!assistantInfo) return currentMessages;
488
+ if (protectedStreamingAssistantRef.current?.assistantId !== assistantInfo.message.id) protectedStreamingAssistantRef.current = {
489
+ assistantId: assistantInfo.message.id,
490
+ anchorMessageId: currentMessages[assistantInfo.index - 1]?.id ?? null
491
+ };
492
+ return moveMessageToEnd(currentMessages, assistantInfo.message.id);
1013
493
  });
1014
494
  }, [setMessages]);
1015
495
  const restoreProtectedStreamingAssistant = useCallback((assistantId) => {
@@ -1290,17 +770,18 @@ function useAgentChat(options) {
1290
770
  case "cf_agent_chat_recovering":
1291
771
  setIsRecovering(Boolean(data.recovering));
1292
772
  break;
1293
- case "cf_agent_chat_messages": {
1294
- let next = preserveProtectedStreamingAssistant(data.messages);
1295
- const observed = streamStateRef.current;
1296
- if (observed.status === "observing" && observed.accumulator.parts.length > 0) {
1297
- const snapshotIdx = next.findIndex((m) => m.id === observed.accumulator.messageId);
1298
- const snapshotParts = snapshotIdx >= 0 ? next[snapshotIdx].parts.length : 0;
1299
- if (observed.accumulator.parts.length >= snapshotParts) next = observed.accumulator.mergeInto(next);
1300
- }
1301
- setMessages(next);
773
+ case "cf_agent_chat_messages":
774
+ setMessages((currentMessages) => {
775
+ let next = preserveProtectedStreamingAssistant(data.messages, currentMessages);
776
+ const observed = streamStateRef.current;
777
+ if (observed.status === "observing" && observed.accumulator.parts.length > 0) {
778
+ const snapshotIdx = next.findIndex((m) => m.id === observed.accumulator.messageId);
779
+ const snapshotParts = snapshotIdx >= 0 ? next[snapshotIdx].parts.length : 0;
780
+ if (observed.accumulator.parts.length >= snapshotParts) next = observed.accumulator.mergeInto(next);
781
+ }
782
+ return next;
783
+ });
1302
784
  break;
1303
- }
1304
785
  case "cf_agent_message_updated":
1305
786
  setMessages((prevMessages) => {
1306
787
  const updatedMessage = data.message;
@@ -1368,26 +849,26 @@ function useAgentChat(options) {
1368
849
  if (data.body?.trim()) try {
1369
850
  const chunkData = JSON.parse(data.body);
1370
851
  if (chunkData.type === "start" && typeof chunkData.messageId === "string") {
1371
- localResponseIds.set(data.id, chunkData.messageId);
852
+ const messageId = chunkData.messageId;
853
+ localResponseIds.set(data.id, messageId);
1372
854
  if (!data.continuation) {
1373
- if (protectedStreamingAssistantRef.current?.assistantId !== chunkData.messageId) {
1374
- const msgs = messagesRef.current;
1375
- const idx = msgs.findIndex((m) => m.id === chunkData.messageId);
1376
- const anchorMessageId = idx >= 0 ? msgs[idx - 1]?.id ?? null : msgs[msgs.length - 1]?.id ?? null;
855
+ if (protectedStreamingAssistantRef.current?.assistantId !== messageId) setMessages((currentMessages) => {
856
+ const idx = currentMessages.findIndex((message) => message.id === messageId);
1377
857
  protectedStreamingAssistantRef.current = {
1378
- assistantId: chunkData.messageId,
1379
- anchorMessageId
858
+ assistantId: messageId,
859
+ anchorMessageId: idx >= 0 ? currentMessages[idx - 1]?.id ?? null : currentMessages[currentMessages.length - 1]?.id ?? null
1380
860
  };
1381
- }
861
+ return currentMessages;
862
+ });
1382
863
  }
1383
864
  if (data.replay && !data.continuation && !resumingToolContinuationRef.current && observedToolContinuationRequestIdRef.current !== data.id) {
1384
865
  pendingReplayResumeRequestIdsRef.current.delete(data.id);
1385
- resetMatchingHydratedAssistantForReplay(chunkData.messageId);
866
+ resetMatchingHydratedAssistantForReplay(messageId);
1386
867
  }
1387
868
  }
1388
869
  } catch {}
1389
- if (data.done || data.replayComplete) pendingReplayResumeRequestIdsRef.current.delete(data.id);
1390
- if (data.done) {
870
+ if (data.done || data.error || data.replayComplete) pendingReplayResumeRequestIdsRef.current.delete(data.id);
871
+ if (data.done || data.error) {
1391
872
  if (streamStateRef.current.status === "observing" && streamStateRef.current.streamId === data.id) {
1392
873
  streamStateRef.current = { status: "idle" };
1393
874
  setIsServerStreaming(false);
@@ -1403,6 +884,19 @@ function useAgentChat(options) {
1403
884
  }
1404
885
  let chunkData;
1405
886
  if (data.replay && streamStateRef.current.status !== "observing" && !pendingReplayResumeRequestIdsRef.current.has(data.id)) return;
887
+ if (data.error) {
888
+ pendingReplayResumeRequestIdsRef.current.delete(data.id);
889
+ customTransport.handleServerTurnCompleted(data.id);
890
+ fallbackAckedResumeRequestIdsRef.current.delete(data.id);
891
+ setIsRecovering(false);
892
+ if (streamStateRef.current.status === "idle" || streamStateRef.current.streamId === data.id) {
893
+ const result = transition(streamStateRef.current, { type: "clear" });
894
+ streamStateRef.current = result.state;
895
+ setIsServerStreaming(result.isStreaming);
896
+ }
897
+ if (observedToolContinuationRequestIdRef.current === data.id) resetToolContinuation();
898
+ break;
899
+ }
1406
900
  if (data.body?.trim()) try {
1407
901
  chunkData = JSON.parse(data.body);
1408
902
  if (data.replay && !data.continuation && !resumingToolContinuationRef.current && observedToolContinuationRequestIdRef.current !== data.id && typeof chunkData.messageId === "string" && chunkData.type === "start") {
@@ -1429,8 +923,7 @@ function useAgentChat(options) {
1429
923
  error: data.error,
1430
924
  replay: data.replay,
1431
925
  replayComplete: data.replayComplete,
1432
- continuation: data.continuation,
1433
- currentMessages: data.continuation ? messagesRef.current : void 0
926
+ continuation: data.continuation
1434
927
  });
1435
928
  streamStateRef.current = result.state;
1436
929
  if (result.messagesUpdate) setMessages(result.messagesUpdate);
@@ -1550,13 +1043,13 @@ function useAgentChat(options) {
1550
1043
  const addToolApprovalResponseAndNotifyServer = (args) => {
1551
1044
  const { id: approvalId, approved } = args;
1552
1045
  let toolCallId;
1553
- for (const msg of messagesRef.current) {
1554
- for (const part of msg.parts) if ("toolCallId" in part && "approval" in part && part.approval?.id === approvalId) {
1046
+ setMessages((currentMessages) => {
1047
+ for (const msg of currentMessages) for (const part of msg.parts) if ("toolCallId" in part && "approval" in part && part.approval?.id === approvalId) {
1555
1048
  toolCallId = part.toolCallId;
1556
- break;
1049
+ return currentMessages;
1557
1050
  }
1558
- if (toolCallId) break;
1559
- }
1051
+ return currentMessages;
1052
+ });
1560
1053
  if (toolCallId) sendToolApprovalToServer(toolCallId, approved);
1561
1054
  else console.warn(`[useAgentChat] addToolApprovalResponse: Could not find toolCallId for approval ID "${approvalId}". Server will not be notified, which may cause duplicate messages.`);
1562
1055
  addToolApprovalResponse(args);
@@ -1655,15 +1148,15 @@ function useAgentChat(options) {
1655
1148
  agent.send(JSON.stringify({ type: "cf_agent_chat_clear" }));
1656
1149
  },
1657
1150
  setMessages: (messagesOrUpdater) => {
1658
- let resolvedMessages;
1659
- if (typeof messagesOrUpdater === "function") resolvedMessages = messagesOrUpdater(messagesRef.current);
1660
- else resolvedMessages = messagesOrUpdater;
1661
- if (resolvedMessages.length === 0) markInitialMessagesSeeded();
1662
- setMessages(resolvedMessages);
1663
- if (syncMessagesToServer) agent.send(JSON.stringify({
1664
- messages: resolvedMessages,
1665
- type: "cf_agent_chat_messages"
1666
- }));
1151
+ setMessages((currentMessages) => {
1152
+ const resolvedMessages = typeof messagesOrUpdater === "function" ? messagesOrUpdater(currentMessages) : messagesOrUpdater;
1153
+ if (resolvedMessages.length === 0) markInitialMessagesSeeded();
1154
+ if (syncMessagesToServer) agent.send(JSON.stringify({
1155
+ messages: resolvedMessages,
1156
+ type: "cf_agent_chat_messages"
1157
+ }));
1158
+ return resolvedMessages;
1159
+ });
1667
1160
  }
1668
1161
  };
1669
1162
  }