@assistant-ui/ai-sdk 0.0.8 → 0.0.10

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 (51) hide show
  1. package/LICENSE +1 -1
  2. package/dist/converters/convertMessage.d.ts +2 -0
  3. package/dist/converters/convertMessage.d.ts.map +1 -1
  4. package/dist/converters/convertMessage.js +15 -4
  5. package/dist/converters/convertMessage.js.map +1 -1
  6. package/dist/converters/toCreateMessage.d.ts.map +1 -1
  7. package/dist/converters/toCreateMessage.js +2 -1
  8. package/dist/converters/toCreateMessage.js.map +1 -1
  9. package/dist/runtime/AISDKChat.d.ts.map +1 -1
  10. package/dist/runtime/AISDKChat.js.map +1 -1
  11. package/dist/runtime/AISDKThreads.js.map +1 -1
  12. package/dist/runtime/sdkIdentity.js +1 -1
  13. package/dist/runtime/useAISDKRuntime.d.ts +8 -1
  14. package/dist/runtime/useAISDKRuntime.d.ts.map +1 -1
  15. package/dist/runtime/useAISDKRuntime.js +212 -34
  16. package/dist/runtime/useAISDKRuntime.js.map +1 -1
  17. package/dist/runtime/useChatRuntime.js +1 -1
  18. package/dist/runtime/useChatRuntime.js.map +1 -1
  19. package/dist/runtime/useChatThread.d.ts.map +1 -1
  20. package/dist/runtime/useChatThread.js +40 -10
  21. package/dist/runtime/useChatThread.js.map +1 -1
  22. package/dist/runtime/useExternalHistory.d.ts.map +1 -1
  23. package/dist/runtime/useExternalHistory.js +2 -1
  24. package/dist/runtime/useExternalHistory.js.map +1 -1
  25. package/dist/runtime/useResourceCleanup.js.map +1 -1
  26. package/dist/runtime/useStreamingTiming.js.map +1 -1
  27. package/dist/transport/AssistantChatTransport.d.ts.map +1 -1
  28. package/dist/transport/AssistantChatTransport.js +9 -2
  29. package/dist/transport/AssistantChatTransport.js.map +1 -1
  30. package/dist/transport/resumable.js.map +1 -1
  31. package/dist/usage.js.map +1 -1
  32. package/package.json +15 -12
  33. package/src/converters/convertMessage.test.ts +99 -0
  34. package/src/converters/convertMessage.ts +27 -2
  35. package/src/converters/toCreateMessage.test.ts +13 -0
  36. package/src/converters/toCreateMessage.ts +1 -0
  37. package/src/runtime/AISDKChat.ts +0 -4
  38. package/src/runtime/AISDKThreads.test.ts +26 -0
  39. package/src/runtime/useAISDKRuntime.approval.integration.test.tsx +1588 -19
  40. package/src/runtime/useAISDKRuntime.approval.test.tsx +27 -0
  41. package/src/runtime/useAISDKRuntime.fast-refresh.test.tsx +182 -0
  42. package/src/runtime/useAISDKRuntime.ts +397 -28
  43. package/src/runtime/useChatRuntime.fast-refresh.test.tsx +94 -0
  44. package/src/runtime/useChatRuntime.integration.test.tsx +134 -112
  45. package/src/runtime/useChatRuntime.test.ts +7 -7
  46. package/src/runtime/useChatThread.test.ts +166 -2
  47. package/src/runtime/useChatThread.transport.test.tsx +5 -2
  48. package/src/runtime/useChatThread.ts +56 -17
  49. package/src/runtime/useExternalHistory.ts +12 -1
  50. package/src/transport/AssistantChatTransport.test.ts +164 -0
  51. package/src/transport/AssistantChatTransport.ts +22 -2
@@ -15,6 +15,7 @@ import { useExternalStoreRuntime, useRuntimeAdapters } from "@assistant-ui/core/
15
15
  import { getExternalStoreMessages, pickExternalStoreSharedOptions } from "@assistant-ui/core";
16
16
  import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
17
17
  import { useCallback, useEffect, useInsertionEffect, useMemo, useRef, useState } from "@assistant-ui/tap/react-shim";
18
+ import { useReplaySafeEffect } from "@assistant-ui/store/internal";
18
19
  //#region src/runtime/useAISDKRuntime.ts
19
20
  const toUIMessage = (createMessage, fallbackRole) => ({
20
21
  ...createMessage,
@@ -137,11 +138,97 @@ const useGeneratedSuggestions = (suggestionAdapter, messages, isRunning) => {
137
138
  t6 = $[11];
138
139
  t7 = $[12];
139
140
  }
140
- useEffect(t6, t7);
141
+ useReplaySafeEffect(t6, t7);
141
142
  return suggestions;
142
143
  };
143
144
  const NO_CANCELLED_MESSAGE_IDS = /* @__PURE__ */ new Set();
145
+ const NO_SUPERSEDED_APPROVAL_PROJECTION = Object.freeze({
146
+ approvalIds: Object.freeze(/* @__PURE__ */ new Set()),
147
+ statusMessageIds: Object.freeze(/* @__PURE__ */ new Set())
148
+ });
144
149
  const NO_TOOL_APPROVAL_RESPONSES = /* @__PURE__ */ new Map();
150
+ const hostToolApprovalsByChat = /* @__PURE__ */ new WeakMap();
151
+ const toApprovalResponses = (owned) => owned && owned.size > 0 ? new Map([...owned].map(([id, entry]) => [id, entry.response])) : NO_TOOL_APPROVAL_RESPONSES;
152
+ /**
153
+ * The answers live on the owner, but each mounted runtime renders them from
154
+ * its own state, so a write has to be announced: the runtime that performed it
155
+ * may already be unmounted (a rollback resolving after a remount), and another
156
+ * runtime may be mounted over the same owner.
157
+ */
158
+ const hostApprovalListenersByChat = /* @__PURE__ */ new WeakMap();
159
+ const subscribeToHostApprovals = (owner, listener) => {
160
+ const listeners = hostApprovalListenersByChat.get(owner) ?? /* @__PURE__ */ new Set();
161
+ hostApprovalListenersByChat.set(owner, listeners);
162
+ listeners.add(listener);
163
+ return () => {
164
+ listeners.delete(listener);
165
+ };
166
+ };
167
+ const notifyHostApprovals = (owner) => {
168
+ for (const listener of [...hostApprovalListenersByChat.get(owner) ?? []]) listener();
169
+ };
170
+ const getSupersededApprovalProjection = (messages, hostApprovalIds, joinStrategy, isRunning) => {
171
+ const approvalIds = /* @__PURE__ */ new Set();
172
+ const statusMessageIds = /* @__PURE__ */ new Set();
173
+ const lastIndex = messages.length - 1;
174
+ let lastAssistant;
175
+ let hasSupersededApproval = false;
176
+ let previousWasVoice = false;
177
+ const flush = () => {
178
+ const hasOpenToolPart = lastAssistant === messages[lastIndex] && lastAssistant?.parts?.some((part) => {
179
+ if (!isToolUIPart(part) || part.state === "output-available" || part.state === "output-error" || part.state === "output-denied") return false;
180
+ const approval = part.approval;
181
+ const resolution = approval?.resolution ?? (approval?.descriptor)?.resolution;
182
+ return resolution !== "cancelled" && resolution !== "expired";
183
+ });
184
+ if (lastAssistant && hasSupersededApproval && !hasOpenToolPart && !(isRunning && lastAssistant === messages[lastIndex])) statusMessageIds.add(lastAssistant.id);
185
+ lastAssistant = void 0;
186
+ hasSupersededApproval = false;
187
+ previousWasVoice = false;
188
+ };
189
+ for (let index = 0; index < messages.length; index++) {
190
+ const message = messages[index];
191
+ if (message.role !== "assistant") flush();
192
+ else {
193
+ const isVoice = message.metadata?.modality === "voice";
194
+ if (isVoice || previousWasVoice || joinStrategy === "none") flush();
195
+ lastAssistant = message;
196
+ previousWasVoice = isVoice;
197
+ }
198
+ for (const part of message.parts ?? []) {
199
+ if (!isToolUIPart(part) || part.state !== "approval-requested") continue;
200
+ const approval = part.approval;
201
+ if (!approval) continue;
202
+ const approvalId = approval.id;
203
+ if (hostApprovalIds.has(approvalId)) continue;
204
+ const resolution = approval.resolution ?? approval.descriptor?.resolution;
205
+ if (resolution === "cancelled" || resolution === "expired") continue;
206
+ if (index !== lastIndex) {
207
+ approvalIds.add(approvalId);
208
+ if (message.role === "assistant") hasSupersededApproval = true;
209
+ }
210
+ }
211
+ }
212
+ flush();
213
+ if (approvalIds.size === 0 && statusMessageIds.size === 0) return NO_SUPERSEDED_APPROVAL_PROJECTION;
214
+ return {
215
+ approvalIds,
216
+ statusMessageIds
217
+ };
218
+ };
219
+ const findRawToolMessageIndex = (messages, messageId, toolCallId, joinStrategy) => {
220
+ const containsToolCall = (message) => message.parts?.some((part) => isToolUIPart(part) && part.toolCallId === toolCallId) === true;
221
+ const messageIndex = messages.findIndex((message) => message.id === messageId);
222
+ if (messageIndex === -1) return -1;
223
+ if (containsToolCall(messages[messageIndex])) return messageIndex;
224
+ if (joinStrategy === "none" || messages[messageIndex]?.role !== "assistant") return -1;
225
+ let start = messageIndex;
226
+ while (start > 0 && messages[start - 1]?.role === "assistant") start--;
227
+ let end = messageIndex;
228
+ while (end + 1 < messages.length && messages[end + 1]?.role === "assistant") end++;
229
+ for (let index = start; index <= end; index++) if (containsToolCall(messages[index])) return index;
230
+ return -1;
231
+ };
145
232
  const toChatError = (error) => {
146
233
  const code = error.code;
147
234
  return {
@@ -155,11 +242,42 @@ const useAISDKRuntime = (chatHelpers, adapter = {}) => {
155
242
  const contextAdapters = useRuntimeAdapters();
156
243
  const [toolStatuses, setToolStatuses] = useState({});
157
244
  const [cancelledMessages, setCancelledMessages] = useState(null);
158
- const [toolApprovalResponses, setToolApprovalResponses] = useState(NO_TOOL_APPROVAL_RESPONSES);
245
+ const approvalOwner = adapter.unstable_hostApprovalOwner;
246
+ const ownedApprovals = approvalOwner ? hostToolApprovalsByChat.get(approvalOwner) ?? (() => {
247
+ const created = /* @__PURE__ */ new Map();
248
+ hostToolApprovalsByChat.set(approvalOwner, created);
249
+ return created;
250
+ })() : void 0;
251
+ const [toolApprovalResponses, setToolApprovalResponses] = useState(() => toApprovalResponses(ownedApprovals));
159
252
  const [toolArtifactEpoch, setToolArtifactEpoch] = useState(0);
160
253
  const [toolInteractionEpoch, setToolInteractionEpoch] = useState(0);
161
- const hostApprovalIdsRef = useRef(/* @__PURE__ */ new Set());
162
- const toolApprovalResponsesRef = useRef(/* @__PURE__ */ new Map());
254
+ const toolApprovalResponsesRef = useRef(new Map(toApprovalResponses(ownedApprovals)));
255
+ const ownedApprovalIdsRef = useRef(new Set(ownedApprovals?.keys()));
256
+ const hostApprovalIdsRef = useRef(new Set(ownedApprovals?.keys()));
257
+ useEffect(() => {
258
+ if (!approvalOwner || !ownedApprovals) return void 0;
259
+ const sync = () => {
260
+ for (const id of ownedApprovalIdsRef.current) toolApprovalResponsesRef.current.delete(id);
261
+ for (const [id_0, entry] of ownedApprovals) toolApprovalResponsesRef.current.set(id_0, entry.response);
262
+ ownedApprovalIdsRef.current = new Set(ownedApprovals.keys());
263
+ hostApprovalIdsRef.current = new Set(toolApprovalResponsesRef.current.keys());
264
+ setToolApprovalResponses((prev) => {
265
+ const next = new Map(toolApprovalResponsesRef.current);
266
+ return prev.size === next.size && [...next].every(([id_1, response]) => prev.get(id_1) === response) ? prev : next;
267
+ });
268
+ };
269
+ const unsubscribe = subscribeToHostApprovals(approvalOwner, sync);
270
+ sync();
271
+ return unsubscribe;
272
+ }, [approvalOwner, ownedApprovals]);
273
+ const lastApprovalOwnerRef = useRef(approvalOwner);
274
+ if (lastApprovalOwnerRef.current !== approvalOwner) {
275
+ lastApprovalOwnerRef.current = approvalOwner;
276
+ hostApprovalIdsRef.current = new Set(ownedApprovals?.keys());
277
+ toolApprovalResponsesRef.current = new Map(toApprovalResponses(ownedApprovals));
278
+ ownedApprovalIdsRef.current = new Set(ownedApprovals?.keys());
279
+ setToolApprovalResponses(new Map(toolApprovalResponsesRef.current));
280
+ }
163
281
  const toolArgsKeyOrderCacheRef = useRef(/* @__PURE__ */ new Map());
164
282
  const toolLastInputCacheRef = useRef(/* @__PURE__ */ new Map());
165
283
  const toolArgsTextCacheRef = useRef(/* @__PURE__ */ new WeakMap());
@@ -182,6 +300,19 @@ const useAISDKRuntime = (chatHelpers, adapter = {}) => {
182
300
  const optimisticMessageId = isRunning && lastMessage?.role === "assistant" ? lastMessage.id : void 0;
183
301
  const cancelledMessageIds = cancelledMessages?.chatId === chatHelpers.id ? cancelledMessages.ids : NO_CANCELLED_MESSAGE_IDS;
184
302
  const supportsRichToolApprovalResponses = customOnRespondToToolApproval != null;
303
+ const supersededApprovalProjectionRef = useRef(NO_SUPERSEDED_APPROVAL_PROJECTION);
304
+ const supersededApprovalProjection = useMemo(() => {
305
+ const projection = getSupersededApprovalProjection(chatHelpers.messages, hostApprovalIdsRef.current, joinStrategy, isRunning);
306
+ const previous = supersededApprovalProjectionRef.current;
307
+ if (projection.approvalIds.size === previous.approvalIds.size && projection.statusMessageIds.size === previous.statusMessageIds.size && [...projection.approvalIds].every((id_3) => previous.approvalIds.has(id_3)) && [...projection.statusMessageIds].every((id_2) => previous.statusMessageIds.has(id_2))) return previous;
308
+ supersededApprovalProjectionRef.current = projection;
309
+ return projection;
310
+ }, [
311
+ chatHelpers.messages,
312
+ joinStrategy,
313
+ isRunning,
314
+ toolApprovalResponses
315
+ ]);
185
316
  const toThreadMessages = useCallback((sourceMessages) => {
186
317
  const metadata = {
187
318
  supportsRichToolApprovalResponses,
@@ -192,9 +323,9 @@ const useAISDKRuntime = (chatHelpers, adapter = {}) => {
192
323
  return AISDKMessageConverter.toThreadMessages(sourceMessages, false, metadata);
193
324
  }, [supportsRichToolApprovalResponses]);
194
325
  const retractCancellation = useCallback((chatId, messageId) => {
195
- setCancelledMessages((prev) => {
196
- if (prev?.chatId !== chatId || !prev.ids.has(messageId)) return prev;
197
- const ids = new Set(prev.ids);
326
+ setCancelledMessages((prev_0) => {
327
+ if (prev_0?.chatId !== chatId || !prev_0.ids.has(messageId)) return prev_0;
328
+ const ids = new Set(prev_0.ids);
198
329
  ids.delete(messageId);
199
330
  return {
200
331
  chatId,
@@ -228,6 +359,8 @@ const useAISDKRuntime = (chatHelpers, adapter = {}) => {
228
359
  toolArtifacts: toolArtifactsRef.current,
229
360
  toolInteractions: toolInteractionsRef.current,
230
361
  supportsRichToolApprovalResponses,
362
+ cancelledToolApprovalIds: supersededApprovalProjection.approvalIds,
363
+ cancelledStatusMessageIds: supersededApprovalProjection.statusMessageIds,
231
364
  ...optimisticMessageId && { optimisticMessageId },
232
365
  ...chatHelpers.error && { error: toChatError(chatHelpers.error) },
233
366
  ...cancelledMessageIds.size > 0 && { cancelledMessageIds },
@@ -240,6 +373,7 @@ const useAISDKRuntime = (chatHelpers, adapter = {}) => {
240
373
  cancelledMessageIds,
241
374
  toolApprovalResponses,
242
375
  supportsRichToolApprovalResponses,
376
+ supersededApprovalProjection,
243
377
  toolArtifactEpoch,
244
378
  toolInteractionEpoch
245
379
  ])
@@ -256,6 +390,7 @@ const useAISDKRuntime = (chatHelpers, adapter = {}) => {
256
390
  const { isLoading, deleteMessage: deleteHistoryMessage, persistToolInteractions, persistToolApprovalResponses } = useExternalHistory(runtimeRef, adapters?.history ?? contextAdapters?.history, toThreadMessages, aiSDKV6FormatAdapter, (messages_0) => {
257
391
  chatHelpers.setMessages(messages_0);
258
392
  }, toolArtifactsRef.current, markToolArtifactsChanged, toolInteractionsRef.current, markToolInteractionsChanged, toolApprovalResponsesRef.current, () => {
393
+ for (const [id_4, entry_0] of ownedApprovals ?? []) toolApprovalResponsesRef.current.set(id_4, entry_0.response);
259
394
  hostApprovalIdsRef.current = new Set(toolApprovalResponsesRef.current.keys());
260
395
  setToolApprovalResponses(new Map(toolApprovalResponsesRef.current));
261
396
  });
@@ -273,7 +408,7 @@ const useAISDKRuntime = (chatHelpers, adapter = {}) => {
273
408
  if (!cancelPendingToolCallsOnSend) return;
274
409
  chatHelpers.setMessages((messages_1) => {
275
410
  let hasChanges = false;
276
- const next = messages_1.map((message) => {
411
+ const next_0 = messages_1.map((message) => {
277
412
  if (message.role !== "assistant") return message;
278
413
  let messageChanged = false;
279
414
  const parts = message.parts?.map((part) => {
@@ -295,7 +430,7 @@ const useAISDKRuntime = (chatHelpers, adapter = {}) => {
295
430
  };
296
431
  });
297
432
  if (!hasChanges) return messages_1;
298
- return next;
433
+ return next_0;
299
434
  });
300
435
  };
301
436
  const respondViaAISDK = ({ approvalId, approved, reason }) => Promise.resolve(chatHelpers.addToolApprovalResponse({
@@ -304,38 +439,51 @@ const useAISDKRuntime = (chatHelpers, adapter = {}) => {
304
439
  ...reason != null && { reason },
305
440
  options: { metadata: lastRunConfigRef.current }
306
441
  }));
307
- const respondViaHost = async (onRespond, response) => {
308
- const { approvalId: approvalId_0 } = response;
442
+ const respondViaHost = async (onRespond, response_0) => {
443
+ const { approvalId: approvalId_0 } = response_0;
309
444
  const requested = chatHelpers.messages.flatMap((message_0) => message_0.parts.flatMap((part_0) => isToolUIPart(part_0) ? [{
310
445
  messageId: message_0.id,
311
446
  part: part_0
312
- }] : [])).find(({ part: part_1 }) => part_1.state === "approval-requested" && part_1.approval.id === approvalId_0);
447
+ }] : [])).find(({ part: part_1 }) => part_1.state === "approval-requested" && part_1.approval?.id === approvalId_0);
313
448
  if (!requested || hostApprovalIdsRef.current.has(approvalId_0)) throw new Error(`Tool approval ${approvalId_0} is not waiting for a response.`);
449
+ const startedWith = ownedApprovals;
450
+ const startedOwner = approvalOwner;
451
+ const startedResponses = toolApprovalResponsesRef.current;
452
+ let isApplied = false;
314
453
  const applyResponse = (applied) => {
454
+ isApplied = applied;
455
+ if (applied) startedWith?.set(approvalId_0, { response: response_0 });
456
+ else startedWith?.delete(approvalId_0);
457
+ if (startedOwner) {
458
+ notifyHostApprovals(startedOwner);
459
+ return;
460
+ }
461
+ if (lastApprovalOwnerRef.current !== startedOwner) return;
315
462
  if (applied) hostApprovalIdsRef.current.add(approvalId_0);
316
463
  else hostApprovalIdsRef.current.delete(approvalId_0);
317
- if (applied) toolApprovalResponsesRef.current.set(approvalId_0, response);
464
+ if (applied) toolApprovalResponsesRef.current.set(approvalId_0, response_0);
318
465
  else toolApprovalResponsesRef.current.delete(approvalId_0);
319
466
  setToolApprovalResponses(new Map(toolApprovalResponsesRef.current));
320
467
  };
321
468
  applyResponse(true);
322
469
  try {
323
- await onRespond(response, {
470
+ await onRespond(response_0, {
324
471
  toolCallId: requested.part.toolCallId,
325
472
  toolName: getToolName(requested.part),
326
473
  respondViaAISDK: async () => {
327
474
  try {
328
- await respondViaAISDK(response);
475
+ await respondViaAISDK(response_0);
329
476
  } finally {
330
477
  applyResponse(false);
331
478
  }
332
479
  }
333
480
  });
334
481
  } catch (error_0) {
335
- if (hostApprovalIdsRef.current.has(approvalId_0)) applyResponse(false);
482
+ if (isApplied) applyResponse(false);
336
483
  throw error_0;
337
484
  }
338
- if (hostApprovalIdsRef.current.has(approvalId_0)) await persistToolApprovalResponses(requested.messageId);
485
+ const remainsApplied = startedWith?.has(approvalId_0) ?? startedResponses.has(approvalId_0);
486
+ if (isApplied && remainsApplied) await persistToolApprovalResponses(requested.messageId);
339
487
  };
340
488
  const hasSeededRepositoryRef = useRef(false);
341
489
  const shouldFeedRepository = exportedMessageRepository != null && !hasSeededRepositoryRef.current && messages.length === 0;
@@ -377,8 +525,8 @@ const useAISDKRuntime = (chatHelpers, adapter = {}) => {
377
525
  const cancelledId = isRunning && message_2?.role === "assistant" ? message_2.id : void 0;
378
526
  if (cancelledId) {
379
527
  const liveIds = new Set(chatHelpers.messages.map((m) => m.id));
380
- setCancelledMessages((prev_0) => {
381
- const kept = prev_0?.chatId === chatHelpers.id ? [...prev_0.ids].filter((id) => liveIds.has(id)) : [];
528
+ setCancelledMessages((prev_1) => {
529
+ const kept = prev_1?.chatId === chatHelpers.id ? [...prev_1.ids].filter((id_5) => liveIds.has(id_5)) : [];
382
530
  return {
383
531
  chatId: chatHelpers.id,
384
532
  ids: /* @__PURE__ */ new Set([...kept, cancelledId])
@@ -427,13 +575,17 @@ const useAISDKRuntime = (chatHelpers, adapter = {}) => {
427
575
  removedToolArtifact = toolArtifactsRef.current.delete(part_2.toolCallId) || removedToolArtifact;
428
576
  removedToolInteractions = toolInteractionsRef.current.delete(part_2.toolCallId) || removedToolInteractions;
429
577
  if (part_2.approval) {
578
+ ownedApprovals?.delete(part_2.approval.id);
430
579
  removedToolApprovalResponse = toolApprovalResponsesRef.current.delete(part_2.approval.id) || removedToolApprovalResponse;
431
580
  removedHostApprovalId = hostApprovalIdsRef.current.delete(part_2.approval.id) || removedHostApprovalId;
432
581
  }
433
582
  }
434
583
  if (removedToolArtifact) markToolArtifactsChanged();
435
584
  if (removedToolInteractions) markToolInteractionsChanged();
436
- if (removedToolApprovalResponse || removedHostApprovalId) setToolApprovalResponses(new Map(toolApprovalResponsesRef.current));
585
+ if (removedToolApprovalResponse || removedHostApprovalId) {
586
+ if (approvalOwner) notifyHostApprovals(approvalOwner);
587
+ setToolApprovalResponses(new Map(toolApprovalResponsesRef.current));
588
+ }
437
589
  const deleteIds = new Set(getExternalStoreMessages(threadMessages[messageIndex]).map((message_6) => message_6.id));
438
590
  chatHelpers.setMessages((current_3) => current_3.filter((message_7) => !deleteIds.has(message_7.id)));
439
591
  },
@@ -443,34 +595,60 @@ const useAISDKRuntime = (chatHelpers, adapter = {}) => {
443
595
  chatHelpers.setMessages(newMessages);
444
596
  await chatHelpers.regenerate({ metadata: config.runConfig });
445
597
  },
446
- onAddToolResult: ({ toolCallId, toolName, result: result_0, isError, artifact, modelContent }) => {
598
+ onAddToolResult: ({ messageId: messageId_1, toolCallId, toolName, result: result_0, isError, artifact, modelContent }) => {
447
599
  if (artifact !== void 0) {
448
600
  toolArtifactsRef.current.set(toolCallId, artifact);
449
601
  markToolArtifactsChanged();
450
602
  }
603
+ const targetIndex = findRawToolMessageIndex(chatHelpers.messages, messageId_1, toolCallId, joinStrategy);
604
+ const errorText = typeof result_0 === "string" ? result_0 : JSON.stringify(result_0);
605
+ const output = !isError && modelContent !== void 0 ? wrapModelContentEnvelope(result_0, modelContent) : result_0;
606
+ if (targetIndex >= 0 && targetIndex !== chatHelpers.messages.length - 1) {
607
+ const target = chatHelpers.messages[targetIndex];
608
+ const targetPart = target.parts.find((part_3) => isToolUIPart(part_3) && part_3.toolCallId === toolCallId);
609
+ if (targetPart?.state === "output-error" || targetPart?.state === "output-denied" || targetPart?.state === "output-available" && !targetPart.preliminary) return Promise.resolve();
610
+ const targetMessageId = target.id;
611
+ chatHelpers.setMessages((current_4) => current_4.map((message_8) => {
612
+ if (message_8.id !== targetMessageId) return message_8;
613
+ return {
614
+ ...message_8,
615
+ parts: message_8.parts.map((part_4) => {
616
+ if (!isToolUIPart(part_4) || part_4.toolCallId !== toolCallId) return part_4;
617
+ const { preliminary: _preliminary, ...finalPart } = part_4;
618
+ return {
619
+ ...finalPart,
620
+ state: isError ? "output-error" : "output-available",
621
+ output: isError ? void 0 : output,
622
+ errorText: isError ? errorText : void 0
623
+ };
624
+ })
625
+ };
626
+ }));
627
+ return Promise.resolve();
628
+ }
451
629
  const options = { metadata: lastRunConfigRef.current };
452
630
  if (isError) return Promise.resolve(chatHelpers.addToolOutput({
453
631
  state: "output-error",
454
632
  tool: toolName ?? toolCallId,
455
633
  toolCallId,
456
- errorText: typeof result_0 === "string" ? result_0 : JSON.stringify(result_0),
634
+ errorText,
457
635
  options
458
636
  }));
459
- else {
460
- const output = modelContent !== void 0 ? wrapModelContentEnvelope(result_0, modelContent) : result_0;
461
- return Promise.resolve(chatHelpers.addToolOutput({
462
- tool: toolName,
463
- toolCallId,
464
- output,
465
- options
466
- }));
467
- }
637
+ return Promise.resolve(chatHelpers.addToolOutput({
638
+ tool: toolName,
639
+ toolCallId,
640
+ output,
641
+ options
642
+ }));
643
+ },
644
+ onRespondToToolApproval: (response_1) => {
645
+ if (supersededApprovalProjection.approvalIds.has(response_1.approvalId)) return Promise.reject(/* @__PURE__ */ new Error(`Tool approval ${response_1.approvalId} is not waiting for a response.`));
646
+ return customOnRespondToToolApproval ? respondViaHost(customOnRespondToToolApproval, response_1) : respondViaAISDK(response_1);
468
647
  },
469
- onRespondToToolApproval: customOnRespondToToolApproval ? (response_0) => respondViaHost(customOnRespondToToolApproval, response_0) : respondViaAISDK,
470
- unstable_onRecordToolInteraction: ({ messageId: messageId_1, toolCallId: toolCallId_0, interaction }) => {
648
+ unstable_onRecordToolInteraction: ({ messageId: messageId_2, toolCallId: toolCallId_0, interaction }) => {
471
649
  toolInteractionsRef.current.set(toolCallId_0, appendToolInteraction(toolInteractionsRef.current.get(toolCallId_0), interaction));
472
650
  markToolInteractionsChanged();
473
- return persistToolInteractions(messageId_1);
651
+ return persistToolInteractions(messageId_2);
474
652
  },
475
653
  ...pickExternalStoreSharedOptions(adapter),
476
654
  ...adapter.unstable_messageRepositoryInstance && { unstable_messageRepositoryInstance: adapter.unstable_messageRepositoryInstance },