@assistant-ui/ai-sdk 0.0.10 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/converters/convertMessage.d.ts +8 -6
- package/dist/converters/convertMessage.d.ts.map +1 -1
- package/dist/converters/convertMessage.js +106 -28
- package/dist/converters/convertMessage.js.map +1 -1
- package/dist/converters/toolApprovalAnswers.d.ts +3 -0
- package/dist/converters/toolApprovalAnswers.d.ts.map +1 -0
- package/dist/converters/toolApprovalAnswers.js +17 -0
- package/dist/converters/toolApprovalAnswers.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.native.d.ts +1 -1
- package/dist/index.native.d.ts.map +1 -1
- package/dist/index.native.js +1 -1
- package/dist/model-context/injectInteractableContext.d.ts +3 -0
- package/dist/model-context/injectInteractableContext.d.ts.map +1 -1
- package/dist/model-context/injectInteractableContext.js +3 -0
- package/dist/model-context/injectInteractableContext.js.map +1 -1
- package/dist/model-context/injectQuoteContext.d.ts +1 -0
- package/dist/model-context/injectQuoteContext.d.ts.map +1 -1
- package/dist/model-context/injectQuoteContext.js +1 -0
- package/dist/model-context/injectQuoteContext.js.map +1 -1
- package/dist/runtime/AISDKChat.js +1 -1
- package/dist/runtime/AISDKThreads.d.ts +5 -0
- package/dist/runtime/AISDKThreads.d.ts.map +1 -1
- package/dist/runtime/AISDKThreads.js +37 -4
- package/dist/runtime/AISDKThreads.js.map +1 -1
- package/dist/runtime/DynamicChatTransport.d.ts +49 -0
- package/dist/runtime/DynamicChatTransport.d.ts.map +1 -0
- package/dist/runtime/DynamicChatTransport.js +147 -0
- package/dist/runtime/DynamicChatTransport.js.map +1 -0
- package/dist/runtime/getResumableAdapter.d.ts +5 -0
- package/dist/runtime/getResumableAdapter.d.ts.map +1 -0
- package/dist/runtime/getResumableAdapter.js +12 -0
- package/dist/runtime/getResumableAdapter.js.map +1 -0
- package/dist/runtime/sdkIdentity.js +1 -1
- package/dist/runtime/toolHistoryCodec.d.ts +20 -0
- package/dist/runtime/toolHistoryCodec.d.ts.map +1 -0
- package/dist/runtime/toolHistoryCodec.js +107 -0
- package/dist/runtime/toolHistoryCodec.js.map +1 -0
- package/dist/runtime/useAISDKRuntime.d.ts +4 -1
- package/dist/runtime/useAISDKRuntime.d.ts.map +1 -1
- package/dist/runtime/useAISDKRuntime.js +154 -155
- package/dist/runtime/useAISDKRuntime.js.map +1 -1
- package/dist/runtime/useChatRuntime.d.ts +14 -2
- package/dist/runtime/useChatRuntime.d.ts.map +1 -1
- package/dist/runtime/useChatRuntime.js +12 -3
- package/dist/runtime/useChatRuntime.js.map +1 -1
- package/dist/runtime/useChatThread.d.ts +3 -2
- package/dist/runtime/useChatThread.d.ts.map +1 -1
- package/dist/runtime/useChatThread.js +56 -36
- package/dist/runtime/useChatThread.js.map +1 -1
- package/dist/runtime/useDynamicChatTransport.d.ts +4 -0
- package/dist/runtime/useDynamicChatTransport.d.ts.map +1 -0
- package/dist/runtime/useDynamicChatTransport.js +64 -0
- package/dist/runtime/useDynamicChatTransport.js.map +1 -0
- package/dist/runtime/useExternalHistory.d.ts.map +1 -1
- package/dist/runtime/useExternalHistory.js +11 -105
- package/dist/runtime/useExternalHistory.js.map +1 -1
- package/dist/runtime/useResourceCleanup.js +1 -1
- package/dist/runtime/useStreamingTiming.js +2 -2
- package/dist/runtime/useStreamingTiming.js.map +1 -1
- package/dist/tools/generativeTools.d.ts +2 -1
- package/dist/tools/generativeTools.d.ts.map +1 -1
- package/dist/tools/generativeTools.js +17 -6
- package/dist/tools/generativeTools.js.map +1 -1
- package/dist/usage.js +1 -1
- package/dist/utils/sliceMessagesUntil.d.ts.map +1 -1
- package/dist/utils/sliceMessagesUntil.js +1 -2
- package/dist/utils/sliceMessagesUntil.js.map +1 -1
- package/package.json +12 -10
- package/src/converters/convertMessage.test.ts +350 -2
- package/src/converters/convertMessage.tool-args-status.test.tsx +185 -0
- package/src/converters/convertMessage.ts +154 -23
- package/src/converters/toCreateMessage.test.ts +27 -0
- package/src/converters/toolApprovalAnswers.ts +27 -0
- package/src/index.native.ts +1 -1
- package/src/index.ts +1 -1
- package/src/model-context/injectInteractableContext.ts +3 -0
- package/src/model-context/injectQuoteContext.ts +1 -0
- package/src/runtime/AISDKChat.integration.test.tsx +57 -2
- package/src/runtime/AISDKThreads.cloud.test.ts +3 -0
- package/src/runtime/AISDKThreads.test.ts +181 -0
- package/src/runtime/AISDKThreads.ts +31 -4
- package/src/runtime/DynamicChatTransport.test.ts +203 -0
- package/src/runtime/DynamicChatTransport.ts +273 -0
- package/src/runtime/__tests__/controlled-transport.ts +3 -0
- package/src/runtime/getResumableAdapter.ts +16 -0
- package/src/runtime/toolHistoryCodec.test.ts +161 -0
- package/src/runtime/toolHistoryCodec.ts +207 -0
- package/src/runtime/useAISDKRuntime.approval.test.tsx +12 -0
- package/src/runtime/useAISDKRuntime.reload.test.tsx +219 -0
- package/src/runtime/useAISDKRuntime.test.ts +538 -5
- package/src/runtime/useAISDKRuntime.ts +161 -51
- package/src/runtime/useChatRuntime.integration.test.tsx +319 -4
- package/src/runtime/useChatRuntime.local-storage.test.tsx +123 -0
- package/src/runtime/useChatRuntime.test.ts +107 -1
- package/src/runtime/useChatRuntime.ts +26 -4
- package/src/runtime/useChatThread.binding.test.tsx +143 -0
- package/src/runtime/useChatThread.ts +98 -81
- package/src/runtime/useDynamicChatTransport.ts +26 -0
- package/src/runtime/useExternalHistory.test.ts +205 -0
- package/src/runtime/useExternalHistory.ts +14 -206
- package/src/runtime/useStreamingTiming.ts +2 -2
- package/src/tools/generativeTools.test.ts +190 -2
- package/src/tools/generativeTools.ts +28 -8
- package/src/utils/sliceMessagesUntil.test.ts +2 -6
- package/src/utils/sliceMessagesUntil.ts +1 -5
- package/dist/converters/modelContentEnvelope.d.ts +0 -14
- package/dist/converters/modelContentEnvelope.d.ts.map +0 -1
- package/dist/converters/modelContentEnvelope.js +0 -22
- package/dist/converters/modelContentEnvelope.js.map +0 -1
- package/dist/converters/toolOutputConversion.d.ts +0 -26
- package/dist/converters/toolOutputConversion.d.ts.map +0 -1
- package/dist/converters/toolOutputConversion.js +0 -31
- package/dist/converters/toolOutputConversion.js.map +0 -1
- package/dist/tools/frontendTools.d.ts +0 -30
- package/dist/tools/frontendTools.d.ts.map +0 -1
- package/dist/tools/frontendTools.js +0 -33
- package/dist/tools/frontendTools.js.map +0 -1
- package/src/converters/modelContentEnvelope.ts +0 -41
- package/src/converters/toolOutputConversion.ts +0 -26
- package/src/tools/frontendTools.test.ts +0 -205
- package/src/tools/frontendTools.ts +0 -83
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
useMemo,
|
|
8
8
|
useRef,
|
|
9
9
|
useState,
|
|
10
|
+
useSyncExternalStore,
|
|
10
11
|
} from "react";
|
|
11
12
|
import type {
|
|
12
13
|
UIMessage,
|
|
@@ -60,7 +61,7 @@ import {
|
|
|
60
61
|
AISDKMessageConverter,
|
|
61
62
|
type AISDKMessageConverterMetadata,
|
|
62
63
|
} from "../converters/convertMessage";
|
|
63
|
-
import { wrapModelContentEnvelope } from "
|
|
64
|
+
import { wrapModelContentEnvelope } from "assistant-stream/internal";
|
|
64
65
|
import {
|
|
65
66
|
type AISDKStorageFormat,
|
|
66
67
|
aiSDKV6FormatAdapter,
|
|
@@ -114,12 +115,15 @@ export type AISDKRuntimeAdapter<UI_MESSAGE extends UIMessage = UIMessage> =
|
|
|
114
115
|
})
|
|
115
116
|
| undefined;
|
|
116
117
|
toCreateMessage?: CustomToCreateMessageFunction;
|
|
118
|
+
/** @deprecated Experimental since 2026-08-28. Not scheduled for removal; the API may change in any release. */
|
|
117
119
|
unstable_messageRepositoryInstance?: MessageRepository | undefined;
|
|
118
120
|
/**
|
|
119
121
|
* The object a host answer belongs to, normally the `Chat` the runtime
|
|
120
122
|
* renders. A host answer never reaches the `useChat` messages, so without
|
|
121
123
|
* an owner it lives only as long as this runtime. With an owner, it
|
|
122
124
|
* survives runtime remounts over the same chat.
|
|
125
|
+
*
|
|
126
|
+
* @deprecated Experimental since 2026-09-29. Not scheduled for removal; the API may change in any release.
|
|
123
127
|
*/
|
|
124
128
|
unstable_hostApprovalOwner?: object | undefined;
|
|
125
129
|
/**
|
|
@@ -183,7 +187,7 @@ export type AISDKRuntimeAdapter<UI_MESSAGE extends UIMessage = UIMessage> =
|
|
|
183
187
|
* Called after an explicit `switchToBranch` (for example a BranchPicker
|
|
184
188
|
* click). Complements `setMessages` and does not enable switching by itself.
|
|
185
189
|
*
|
|
186
|
-
* @deprecated
|
|
190
|
+
* @deprecated Experimental since 2026-06-23. Not scheduled for removal; the API may change in any release.
|
|
187
191
|
*/
|
|
188
192
|
unstable_onBranchChange?: ExternalStoreAdapter["unstable_onBranchChange"];
|
|
189
193
|
};
|
|
@@ -203,11 +207,25 @@ const useGeneratedSuggestions = (
|
|
|
203
207
|
useInsertionEffect(() => {
|
|
204
208
|
messagesRef.current = messages;
|
|
205
209
|
}, [messages]);
|
|
210
|
+
const adapterKey = suggestionAdapter?.key;
|
|
206
211
|
const adapterRef = useRef(suggestionAdapter);
|
|
212
|
+
const lastAdapterKeyRef = useRef(adapterKey);
|
|
213
|
+
const adapterReplacedRef = useRef(false);
|
|
207
214
|
useInsertionEffect(() => {
|
|
215
|
+
const previousAdapter = adapterRef.current;
|
|
208
216
|
adapterRef.current = suggestionAdapter;
|
|
209
|
-
|
|
210
|
-
|
|
217
|
+
const adapterReplaced =
|
|
218
|
+
previousAdapter !== undefined &&
|
|
219
|
+
suggestionAdapter !== undefined &&
|
|
220
|
+
lastAdapterKeyRef.current !== adapterKey;
|
|
221
|
+
adapterReplacedRef.current ||= adapterReplaced;
|
|
222
|
+
if (suggestionAdapter === undefined || adapterReplaced) {
|
|
223
|
+
controllerRef.current?.abort();
|
|
224
|
+
}
|
|
225
|
+
lastAdapterKeyRef.current = adapterKey;
|
|
226
|
+
}, [adapterKey, suggestionAdapter]);
|
|
227
|
+
const hasAdapter = suggestionAdapter !== undefined;
|
|
228
|
+
const adapterPresentRef = useRef(hasAdapter);
|
|
211
229
|
|
|
212
230
|
useEffect(() => {
|
|
213
231
|
const clearSuggestions = () => {
|
|
@@ -217,12 +235,18 @@ const useGeneratedSuggestions = (
|
|
|
217
235
|
};
|
|
218
236
|
|
|
219
237
|
const adapter = adapterRef.current;
|
|
238
|
+
const adapterRestored = hasAdapter && !adapterPresentRef.current;
|
|
239
|
+
adapterPresentRef.current = hasAdapter;
|
|
240
|
+
const adapterReplaced = adapterReplacedRef.current;
|
|
241
|
+
adapterReplacedRef.current = false;
|
|
220
242
|
if (!adapter) {
|
|
221
243
|
clearSuggestions();
|
|
222
244
|
wasRunningRef.current = isRunning;
|
|
223
245
|
return;
|
|
224
246
|
}
|
|
225
247
|
|
|
248
|
+
if (adapterReplaced) clearSuggestions();
|
|
249
|
+
|
|
226
250
|
if (isRunning) {
|
|
227
251
|
if (!wasRunningRef.current) {
|
|
228
252
|
clearSuggestions();
|
|
@@ -231,7 +255,7 @@ const useGeneratedSuggestions = (
|
|
|
231
255
|
return;
|
|
232
256
|
}
|
|
233
257
|
|
|
234
|
-
if (!wasRunningRef.current) return;
|
|
258
|
+
if (!wasRunningRef.current && !adapterReplaced && !adapterRestored) return;
|
|
235
259
|
wasRunningRef.current = false;
|
|
236
260
|
|
|
237
261
|
const currentMessages = messagesRef.current;
|
|
@@ -252,11 +276,14 @@ const useGeneratedSuggestions = (
|
|
|
252
276
|
|
|
253
277
|
await consumeSuggestionResult(promiseOrGenerator, {
|
|
254
278
|
signal,
|
|
255
|
-
onUpdate:
|
|
279
|
+
onUpdate: (nextSuggestions) => {
|
|
280
|
+
if (controllerRef.current !== controller) return;
|
|
281
|
+
setSuggestions(nextSuggestions);
|
|
282
|
+
},
|
|
256
283
|
});
|
|
257
284
|
} catch {}
|
|
258
285
|
})();
|
|
259
|
-
}, [hasAdapter, isRunning]);
|
|
286
|
+
}, [adapterKey, hasAdapter, isRunning]);
|
|
260
287
|
|
|
261
288
|
useReplaySafeEffect(() => {
|
|
262
289
|
return () => {
|
|
@@ -290,10 +317,25 @@ type OwnedApproval = {
|
|
|
290
317
|
response: RespondToToolApprovalOptions;
|
|
291
318
|
};
|
|
292
319
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
320
|
+
type ChatRuntimeState = {
|
|
321
|
+
approvals: Map<string, OwnedApproval>;
|
|
322
|
+
cancelledMessageIds: ReadonlySet<string>;
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
const chatRuntimeStates = new WeakMap<object, ChatRuntimeState>();
|
|
326
|
+
|
|
327
|
+
const getChatRuntimeState = (owner: object | undefined) => {
|
|
328
|
+
if (owner === undefined) return undefined;
|
|
329
|
+
let state = chatRuntimeStates.get(owner);
|
|
330
|
+
if (state === undefined) {
|
|
331
|
+
state = {
|
|
332
|
+
approvals: new Map(),
|
|
333
|
+
cancelledMessageIds: NO_CANCELLED_MESSAGE_IDS,
|
|
334
|
+
};
|
|
335
|
+
chatRuntimeStates.set(owner, state);
|
|
336
|
+
}
|
|
337
|
+
return state;
|
|
338
|
+
};
|
|
297
339
|
|
|
298
340
|
const toApprovalResponses = (
|
|
299
341
|
owned: ReadonlyMap<string, OwnedApproval> | undefined,
|
|
@@ -308,19 +350,19 @@ const toApprovalResponses = (
|
|
|
308
350
|
* may already be unmounted (a rollback resolving after a remount), and another
|
|
309
351
|
* runtime may be mounted over the same owner.
|
|
310
352
|
*/
|
|
311
|
-
const
|
|
353
|
+
const chatRuntimeListeners = new WeakMap<object, Set<() => void>>();
|
|
312
354
|
|
|
313
|
-
const
|
|
314
|
-
const listeners =
|
|
315
|
-
|
|
355
|
+
const subscribeToChatRuntimeState = (owner: object, listener: () => void) => {
|
|
356
|
+
const listeners = chatRuntimeListeners.get(owner) ?? new Set();
|
|
357
|
+
chatRuntimeListeners.set(owner, listeners);
|
|
316
358
|
listeners.add(listener);
|
|
317
359
|
return () => {
|
|
318
360
|
listeners.delete(listener);
|
|
319
361
|
};
|
|
320
362
|
};
|
|
321
363
|
|
|
322
|
-
const
|
|
323
|
-
for (const listener of [...(
|
|
364
|
+
const notifyChatRuntimeState = (owner: object) => {
|
|
365
|
+
for (const listener of [...(chatRuntimeListeners.get(owner) ?? [])]) {
|
|
324
366
|
listener();
|
|
325
367
|
}
|
|
326
368
|
};
|
|
@@ -343,6 +385,7 @@ const getSupersededApprovalProjection = <UI_MESSAGE extends UIMessage>(
|
|
|
343
385
|
lastAssistant === messages[lastIndex] &&
|
|
344
386
|
lastAssistant?.parts?.some((part) => {
|
|
345
387
|
if (
|
|
388
|
+
typeof part?.type !== "string" ||
|
|
346
389
|
!isToolUIPart(part) ||
|
|
347
390
|
part.state === "output-available" ||
|
|
348
391
|
part.state === "output-error" ||
|
|
@@ -391,7 +434,12 @@ const getSupersededApprovalProjection = <UI_MESSAGE extends UIMessage>(
|
|
|
391
434
|
}
|
|
392
435
|
|
|
393
436
|
for (const part of message.parts ?? []) {
|
|
394
|
-
if (
|
|
437
|
+
if (
|
|
438
|
+
typeof part?.type !== "string" ||
|
|
439
|
+
!isToolUIPart(part) ||
|
|
440
|
+
part.state !== "approval-requested"
|
|
441
|
+
)
|
|
442
|
+
continue;
|
|
395
443
|
|
|
396
444
|
const approval = part.approval;
|
|
397
445
|
if (!approval) continue;
|
|
@@ -427,7 +475,10 @@ const findRawToolMessageIndex = <UI_MESSAGE extends UIMessage>(
|
|
|
427
475
|
) => {
|
|
428
476
|
const containsToolCall = (message: UI_MESSAGE) =>
|
|
429
477
|
message.parts?.some(
|
|
430
|
-
(part) =>
|
|
478
|
+
(part) =>
|
|
479
|
+
typeof part?.type === "string" &&
|
|
480
|
+
isToolUIPart(part) &&
|
|
481
|
+
part.toolCallId === toolCallId,
|
|
431
482
|
) === true;
|
|
432
483
|
|
|
433
484
|
const messageIndex = messages.findIndex(
|
|
@@ -491,14 +542,22 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
491
542
|
// Set by hosts that own the chat across runtime lifetimes, so a host answer
|
|
492
543
|
// outlives a remount over that same chat.
|
|
493
544
|
const approvalOwner = adapter.unstable_hostApprovalOwner;
|
|
494
|
-
const
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
545
|
+
const chatRuntimeState = getChatRuntimeState(approvalOwner);
|
|
546
|
+
const ownedApprovals = chatRuntimeState?.approvals;
|
|
547
|
+
const ownedCancelledMessageIds = useSyncExternalStore(
|
|
548
|
+
useCallback(
|
|
549
|
+
(listener) =>
|
|
550
|
+
approvalOwner
|
|
551
|
+
? subscribeToChatRuntimeState(approvalOwner, listener)
|
|
552
|
+
: () => {},
|
|
553
|
+
[approvalOwner],
|
|
554
|
+
),
|
|
555
|
+
useCallback(
|
|
556
|
+
() => chatRuntimeState?.cancelledMessageIds ?? NO_CANCELLED_MESSAGE_IDS,
|
|
557
|
+
[chatRuntimeState],
|
|
558
|
+
),
|
|
559
|
+
() => NO_CANCELLED_MESSAGE_IDS,
|
|
560
|
+
);
|
|
502
561
|
const [toolApprovalResponses, setToolApprovalResponses] = useState<
|
|
503
562
|
ReadonlyMap<string, RespondToToolApprovalOptions>
|
|
504
563
|
>(() => toApprovalResponses(ownedApprovals));
|
|
@@ -532,7 +591,7 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
532
591
|
return unchanged ? prev : next;
|
|
533
592
|
});
|
|
534
593
|
};
|
|
535
|
-
const unsubscribe =
|
|
594
|
+
const unsubscribe = subscribeToChatRuntimeState(approvalOwner, sync);
|
|
536
595
|
// The state was seeded during render, so a write landing between then and
|
|
537
596
|
// this subscription would otherwise never be seen.
|
|
538
597
|
sync();
|
|
@@ -559,7 +618,7 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
559
618
|
new Map(),
|
|
560
619
|
);
|
|
561
620
|
const toolArgsTextCacheRef = useRef<
|
|
562
|
-
|
|
621
|
+
NonNullable<AISDKMessageConverterMetadata["toolArgsTextCache"]>
|
|
563
622
|
>(new WeakMap());
|
|
564
623
|
const mcpAppMetadataCacheRef = useRef<Map<string, McpAppMetadata>>(new Map());
|
|
565
624
|
const toolArtifactsRef = useRef<Map<string, unknown>>(new Map());
|
|
@@ -590,8 +649,9 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
590
649
|
const optimisticMessageId =
|
|
591
650
|
isRunning && lastMessage?.role === "assistant" ? lastMessage.id : undefined;
|
|
592
651
|
|
|
593
|
-
const cancelledMessageIds =
|
|
594
|
-
|
|
652
|
+
const cancelledMessageIds = chatRuntimeState
|
|
653
|
+
? ownedCancelledMessageIds
|
|
654
|
+
: cancelledMessages?.chatId === chatHelpers.id
|
|
595
655
|
? cancelledMessages.ids
|
|
596
656
|
: NO_CANCELLED_MESSAGE_IDS;
|
|
597
657
|
const supportsRichToolApprovalResponses =
|
|
@@ -646,6 +706,14 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
646
706
|
|
|
647
707
|
const retractCancellation = useCallback(
|
|
648
708
|
(chatId: string, messageId: string) => {
|
|
709
|
+
if (chatRuntimeState !== undefined && approvalOwner !== undefined) {
|
|
710
|
+
if (!chatRuntimeState.cancelledMessageIds.has(messageId)) return;
|
|
711
|
+
const ids = new Set(chatRuntimeState.cancelledMessageIds);
|
|
712
|
+
ids.delete(messageId);
|
|
713
|
+
chatRuntimeState.cancelledMessageIds = ids;
|
|
714
|
+
notifyChatRuntimeState(approvalOwner);
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
649
717
|
setCancelledMessages((prev) => {
|
|
650
718
|
if (prev?.chatId !== chatId || !prev.ids.has(messageId)) return prev;
|
|
651
719
|
const ids = new Set(prev.ids);
|
|
@@ -653,7 +721,7 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
653
721
|
return { chatId, ids };
|
|
654
722
|
});
|
|
655
723
|
},
|
|
656
|
-
[],
|
|
724
|
+
[approvalOwner, chatRuntimeState],
|
|
657
725
|
);
|
|
658
726
|
|
|
659
727
|
// A provider run that resumes the stopped response retracts its cancellation;
|
|
@@ -801,7 +869,8 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
801
869
|
|
|
802
870
|
let messageChanged = false;
|
|
803
871
|
const parts = message.parts?.map((part) => {
|
|
804
|
-
if (!isToolUIPart(part))
|
|
872
|
+
if (typeof part?.type !== "string" || !isToolUIPart(part))
|
|
873
|
+
return part;
|
|
805
874
|
if (
|
|
806
875
|
part.state === "output-available" ||
|
|
807
876
|
part.state === "output-error" ||
|
|
@@ -850,7 +919,9 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
850
919
|
const requested = chatHelpers.messages
|
|
851
920
|
.flatMap((message) =>
|
|
852
921
|
message.parts.flatMap((part) =>
|
|
853
|
-
|
|
922
|
+
typeof part?.type === "string" && isToolUIPart(part)
|
|
923
|
+
? [{ messageId: message.id, part }]
|
|
924
|
+
: [],
|
|
854
925
|
),
|
|
855
926
|
)
|
|
856
927
|
.find(
|
|
@@ -888,7 +959,7 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
888
959
|
if (startedOwner) {
|
|
889
960
|
// Every runtime mounted over that owner re-reads the record, including
|
|
890
961
|
// one mounted after this response started.
|
|
891
|
-
|
|
962
|
+
notifyChatRuntimeState(startedOwner);
|
|
892
963
|
return;
|
|
893
964
|
}
|
|
894
965
|
|
|
@@ -924,6 +995,20 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
924
995
|
}
|
|
925
996
|
};
|
|
926
997
|
|
|
998
|
+
// A thread message can join several AI SDK messages, so a slice through it
|
|
999
|
+
// has to end at the last of them rather than at the id the thread exposes.
|
|
1000
|
+
const lastAISDKMessageId = (threadMessageId: string | null) => {
|
|
1001
|
+
if (threadMessageId == null) return null;
|
|
1002
|
+
const threadMessage = runtimeRef.current.thread
|
|
1003
|
+
.getState()
|
|
1004
|
+
.messages.find((message) => message.id === threadMessageId);
|
|
1005
|
+
return (
|
|
1006
|
+
(threadMessage &&
|
|
1007
|
+
getVercelAIMessages<UI_MESSAGE>(threadMessage).at(-1)?.id) ??
|
|
1008
|
+
threadMessageId
|
|
1009
|
+
);
|
|
1010
|
+
};
|
|
1011
|
+
|
|
927
1012
|
const hasSeededRepositoryRef = useRef(false);
|
|
928
1013
|
const shouldFeedRepository =
|
|
929
1014
|
exportedMessageRepository != null &&
|
|
@@ -1008,16 +1093,26 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
1008
1093
|
isRunning && message?.role === "assistant" ? message.id : undefined;
|
|
1009
1094
|
if (cancelledId) {
|
|
1010
1095
|
const liveIds = new Set(chatHelpers.messages.map((m) => m.id));
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1096
|
+
if (chatRuntimeState !== undefined && approvalOwner !== undefined) {
|
|
1097
|
+
chatRuntimeState.cancelledMessageIds = new Set([
|
|
1098
|
+
...[...chatRuntimeState.cancelledMessageIds].filter((id) =>
|
|
1099
|
+
liveIds.has(id),
|
|
1100
|
+
),
|
|
1101
|
+
cancelledId,
|
|
1102
|
+
]);
|
|
1103
|
+
notifyChatRuntimeState(approvalOwner);
|
|
1104
|
+
} else {
|
|
1105
|
+
setCancelledMessages((prev) => {
|
|
1106
|
+
const kept =
|
|
1107
|
+
prev?.chatId === chatHelpers.id
|
|
1108
|
+
? [...prev.ids].filter((id) => liveIds.has(id))
|
|
1109
|
+
: [];
|
|
1110
|
+
return {
|
|
1111
|
+
chatId: chatHelpers.id,
|
|
1112
|
+
ids: new Set([...kept, cancelledId]),
|
|
1113
|
+
};
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1021
1116
|
}
|
|
1022
1117
|
try {
|
|
1023
1118
|
await chatHelpers.stop();
|
|
@@ -1052,9 +1147,10 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
1052
1147
|
customToCreateMessage ?? toCreateMessage
|
|
1053
1148
|
)<UI_MESSAGE>(message);
|
|
1054
1149
|
|
|
1150
|
+
const parentId = lastAISDKMessageId(message.parentId);
|
|
1055
1151
|
if (!(message.startRun ?? message.role === "user")) {
|
|
1056
1152
|
chatHelpers.setMessages((current) => [
|
|
1057
|
-
...sliceMessagesUntil(current,
|
|
1153
|
+
...sliceMessagesUntil(current, parentId),
|
|
1058
1154
|
toUIMessage<UI_MESSAGE>(createMessage, message.role),
|
|
1059
1155
|
]);
|
|
1060
1156
|
return;
|
|
@@ -1062,7 +1158,7 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
1062
1158
|
|
|
1063
1159
|
lastRunConfigRef.current = message.runConfig;
|
|
1064
1160
|
chatHelpers.setMessages((current) =>
|
|
1065
|
-
sliceMessagesUntil(current,
|
|
1161
|
+
sliceMessagesUntil(current, parentId),
|
|
1066
1162
|
);
|
|
1067
1163
|
await chatHelpers.sendMessage(createMessage, {
|
|
1068
1164
|
metadata: message.runConfig,
|
|
@@ -1103,7 +1199,7 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
1103
1199
|
if (removedToolArtifact) markToolArtifactsChanged();
|
|
1104
1200
|
if (removedToolInteractions) markToolInteractionsChanged();
|
|
1105
1201
|
if (removedToolApprovalResponse || removedHostApprovalId) {
|
|
1106
|
-
if (approvalOwner)
|
|
1202
|
+
if (approvalOwner) notifyChatRuntimeState(approvalOwner);
|
|
1107
1203
|
setToolApprovalResponses(new Map(toolApprovalResponsesRef.current));
|
|
1108
1204
|
}
|
|
1109
1205
|
|
|
@@ -1118,7 +1214,14 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
1118
1214
|
},
|
|
1119
1215
|
onReload: async (parentId: string | null, config) => {
|
|
1120
1216
|
lastRunConfigRef.current = config.runConfig;
|
|
1121
|
-
const newMessages = sliceMessagesUntil(
|
|
1217
|
+
const newMessages = sliceMessagesUntil(
|
|
1218
|
+
chatHelpers.messages,
|
|
1219
|
+
lastAISDKMessageId(parentId),
|
|
1220
|
+
);
|
|
1221
|
+
// regenerate drops a trailing assistant message, so the first message of
|
|
1222
|
+
// the reloaded response stays for it to drop instead of the parent.
|
|
1223
|
+
const reloaded = chatHelpers.messages[newMessages.length];
|
|
1224
|
+
if (reloaded?.role === "assistant") newMessages.push(reloaded);
|
|
1122
1225
|
chatHelpers.setMessages(newMessages);
|
|
1123
1226
|
|
|
1124
1227
|
await chatHelpers.regenerate({ metadata: config.runConfig });
|
|
@@ -1154,7 +1257,10 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
1154
1257
|
if (targetIndex >= 0 && targetIndex !== chatHelpers.messages.length - 1) {
|
|
1155
1258
|
const target = chatHelpers.messages[targetIndex]!;
|
|
1156
1259
|
const targetPart = target.parts.find(
|
|
1157
|
-
(part) =>
|
|
1260
|
+
(part) =>
|
|
1261
|
+
typeof part?.type === "string" &&
|
|
1262
|
+
isToolUIPart(part) &&
|
|
1263
|
+
part.toolCallId === toolCallId,
|
|
1158
1264
|
) as { state?: string; preliminary?: boolean } | undefined;
|
|
1159
1265
|
// An earlier message's settled output may already have reached the model, as the error a cancelling send writes does.
|
|
1160
1266
|
if (
|
|
@@ -1172,7 +1278,11 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
1172
1278
|
return {
|
|
1173
1279
|
...message,
|
|
1174
1280
|
parts: message.parts.map((part) => {
|
|
1175
|
-
if (
|
|
1281
|
+
if (
|
|
1282
|
+
typeof part?.type !== "string" ||
|
|
1283
|
+
!isToolUIPart(part) ||
|
|
1284
|
+
part.toolCallId !== toolCallId
|
|
1285
|
+
)
|
|
1176
1286
|
return part;
|
|
1177
1287
|
|
|
1178
1288
|
const { preliminary: _preliminary, ...finalPart } =
|