agents 0.23.0 → 0.25.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 (187) hide show
  1. package/dist/{agent-routing-DE5zmCQ8.d.ts → agent-routing-0DR0Tk2v.d.ts} +270 -190
  2. package/dist/agent-routing.d.ts +1 -1
  3. package/dist/agent-routing.js.map +1 -1
  4. package/dist/agent-tool-types.d.ts +13 -7
  5. package/dist/agent-tool-types.js +23 -1
  6. package/dist/agent-tool-types.js.map +1 -1
  7. package/dist/{agent-tools-y7zLfw4Q.js → agent-tools-CdJC-8Dc.js} +72 -9
  8. package/dist/agent-tools-CdJC-8Dc.js.map +1 -0
  9. package/dist/{agent-tools-DtXMTDGM.d.ts → agent-tools-YrATZJu2.d.ts} +29 -15
  10. package/dist/agent-tools.d.ts +6 -4
  11. package/dist/agent-tools.js.map +1 -1
  12. package/dist/ai-fm7xx1BK.js +312 -0
  13. package/dist/ai-fm7xx1BK.js.map +1 -0
  14. package/dist/base64-redaction-C1Uu29t5.js +59 -0
  15. package/dist/base64-redaction-C1Uu29t5.js.map +1 -0
  16. package/dist/browser/ai-sdk.d.ts +90 -0
  17. package/dist/browser/ai-sdk.js +51 -0
  18. package/dist/browser/ai-sdk.js.map +1 -0
  19. package/dist/browser/ai.d.ts +4 -4
  20. package/dist/browser/ai.js +1 -347
  21. package/dist/browser/index.d.ts +81 -79
  22. package/dist/browser/index.js +361 -2
  23. package/dist/browser/index.js.map +1 -0
  24. package/dist/browser/tanstack-ai.d.ts +96 -1
  25. package/dist/browser/tanstack-ai.js +62 -2
  26. package/dist/browser/tanstack-ai.js.map +1 -1
  27. package/dist/browser-DE3d-RLk.d.ts +275 -0
  28. package/dist/browser-tool-CQ10e6Kj.js +480 -0
  29. package/dist/browser-tool-CQ10e6Kj.js.map +1 -0
  30. package/dist/browser-tool-DuNdlwZ9.d.ts +73 -0
  31. package/dist/{capability-BjSKYpzg.js → capability-B4WbF81e.js} +2 -1
  32. package/dist/capability-B4WbF81e.js.map +1 -0
  33. package/dist/{capability-runner-Be_-PLR1.d.ts → capability-runner-DBAnGr6L.d.ts} +45 -15
  34. package/dist/chat/index.d.ts +341 -232
  35. package/dist/chat/index.js +602 -72
  36. package/dist/chat/index.js.map +1 -1
  37. package/dist/chat/react.d.ts +42 -2
  38. package/dist/chat/react.js +191 -35
  39. package/dist/chat/react.js.map +1 -1
  40. package/dist/chat/transport.d.ts +1 -1
  41. package/dist/chat/transport.js +1 -1
  42. package/dist/chat-sdk/index.d.ts +17 -3
  43. package/dist/chat-sdk/index.js +28 -2
  44. package/dist/chat-sdk/index.js.map +1 -1
  45. package/dist/client-4OlMIJx-.js +591 -0
  46. package/dist/client-4OlMIJx-.js.map +1 -0
  47. package/dist/{client-jagG8a9_.js → client-fyQrLGkD.js} +2 -2
  48. package/dist/{client-jagG8a9_.js.map → client-fyQrLGkD.js.map} +1 -1
  49. package/dist/client-je3koD9Z.d.ts +446 -0
  50. package/dist/client.d.ts +29 -283
  51. package/dist/client.js +2 -274
  52. package/dist/{connector-CptFKzRh.js → connector-58XDGvc3.js} +251 -120
  53. package/dist/connector-58XDGvc3.js.map +1 -0
  54. package/dist/{connector-CkQD4MK3.d.ts → connector-Cl-VqPmA.d.ts} +244 -146
  55. package/dist/context/index.d.ts +29 -0
  56. package/dist/context/index.js +64 -17
  57. package/dist/context/index.js.map +1 -1
  58. package/dist/current-agent-DhoDkSnH.js.map +1 -1
  59. package/dist/current-agent-vAk_5JMt.d.ts +93 -0
  60. package/dist/{current-agent-Da_C9a3b.d.ts → durable-object-lifecycle-HhgOMWq9.d.ts} +26 -127
  61. package/dist/harnesses/pi/index.d.ts +348 -0
  62. package/dist/harnesses/pi/index.js +754 -0
  63. package/dist/harnesses/pi/index.js.map +1 -0
  64. package/dist/images-iiTDSyRu.js +1631 -0
  65. package/dist/images-iiTDSyRu.js.map +1 -0
  66. package/dist/index-BreL0YrQ.d.ts +78 -0
  67. package/dist/index-kbSUIer3.d.ts +343 -0
  68. package/dist/index.d.ts +43 -41
  69. package/dist/index.js +2 -2
  70. package/dist/internal_context.d.ts +1 -1
  71. package/dist/lifecycle/index.d.ts +19 -17
  72. package/dist/lifecycle/index.js +2 -2
  73. package/dist/{lifecycle-CMRGjZdw.js → lifecycle-CFu6OV03.js} +73 -29
  74. package/dist/lifecycle-CFu6OV03.js.map +1 -0
  75. package/dist/mcp/client/index.d.ts +20 -20
  76. package/dist/mcp/client/index.js +1 -1
  77. package/dist/mcp/client/x402.js +21 -19
  78. package/dist/mcp/client/x402.js.map +1 -1
  79. package/dist/mcp/index.d.ts +35 -35
  80. package/dist/mcp/index.js +2 -2
  81. package/dist/{message-builder-BymO4N_D.js → message-builder-DAl9AhHH.js} +89 -4
  82. package/dist/message-builder-DAl9AhHH.js.map +1 -0
  83. package/dist/models/ai-sdk/index.d.ts +353 -0
  84. package/dist/models/ai-sdk/index.js +2443 -0
  85. package/dist/models/ai-sdk/index.js.map +1 -0
  86. package/dist/models/pi-ai/index.d.ts +313 -0
  87. package/dist/models/pi-ai/index.js +1795 -0
  88. package/dist/models/pi-ai/index.js.map +1 -0
  89. package/dist/observability/ai/index.js +20 -4
  90. package/dist/observability/ai/index.js.map +1 -1
  91. package/dist/{protocol-B0nh6KNf.js → protocol-kHsr-f0X.js} +26 -12
  92. package/dist/{protocol-B0nh6KNf.js.map → protocol-kHsr-f0X.js.map} +1 -1
  93. package/dist/queue/index.d.ts +123 -0
  94. package/dist/queue/index.js +2 -0
  95. package/dist/queue-BB-zfIYF.js +406 -0
  96. package/dist/queue-BB-zfIYF.js.map +1 -0
  97. package/dist/react.d.ts +31 -13
  98. package/dist/react.js +190 -71
  99. package/dist/react.js.map +1 -1
  100. package/dist/{retries-D9Ds-1lz.d.ts → retries-CV6AkXmQ.d.ts} +23 -8
  101. package/dist/retries.d.ts +12 -8
  102. package/dist/retries.js +19 -1
  103. package/dist/retries.js.map +1 -1
  104. package/dist/routing/index.d.ts +2 -2
  105. package/dist/routing/index.js +1 -1
  106. package/dist/{scheduler-DD9NdYbF.js → scheduler-DZ3xQfeK.js} +4 -11
  107. package/dist/scheduler-DZ3xQfeK.js.map +1 -0
  108. package/dist/{scheduler-Dwh85ZGl.d.ts → scheduler-RdACNXDP.d.ts} +6 -6
  109. package/dist/schedules/index.d.ts +1 -1
  110. package/dist/schedules/index.js +1 -1
  111. package/dist/serializable.d.ts +1 -1
  112. package/dist/sessions/index.d.ts +70 -3
  113. package/dist/sessions/index.js +276 -67
  114. package/dist/sessions/index.js.map +1 -1
  115. package/dist/skills/index.d.ts +29 -314
  116. package/dist/socket-address.d.ts +16 -0
  117. package/dist/socket-address.js +19 -0
  118. package/dist/socket-address.js.map +1 -0
  119. package/dist/{src-DlSHshb2.js → src-BZeu-SCD.js} +541 -500
  120. package/dist/src-BZeu-SCD.js.map +1 -0
  121. package/dist/state/index.d.ts +6 -0
  122. package/dist/state/index.js +137 -0
  123. package/dist/state/index.js.map +1 -0
  124. package/dist/streams/index.d.ts +1 -1
  125. package/dist/streams/index.js +1 -1
  126. package/dist/{streams-D6tJ0NN9.d.ts → streams-Cmkj9QfW.d.ts} +4 -2
  127. package/dist/{streams-DZKgAj9b.js → streams-DW-UCOUc.js} +9 -2
  128. package/dist/streams-DW-UCOUc.js.map +1 -0
  129. package/dist/sub-routing.d.ts +12 -12
  130. package/dist/tasks/index.d.ts +1 -1
  131. package/dist/tasks/index.js +1 -1
  132. package/dist/{tasks-ylZgBjhj.js → tasks-68DESxf0.js} +3 -3
  133. package/dist/{tasks-ylZgBjhj.js.map → tasks-68DESxf0.js.map} +1 -1
  134. package/dist/{tasks-BRJ5zgya.d.ts → tasks-9YxYtByh.d.ts} +5 -5
  135. package/dist/transport-PfFnt-Ld.d.ts +525 -0
  136. package/dist/transport-protocol-BxyngQ_R.js +25 -0
  137. package/dist/transport-protocol-BxyngQ_R.js.map +1 -0
  138. package/dist/transport-protocol-ZTew5Wso.d.ts +27 -0
  139. package/dist/types-BA4anrjI.d.ts +70 -0
  140. package/dist/voice/index.d.ts +2 -2
  141. package/dist/websockets/index.d.ts +195 -69
  142. package/dist/websockets/index.js +3 -2
  143. package/dist/websockets-Ci-LZFHH.js +1030 -0
  144. package/dist/websockets-Ci-LZFHH.js.map +1 -0
  145. package/dist/wire-types-CnMt6_HR.js.map +1 -1
  146. package/dist/wire-types-CrjKvDyi.d.ts +300 -0
  147. package/dist/workflow-types.d.ts +25 -25
  148. package/dist/workflows.d.ts +21 -21
  149. package/dist/workflows.js +1 -1
  150. package/dist/{ws-chat-transport-UNRIS2xl.d.ts → ws-chat-transport-BbT1A5Lc.d.ts} +51 -2
  151. package/dist/{ws-chat-transport-rWwta645.js → ws-chat-transport-cqMKq3IY.js} +143 -16
  152. package/dist/ws-chat-transport-cqMKq3IY.js.map +1 -0
  153. package/docs/agent-class.md +2 -2
  154. package/docs/agent-tools.md +29 -4
  155. package/docs/browse-the-web.md +87 -5
  156. package/docs/chat-agents.md +124 -16
  157. package/docs/client-sdk.md +30 -0
  158. package/docs/context.md +27 -0
  159. package/docs/durable-execution.md +2 -0
  160. package/docs/harnesses/pi.md +91 -0
  161. package/docs/human-in-the-loop.md +4 -0
  162. package/docs/index.md +4 -1
  163. package/docs/lifecycle.md +114 -23
  164. package/docs/models-pi-ai.md +325 -0
  165. package/docs/models.md +504 -0
  166. package/docs/queue.md +135 -241
  167. package/docs/resumable-streaming.md +2 -0
  168. package/docs/retries.md +1 -1
  169. package/docs/routing.md +13 -13
  170. package/docs/sessions.md +36 -1
  171. package/docs/state.md +1 -0
  172. package/docs/sub-agents.md +12 -3
  173. package/package.json +51 -3
  174. package/dist/agent-tools-y7zLfw4Q.js.map +0 -1
  175. package/dist/browser/ai.js.map +0 -1
  176. package/dist/capability-BjSKYpzg.js.map +0 -1
  177. package/dist/client-tools-aIBO0Fk7.d.ts +0 -53
  178. package/dist/client.js.map +0 -1
  179. package/dist/connector-CptFKzRh.js.map +0 -1
  180. package/dist/lifecycle-CMRGjZdw.js.map +0 -1
  181. package/dist/message-builder-BymO4N_D.js.map +0 -1
  182. package/dist/scheduler-DD9NdYbF.js.map +0 -1
  183. package/dist/src-DlSHshb2.js.map +0 -1
  184. package/dist/streams-DZKgAj9b.js.map +0 -1
  185. package/dist/websockets-DUfRHPRq.js +0 -502
  186. package/dist/websockets-DUfRHPRq.js.map +0 -1
  187. package/dist/ws-chat-transport-rWwta645.js.map +0 -1
@@ -1,9 +1,12 @@
1
- import { n as ClientToolSchema } from "../client-tools-aIBO0Fk7.js";
1
+ import {
2
+ l as ClientToolSchema,
3
+ t as ChatTurnOutcome
4
+ } from "../wire-types-CrjKvDyi.js";
2
5
  import {
3
6
  n as WebSocketChatTransport,
4
7
  r as WebSocketChatTransportOptions,
5
8
  t as AgentConnection
6
- } from "../ws-chat-transport-UNRIS2xl.js";
9
+ } from "../ws-chat-transport-BbT1A5Lc.js";
7
10
  import { ChatInit, JSONSchema7, Tool, UIMessage } from "ai";
8
11
  import { UseChatOptions, useChat } from "@ai-sdk/react";
9
12
 
@@ -190,6 +193,23 @@ type AddToolOutputOptions = {
190
193
  | "output-error" /** Error message when state is "output-error" */;
191
194
  errorText?: string;
192
195
  };
196
+ /**
197
+ * A chat request that ended, passed to `onTurnEnd`.
198
+ */
199
+ type ChatTurnEndEvent = {
200
+ /** The transport id of the request. */ requestId: string;
201
+ /**
202
+ * Ids of the user messages the request ended with. Absent when it did not
203
+ * end with a user message, or the server no longer has a record of it.
204
+ */
205
+ messageIds?: string[];
206
+ outcome: Exclude<
207
+ ChatTurnOutcome,
208
+ "recovering"
209
+ > /** The error message, when `outcome` is `"error"`. */;
210
+ error?: string /** Whether this outcome was replayed on reconnect rather than seen live. */;
211
+ replay: boolean;
212
+ };
193
213
  /**
194
214
  * Callback for handling client-side tool execution.
195
215
  * Called when a tool without server-side execute is invoked.
@@ -243,6 +263,9 @@ type UseAgentChatOptions<
243
263
  /**
244
264
  * Callback for handling client-side tool execution.
245
265
  * Called when a tool without server-side `execute` is invoked by the LLM.
266
+ * It fires once the response stream ends, for each tool call still waiting
267
+ * for a result, so server tools that resolve in the same stream never
268
+ * reach it.
246
269
  *
247
270
  * Use this for:
248
271
  * - Tools that need browser APIs (geolocation, camera, etc.)
@@ -263,6 +286,21 @@ type UseAgentChatOptions<
263
286
  * ```
264
287
  */
265
288
  onToolCall?: OnToolCallCallback;
289
+ /**
290
+ * Called once for each chat request that ends, with the ids of the user
291
+ * messages it carried. Use it to settle optimistic sends by the ids the
292
+ * client minted. It fires for this tab's requests, for requests from other
293
+ * connections, and for outcomes replayed on reconnect. It does not fire for
294
+ * a request that recovery continues under a new request; that request
295
+ * reports the same `messageIds` when it ends.
296
+ *
297
+ * A message id can appear in more than one event, for example when a send
298
+ * is skipped and a later turn answers it. The last event for an id wins.
299
+ *
300
+ * For a request this tab is streaming, it fires once `status` has
301
+ * settled, so it can call `sendMessage`.
302
+ */
303
+ onTurnEnd?: (event: ChatTurnEndEvent) => void;
266
304
  /**
267
305
  * @deprecated Use `onToolCall` callback instead for automatic tool execution.
268
306
  * @description Whether to automatically resolve tool calls that do not require human interaction.
@@ -444,6 +482,8 @@ declare function useAgentChat<
444
482
  export {
445
483
  AITool,
446
484
  type AgentConnection,
485
+ ChatTurnEndEvent,
486
+ type ChatTurnOutcome,
447
487
  type ClientToolSchema,
448
488
  JSONSchemaType,
449
489
  OnToolCallCallback,
@@ -1,6 +1,7 @@
1
- import { n as STREAM_RESUME_NONE_REASONS, r as transition } from "../protocol-B0nh6KNf.js";
1
+ import { isSocketAddressPending } from "../socket-address.js";
2
+ import { i as transition, n as STREAM_RESUME_NONE_REASONS, r as observedDivergesFrom } from "../protocol-kHsr-f0X.js";
2
3
  import "../wire-types-CnMt6_HR.js";
3
- import { t as WebSocketChatTransport } from "../ws-chat-transport-rWwta645.js";
4
+ import { t as WebSocketChatTransport } from "../ws-chat-transport-cqMKq3IY.js";
4
5
  import { getToolName, isToolUIPart } from "ai";
5
6
  import { nanoid } from "nanoid";
6
7
  import { use, useCallback, useEffect, useMemo, useRef, useState } from "react";
@@ -165,6 +166,11 @@ async function getAgentMessages(options) {
165
166
  }
166
167
  }
167
168
  /**
169
+ * Replays re-send only recent terminals, so duplicate suppression needs only
170
+ * a recent window of ended request ids.
171
+ */
172
+ const MAX_REMEMBERED_ENDED_TURNS = 500;
173
+ /**
168
174
  * Module-level cache for initial message fetches. Intentionally shared across
169
175
  * all useAgentChat instances to deduplicate requests during React Strict Mode
170
176
  * double-renders and re-renders. Cache keys include the agent URL, agent type,
@@ -197,6 +203,12 @@ function prependMissingHydratedMessages(hydratedMessages, currentMessages) {
197
203
  if (missingHydratedMessages.length === 0) return currentMessages;
198
204
  return [...missingHydratedMessages, ...currentMessages];
199
205
  }
206
+ function restoreBufferedSends(snapshot, local, bufferedIds) {
207
+ if (bufferedIds.size === 0) return snapshot;
208
+ const snapshotIds = new Set(snapshot.map((message) => message.id));
209
+ const restored = local.filter((message) => bufferedIds.has(message.id) && !snapshotIds.has(message.id));
210
+ return restored.length === 0 ? snapshot : [...snapshot, ...restored];
211
+ }
200
212
  /**
201
213
  * React hook for building AI chat interfaces using an Agent
202
214
  * @param options Chat options including the agent connection
@@ -216,7 +228,7 @@ function detectToolsRequiringConfirmation(tools) {
216
228
  return Object.entries(tools).filter(([_name, tool]) => !tool.execute).map(([name]) => name);
217
229
  }
218
230
  function useAgentChat(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;
231
+ const { agent, getInitialMessages, messages: optionsInitialMessages, onToolCall, onTurnEnd, onData, experimental_automaticToolResolution, tools, toolsRequiringConfirmation: manualToolsRequiringConfirmation, autoContinueAfterToolResult = true, autoSendAfterAllConfirmationsResolved = true, resume = true, cancelOnClientAbort = false, syncMessagesToServer = true, body: bodyOption, prepareSendMessagesRequest, throttle, experimental_throttle, ...rest } = options;
220
232
  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.");
221
233
  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.");
222
234
  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.");
@@ -227,13 +239,38 @@ function useAgentChat(options) {
227
239
  }, [manualToolsRequiringConfirmation, tools]);
228
240
  const onToolCallRef = useRef(onToolCall);
229
241
  onToolCallRef.current = onToolCall;
242
+ const onTurnEndRef = useRef(onTurnEnd);
243
+ onTurnEndRef.current = onTurnEnd;
244
+ const turnErrorsRef = useRef(/* @__PURE__ */ new Map());
245
+ const endedTurnIdsRef = useRef(/* @__PURE__ */ new Set());
246
+ const pendingTurnEndsRef = useRef([]);
230
247
  const onDataRef = useRef(onData);
231
248
  onDataRef.current = onData;
232
- const rawHttpUrl = agent.getHttpUrl();
233
- const agentUrl = rawHttpUrl ? new URL(rawHttpUrl) : null;
234
- if (agentUrl) agentUrl.searchParams.delete("_pk");
235
- const agentUrlString = agentUrl?.toString() ?? null;
236
- const agentAddressKey = Array.isArray(agent.path) ? JSON.stringify(agent.path.map((step) => [step.agent, step.name])) : JSON.stringify([[agent.agent ?? "", agent.name ?? ""]]);
249
+ const socketAddressPending = isSocketAddressPending(agent);
250
+ const committedAddressRef = useRef(null);
251
+ let address = committedAddressRef.current;
252
+ if (!socketAddressPending || address === null) {
253
+ const rawHttpUrl = agent.getHttpUrl();
254
+ const url = rawHttpUrl ? new URL(rawHttpUrl) : null;
255
+ url?.searchParams.delete("_pk");
256
+ const route = agent.partySocketOptions;
257
+ address = {
258
+ urlString: url?.toString() ?? null,
259
+ addressKey: JSON.stringify([Array.isArray(agent.path) ? agent.path.map((step) => [step.agent, step.name]) : [[agent.agent ?? "", agent.name ?? ""]], route ? [
260
+ route.host ?? null,
261
+ route.basePath ?? null,
262
+ route.prefix ?? null,
263
+ route.party ?? null,
264
+ route.path ?? null
265
+ ] : null]),
266
+ agent: agent.agent,
267
+ name: agent.name
268
+ };
269
+ if (!socketAddressPending) committedAddressRef.current = address;
270
+ }
271
+ const agentUrl = address.urlString ? new URL(address.urlString) : null;
272
+ const agentUrlString = address.urlString;
273
+ const agentAddressKey = address.addressKey;
237
274
  const resolvedInitialMessagesCacheKey = agentUrl ? `${agentUrl.origin}${agentUrl.pathname}|${agentAddressKey}` : null;
238
275
  const initialMessagesCacheKey = agentAddressKey;
239
276
  const stableChatIdRef = useRef(null);
@@ -275,9 +312,9 @@ function useAgentChat(options) {
275
312
  requestCache.set(cacheKey, promise);
276
313
  return promise;
277
314
  }
278
- const initialMessagesPromise = !(getInitialMessages === null ? false : getInitialMessages ? true : !!agentUrlString) ? null : doGetInitialMessages({
279
- agent: agent.agent,
280
- name: agent.name,
315
+ const initialMessagesPromise = !(getInitialMessages === null || committedAddressRef.current === null ? false : getInitialMessages ? true : !!agentUrlString) ? null : doGetInitialMessages({
316
+ agent: address.agent,
317
+ name: address.name,
281
318
  url: agentUrlString ?? void 0
282
319
  }, initialMessagesCacheKey);
283
320
  const initialMessages = initialMessagesPromise ? use(initialMessagesPromise) : optionsInitialMessages ?? [];
@@ -299,6 +336,7 @@ function useAgentChat(options) {
299
336
  * Used by onAgentMessage to skip messages already handled by the transport.
300
337
  */
301
338
  const localRequestIdsRef = useRef(/* @__PURE__ */ new Set());
339
+ const pendingBufferedSendIdsRef = useRef(/* @__PURE__ */ new Set());
302
340
  const pendingReplayResumeRequestIdsRef = useRef(/* @__PURE__ */ new Set());
303
341
  const replayHydratedAssistantMessageIdsRef = useRef(/* @__PURE__ */ new Set());
304
342
  /**
@@ -317,6 +355,9 @@ function useAgentChat(options) {
317
355
  agent: agentRef.current,
318
356
  activeRequestIds: localRequestIdsRef.current,
319
357
  cancelOnClientAbort,
358
+ onRequestBuffered: (messageId) => {
359
+ if (messageId) pendingBufferedSendIdsRef.current.add(messageId);
360
+ },
320
361
  prepareBody: async ({ messages: msgs, trigger, messageId }) => {
321
362
  let extraBody = {};
322
363
  const currentBody = bodyOptionRef.current;
@@ -449,6 +490,8 @@ function useAgentChat(options) {
449
490
  const isResolvingToolsRef = useRef(false);
450
491
  const [toolResolutionTrigger, setToolResolutionTrigger] = useState(0);
451
492
  const [clientToolResults, setClientToolResults] = useState(/* @__PURE__ */ new Map());
493
+ const clientToolResultsRef = useRef(clientToolResults);
494
+ clientToolResultsRef.current = clientToolResults;
452
495
  const initialMessagesRef = useRef(initialMessages);
453
496
  initialMessagesRef.current = initialMessages;
454
497
  const seededInitialMessagesKeyRef = useRef(null);
@@ -569,10 +612,15 @@ function useAgentChat(options) {
569
612
  fallbackAckedResumeRequestIdsRef.current.clear();
570
613
  replayHydratedAssistantMessageIdsRef.current.clear();
571
614
  protectedStreamingAssistantRef.current = null;
615
+ pendingBufferedSendIdsRef.current.clear();
616
+ customTransport.appliedChunks.clear();
617
+ turnErrorsRef.current.clear();
618
+ pendingTurnEndsRef.current = [];
572
619
  }, [
573
620
  markInitialMessagesSeeded,
574
621
  setMessages,
575
- resetToolContinuation
622
+ resetToolContinuation,
623
+ customTransport
576
624
  ]);
577
625
  const sendMessageWithStreamingProtection = useCallback(async (message, options) => {
578
626
  const request = sendMessage(message, options);
@@ -695,9 +743,33 @@ function useAgentChat(options) {
695
743
  }));
696
744
  if (autoContinueAfterToolResult) startToolContinuation();
697
745
  }, [autoContinueAfterToolResult, startToolContinuation]);
746
+ const [isServerStreaming, setIsServerStreaming] = useState(false);
747
+ const [unresolvedObservedRequestId, setUnresolvedObservedRequestId] = useState(null);
748
+ const unresolvedObservedRequestIdRef = useRef(unresolvedObservedRequestId);
749
+ unresolvedObservedRequestIdRef.current = unresolvedObservedRequestId;
750
+ const [recoveringRequestId, setRecoveringRequestIdState] = useState(null);
751
+ const recoveringRequestIdRef = useRef(null);
752
+ const setRecoveringRequestId = useCallback((requestId) => {
753
+ recoveringRequestIdRef.current = requestId;
754
+ setRecoveringRequestIdState(requestId);
755
+ }, []);
756
+ useEffect(() => {
757
+ if (status === "submitted") {
758
+ setUnresolvedObservedRequestId(null);
759
+ setRecoveringRequestId(null);
760
+ }
761
+ }, [status, setRecoveringRequestId]);
762
+ useEffect(() => {
763
+ if (status === "submitted" || status === "streaming") return;
764
+ const events = pendingTurnEndsRef.current;
765
+ if (events.length === 0) return;
766
+ pendingTurnEndsRef.current = [];
767
+ for (const event of events) onTurnEndRef.current?.(event);
768
+ }, [status]);
698
769
  useEffect(() => {
699
770
  const currentOnToolCall = onToolCallRef.current;
700
771
  if (!currentOnToolCall) return;
772
+ if (status === "streaming" || status === "submitted" || isServerStreaming || unresolvedObservedRequestId !== null || recoveringRequestId !== null) return;
701
773
  const lastMsg = chatMessages[chatMessages.length - 1];
702
774
  if (!lastMsg || lastMsg.role !== "assistant") return;
703
775
  const pendingToolCalls = lastMsg.parts.filter((part) => isToolUIPart(part) && part.state === "input-available" && !processedToolCalls.current.has(part.toolCallId));
@@ -739,15 +811,53 @@ function useAgentChat(options) {
739
811
  }
740
812
  }, [
741
813
  chatMessages,
814
+ status,
815
+ isServerStreaming,
816
+ unresolvedObservedRequestId,
817
+ recoveringRequestId,
742
818
  sendToolOutputToServer,
743
819
  addToolResult,
744
820
  finishOnToolCall
745
821
  ]);
746
822
  const streamStateRef = useRef({ status: "idle" });
747
- const [isServerStreaming, setIsServerStreaming] = useState(false);
748
823
  const [isRecovering, setIsRecovering] = useState(false);
749
824
  useEffect(() => {
750
825
  const localResponseIds = localResponseMessageIdsRef.current;
826
+ const turnErrors = turnErrorsRef.current;
827
+ const endedTurnIds = endedTurnIdsRef.current;
828
+ let idleProbeNeeded = false;
829
+ let idleProbeId = null;
830
+ let serverReportsRecovery = false;
831
+ function reportTurnEnd(frame) {
832
+ if (frame.error && !frame.done) {
833
+ turnErrors.set(frame.id, frame.body);
834
+ if (turnErrors.size > MAX_REMEMBERED_ENDED_TURNS) {
835
+ const oldest = turnErrors.keys().next().value;
836
+ if (oldest !== void 0) turnErrors.delete(oldest);
837
+ }
838
+ return;
839
+ }
840
+ if (!frame.done) return;
841
+ const earlierError = turnErrors.get(frame.id);
842
+ turnErrors.delete(frame.id);
843
+ const outcome = frame.error ? "error" : frame.outcome ?? (earlierError !== void 0 ? "error" : "completed");
844
+ if (outcome === "recovering" || endedTurnIds.has(frame.id)) return;
845
+ endedTurnIds.add(frame.id);
846
+ if (endedTurnIds.size > MAX_REMEMBERED_ENDED_TURNS) {
847
+ const oldest = endedTurnIds.values().next().value;
848
+ if (oldest !== void 0) endedTurnIds.delete(oldest);
849
+ }
850
+ const error = frame.error ? frame.body : earlierError;
851
+ const event = {
852
+ requestId: frame.id,
853
+ ...frame.messageIds ? { messageIds: frame.messageIds } : {},
854
+ outcome,
855
+ ...outcome === "error" && error ? { error } : {},
856
+ replay: frame.replay === true
857
+ };
858
+ if (localRequestIdsRef.current.has(frame.id) && (statusRef.current === "submitted" || statusRef.current === "streaming")) pendingTurnEndsRef.current.push(event);
859
+ else onTurnEndRef.current?.(event);
860
+ }
751
861
  /**
752
862
  * Unified message handler that parses JSON once and dispatches based on type.
753
863
  * Avoids duplicate parsing overhead from separate listeners.
@@ -764,24 +874,31 @@ function useAgentChat(options) {
764
874
  case "cf_agent_chat_clear":
765
875
  streamStateRef.current = transition(streamStateRef.current, { type: "clear" }).state;
766
876
  setIsServerStreaming(false);
877
+ setUnresolvedObservedRequestId(null);
878
+ setRecoveringRequestId(null);
767
879
  setIsRecovering(false);
768
880
  resetLocalChatState();
769
881
  break;
770
882
  case "cf_agent_chat_recovering":
771
883
  setIsRecovering(Boolean(data.recovering));
884
+ serverReportsRecovery = Boolean(data.recovering);
885
+ if (!data.recovering) setRecoveringRequestId(null);
772
886
  break;
773
- case "cf_agent_chat_messages":
887
+ case "cf_agent_chat_messages": {
888
+ const bufferedSendIds = data.connect ? new Set(pendingBufferedSendIdsRef.current) : /* @__PURE__ */ new Set();
889
+ pendingBufferedSendIdsRef.current.clear();
774
890
  setMessages((currentMessages) => {
775
891
  let next = preserveProtectedStreamingAssistant(data.messages, currentMessages);
776
892
  const observed = streamStateRef.current;
777
893
  if (observed.status === "observing" && observed.accumulator.parts.length > 0) {
778
894
  const snapshotIdx = next.findIndex((m) => m.id === observed.accumulator.messageId);
779
895
  const snapshotParts = snapshotIdx >= 0 ? next[snapshotIdx].parts.length : 0;
780
- if (observed.accumulator.parts.length >= snapshotParts) next = observed.accumulator.mergeInto(next);
896
+ if (observed.accumulator.parts.length >= snapshotParts && !observedDivergesFrom(observed.accumulator, next)) next = observed.accumulator.mergeInto(next);
781
897
  }
782
- return next;
898
+ return restoreBufferedSends(next, currentMessages, bufferedSendIds);
783
899
  });
784
900
  break;
901
+ }
785
902
  case "cf_agent_message_updated":
786
903
  setMessages((prevMessages) => {
787
904
  const updatedMessage = data.message;
@@ -801,21 +918,33 @@ function useAgentChat(options) {
801
918
  return prevMessages;
802
919
  });
803
920
  break;
804
- case "cf_agent_stream_resume_none":
805
- if (customTransport.handleStreamResumeNone(data) && data.reason === STREAM_RESUME_NONE_REASONS.IDLE && typeof data.probeId === "string") {
921
+ case "cf_agent_stream_resume_none": {
922
+ const handled = customTransport.handleStreamResumeNone(data);
923
+ const answersIdleProbe = idleProbeId !== null && data.probeId === idleProbeId;
924
+ if (answersIdleProbe) idleProbeId = null;
925
+ if ((handled || answersIdleProbe) && data.reason === STREAM_RESUME_NONE_REASONS.IDLE && typeof data.probeId === "string") {
806
926
  const result = transition(streamStateRef.current, { type: "clear" });
807
927
  streamStateRef.current = result.state;
808
928
  setIsServerStreaming(result.isStreaming);
929
+ setUnresolvedObservedRequestId(null);
930
+ if (!serverReportsRecovery) setRecoveringRequestId(null);
809
931
  if (observedToolContinuationRequestIdRef.current !== null) resetToolContinuation();
810
932
  }
811
933
  break;
934
+ }
812
935
  case "cf_agent_stream_pending":
813
936
  customTransport.handleStreamPending();
814
937
  break;
815
938
  case "cf_agent_stream_resuming": {
939
+ setUnresolvedObservedRequestId((current) => current === data.id ? current : null);
940
+ if (data.probeId !== void 0 && data.probeId === idleProbeId) idleProbeId = null;
816
941
  const isEarlyToolContinuation = resumingToolContinuationRef.current && !customTransport.isAwaitingResume();
817
942
  if (!resume && !customTransport.isAwaitingResume()) {
818
- if (!isEarlyToolContinuation) return;
943
+ if (!isEarlyToolContinuation) {
944
+ if (data.probeId === void 0) setUnresolvedObservedRequestId(data.id);
945
+ if (recoveringRequestIdRef.current !== null && recoveringRequestIdRef.current !== data.id) setRecoveringRequestId(null);
946
+ return;
947
+ }
819
948
  }
820
949
  if (!resumingToolContinuationRef.current) pendingReplayResumeRequestIdsRef.current.add(data.id);
821
950
  if (customTransport.handleStreamResuming(data)) return;
@@ -836,6 +965,7 @@ function useAgentChat(options) {
836
965
  }).state;
837
966
  customTransport.observeServerTurn(data.id);
838
967
  setIsServerStreaming(true);
968
+ if (recoveringRequestIdRef.current !== null && recoveringRequestIdRef.current !== data.id) setRecoveringRequestId(null);
839
969
  setIsRecovering(false);
840
970
  fallbackAckedResumeRequestIdsRef.current.add(data.id);
841
971
  agentRef.current.send(JSON.stringify({
@@ -845,6 +975,10 @@ function useAgentChat(options) {
845
975
  break;
846
976
  }
847
977
  case "cf_agent_use_chat_response": {
978
+ if (data.done || data.error) setUnresolvedObservedRequestId((current) => current === data.id ? null : current);
979
+ if (data.done && data.outcome === "recovering") setRecoveringRequestId(data.id);
980
+ else if (recoveringRequestIdRef.current !== null && recoveringRequestIdRef.current !== data.id && !data.replay && data.outcome !== "skipped") setRecoveringRequestId(null);
981
+ reportTurnEnd(data);
848
982
  if (localRequestIdsRef.current.has(data.id)) {
849
983
  if (data.body?.trim()) try {
850
984
  const chunkData = JSON.parse(data.body);
@@ -884,8 +1018,15 @@ function useAgentChat(options) {
884
1018
  }
885
1019
  let chunkData;
886
1020
  if (data.replay && streamStateRef.current.status !== "observing" && !pendingReplayResumeRequestIdsRef.current.has(data.id)) return;
1021
+ if (data.done && !data.error && !(streamStateRef.current.status === "observing" && streamStateRef.current.streamId === data.id) && !pendingReplayResumeRequestIdsRef.current.has(data.id) && observedToolContinuationRequestIdRef.current !== data.id) {
1022
+ customTransport.appliedChunks.forget(data.id);
1023
+ customTransport.handleServerTurnCompleted(data.id);
1024
+ fallbackAckedResumeRequestIdsRef.current.delete(data.id);
1025
+ break;
1026
+ }
887
1027
  if (data.error) {
888
1028
  pendingReplayResumeRequestIdsRef.current.delete(data.id);
1029
+ customTransport.appliedChunks.forget(data.id);
889
1030
  customTransport.handleServerTurnCompleted(data.id);
890
1031
  fallbackAckedResumeRequestIdsRef.current.delete(data.id);
891
1032
  setIsRecovering(false);
@@ -897,13 +1038,14 @@ function useAgentChat(options) {
897
1038
  if (observedToolContinuationRequestIdRef.current === data.id) resetToolContinuation();
898
1039
  break;
899
1040
  }
1041
+ const appliedReplay = customTransport.appliedChunks.isAppliedReplay(data.id, data);
900
1042
  if (data.body?.trim()) try {
901
1043
  chunkData = JSON.parse(data.body);
902
1044
  if (data.replay && !data.continuation && !resumingToolContinuationRef.current && observedToolContinuationRequestIdRef.current !== data.id && typeof chunkData.messageId === "string" && chunkData.type === "start") {
903
1045
  pendingReplayResumeRequestIdsRef.current.delete(data.id);
904
1046
  resetMatchingHydratedAssistantForReplay(chunkData.messageId);
905
1047
  }
906
- if (typeof chunkData.type === "string" && chunkData.type.startsWith("data-") && onDataRef.current) onDataRef.current(chunkData);
1048
+ if (typeof chunkData.type === "string" && chunkData.type.startsWith("data-") && !appliedReplay && onDataRef.current) onDataRef.current(chunkData);
907
1049
  } catch (parseError) {
908
1050
  console.warn("[useAgentChat] Failed to parse stream chunk:", parseError instanceof Error ? parseError.message : parseError, "body:", data.body?.slice(0, 100));
909
1051
  }
@@ -911,14 +1053,17 @@ function useAgentChat(options) {
911
1053
  if (data.done) {
912
1054
  customTransport.handleServerTurnCompleted(data.id);
913
1055
  fallbackAckedResumeRequestIdsRef.current.delete(data.id);
914
- setIsRecovering(false);
1056
+ if (data.outcome !== "recovering") setIsRecovering(false);
915
1057
  }
916
1058
  const completedObservedToolContinuation = data.done && observedToolContinuationRequestIdRef.current === data.id;
1059
+ const alreadyApplied = appliedReplay && chunkData?.type !== "start";
1060
+ if (data.done) customTransport.appliedChunks.forget(data.id);
1061
+ else if (!alreadyApplied) customTransport.appliedChunks.record(data.id, data.seq);
917
1062
  const result = transition(streamStateRef.current, {
918
1063
  type: "response",
919
1064
  streamId: data.id,
920
1065
  messageId: nanoid(),
921
- chunkData,
1066
+ chunkData: alreadyApplied ? void 0 : chunkData,
922
1067
  done: data.done,
923
1068
  error: data.error,
924
1069
  replay: data.replay,
@@ -938,6 +1083,7 @@ function useAgentChat(options) {
938
1083
  let sawClose = false;
939
1084
  let disposed = false;
940
1085
  const clearFallbackObserver = () => {
1086
+ if (streamStateRef.current.status === "observing") setUnresolvedObservedRequestId(streamStateRef.current.streamId);
941
1087
  const result = transition(streamStateRef.current, { type: "clear" });
942
1088
  streamStateRef.current = result.state;
943
1089
  setIsServerStreaming(result.isStreaming);
@@ -947,6 +1093,14 @@ function useAgentChat(options) {
947
1093
  if (disposed || !socketIsOpen || !reconnectProbePendingRef.current) return;
948
1094
  if (!resume) {
949
1095
  reconnectProbePendingRef.current = false;
1096
+ if (idleProbeNeeded) {
1097
+ idleProbeNeeded = false;
1098
+ idleProbeId = nanoid();
1099
+ agentRef.current.send(JSON.stringify({
1100
+ type: "cf_agent_stream_resume_request",
1101
+ probeId: idleProbeId
1102
+ }));
1103
+ }
950
1104
  return;
951
1105
  }
952
1106
  if (customTransport.retryPendingResume()) {
@@ -962,8 +1116,14 @@ function useAgentChat(options) {
962
1116
  function onAgentClose() {
963
1117
  socketIsOpen = false;
964
1118
  sawClose = true;
1119
+ serverReportsRecovery = false;
965
1120
  fallbackAckedResumeRequestIds.clear();
966
- if (!resume) clearFallbackObserver();
1121
+ const unfinishedTurnId = customTransport.activeServerTurnId;
1122
+ if (unfinishedTurnId !== null) setUnresolvedObservedRequestId(unfinishedTurnId);
1123
+ if (!resume) {
1124
+ idleProbeNeeded ||= unfinishedTurnId !== null || streamStateRef.current.status === "observing" || unresolvedObservedRequestIdRef.current !== null || recoveringRequestIdRef.current !== null;
1125
+ clearFallbackObserver();
1126
+ }
967
1127
  }
968
1128
  function onAgentOpen() {
969
1129
  socketIsOpen = true;
@@ -985,6 +1145,7 @@ function useAgentChat(options) {
985
1145
  fallbackAckedResumeRequestIds.clear();
986
1146
  streamStateRef.current = { status: "idle" };
987
1147
  setIsServerStreaming(false);
1148
+ setUnresolvedObservedRequestId(null);
988
1149
  setIsRecovering(false);
989
1150
  protectedStreamingAssistantRef.current = null;
990
1151
  localResponseIds.clear();
@@ -1001,7 +1162,8 @@ function useAgentChat(options) {
1001
1162
  resetMatchingHydratedAssistantForReplay,
1002
1163
  restoreProtectedStreamingAssistant,
1003
1164
  resetLocalChatState,
1004
- invalidateResumeGeneration
1165
+ invalidateResumeGeneration,
1166
+ setRecoveringRequestId
1005
1167
  ]);
1006
1168
  useEffect(() => {
1007
1169
  if (!resume) return;
@@ -1071,17 +1233,11 @@ function useAgentChat(options) {
1071
1233
  useEffect(() => {
1072
1234
  const currentToolCallIds = /* @__PURE__ */ new Set();
1073
1235
  for (const msg of chatMessages) for (const part of msg.parts) if ("toolCallId" in part && part.toolCallId) currentToolCallIds.add(part.toolCallId);
1074
- setClientToolResults((prev) => {
1075
- if (prev.size === 0) return prev;
1076
- let hasStaleEntries = false;
1077
- for (const toolCallId of prev.keys()) if (!currentToolCallIds.has(toolCallId)) {
1078
- hasStaleEntries = true;
1079
- break;
1080
- }
1081
- if (!hasStaleEntries) return prev;
1082
- const newMap = /* @__PURE__ */ new Map();
1083
- for (const [id, output] of prev) if (currentToolCallIds.has(id)) newMap.set(id, output);
1084
- return newMap;
1236
+ const isStale = (id) => !currentToolCallIds.has(id);
1237
+ if ([...clientToolResultsRef.current.keys()].some(isStale)) setClientToolResults((prev) => {
1238
+ const next = /* @__PURE__ */ new Map();
1239
+ for (const [id, output] of prev) if (!isStale(id)) next.set(id, output);
1240
+ return next.size === prev.size ? prev : next;
1085
1241
  });
1086
1242
  for (const toolCallId of processedToolCalls.current) if (!currentToolCallIds.has(toolCallId)) processedToolCalls.current.delete(toolCallId);
1087
1243
  }, [chatMessages]);