@assistant-ui/ai-sdk 0.0.6 → 0.0.8
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/README.md +1 -1
- package/dist/adapters/aiSDKFormatAdapter.d.ts +2 -2
- package/dist/adapters/aiSDKFormatAdapter.d.ts.map +1 -0
- package/dist/adapters/vercelAttachmentAdapter.d.ts +2 -5
- package/dist/adapters/vercelAttachmentAdapter.d.ts.map +1 -1
- package/dist/adapters/vercelAttachmentAdapter.js +3 -3
- package/dist/adapters/vercelAttachmentAdapter.js.map +1 -1
- package/dist/aiSDKExtras.d.ts +6 -9
- package/dist/aiSDKExtras.d.ts.map +1 -1
- package/dist/converters/convertMessage.d.ts +34 -25
- package/dist/converters/convertMessage.d.ts.map +1 -1
- package/dist/converters/convertMessage.js +78 -8
- package/dist/converters/convertMessage.js.map +1 -1
- package/dist/converters/modelContentEnvelope.d.ts +10 -12
- package/dist/converters/modelContentEnvelope.d.ts.map +1 -1
- package/dist/converters/toCreateMessage.d.ts +3 -6
- package/dist/converters/toCreateMessage.d.ts.map +1 -1
- package/dist/converters/toolOutputConversion.d.ts +22 -25
- package/dist/converters/toolOutputConversion.d.ts.map +1 -1
- package/dist/hooks.d.ts +4 -7
- package/dist/hooks.d.ts.map +1 -1
- package/dist/index.d.ts +17 -14
- package/dist/index.d.ts.map +1 -0
- package/dist/index.native.d.ts +15 -12
- package/dist/index.native.d.ts.map +1 -0
- package/dist/model-context/injectInteractableContext.d.ts +3 -6
- package/dist/model-context/injectInteractableContext.d.ts.map +1 -1
- package/dist/model-context/injectQuoteContext.d.ts +2 -5
- package/dist/model-context/injectQuoteContext.d.ts.map +1 -1
- package/dist/runtime/AISDKChat.d.ts +4 -7
- package/dist/runtime/AISDKChat.d.ts.map +1 -1
- package/dist/runtime/AISDKChat.js +1 -1
- package/dist/runtime/AISDKThreads.d.ts +29 -32
- package/dist/runtime/AISDKThreads.d.ts.map +1 -1
- package/dist/runtime/AISDKThreads.js +26 -33
- package/dist/runtime/AISDKThreads.js.map +1 -1
- package/dist/runtime/sdkIdentity.d.ts +2 -5
- package/dist/runtime/sdkIdentity.d.ts.map +1 -1
- package/dist/runtime/sdkIdentity.js +1 -1
- package/dist/runtime/useAISDKRuntime.d.ts +72 -61
- package/dist/runtime/useAISDKRuntime.d.ts.map +1 -1
- package/dist/runtime/useAISDKRuntime.js +170 -56
- package/dist/runtime/useAISDKRuntime.js.map +1 -1
- package/dist/runtime/useChatRuntime.d.ts +8 -11
- package/dist/runtime/useChatRuntime.d.ts.map +1 -1
- package/dist/runtime/useChatRuntime.js +14 -10
- package/dist/runtime/useChatRuntime.js.map +1 -1
- package/dist/runtime/useChatThread.d.ts +100 -81
- package/dist/runtime/useChatThread.d.ts.map +1 -1
- package/dist/runtime/useChatThread.js +33 -13
- package/dist/runtime/useChatThread.js.map +1 -1
- package/dist/runtime/useExternalHistory.d.ts +8 -9
- package/dist/runtime/useExternalHistory.d.ts.map +1 -1
- package/dist/runtime/useExternalHistory.js +236 -58
- package/dist/runtime/useExternalHistory.js.map +1 -1
- package/dist/runtime/useResourceCleanup.d.ts +1 -4
- package/dist/runtime/useResourceCleanup.d.ts.map +1 -1
- package/dist/runtime/useResourceCleanup.js +38 -14
- package/dist/runtime/useResourceCleanup.js.map +1 -1
- package/dist/runtime/useStreamingTiming.d.ts +5 -8
- package/dist/runtime/useStreamingTiming.d.ts.map +1 -1
- package/dist/runtime/useStreamingTiming.js +1 -1
- package/dist/tools/frontendTools.d.ts +24 -27
- package/dist/tools/frontendTools.d.ts.map +1 -1
- package/dist/tools/generativeTools.d.ts +29 -32
- package/dist/tools/generativeTools.d.ts.map +1 -1
- package/dist/tools/mcp-stdio.node.d.ts +2 -2
- package/dist/tools/mcp-stdio.node.d.ts.map +1 -0
- package/dist/tools/mcp-stdio.unsupported.d.ts +2 -5
- package/dist/tools/mcp-stdio.unsupported.d.ts.map +1 -1
- package/dist/transport/AssistantChatTransport.d.ts +17 -20
- package/dist/transport/AssistantChatTransport.d.ts.map +1 -1
- package/dist/transport/resumable.d.ts +29 -32
- package/dist/transport/resumable.d.ts.map +1 -1
- package/dist/usage.d.ts +12 -15
- package/dist/usage.d.ts.map +1 -1
- package/dist/usage.js.map +1 -1
- package/dist/utils/getVercelAIMessages.d.ts +3 -6
- package/dist/utils/getVercelAIMessages.d.ts.map +1 -1
- package/dist/utils/sliceMessagesUntil.d.ts +2 -5
- package/dist/utils/sliceMessagesUntil.d.ts.map +1 -1
- package/package.json +16 -14
- package/src/adapters/vercelAttachmentAdapter.test.ts +23 -0
- package/src/adapters/vercelAttachmentAdapter.ts +2 -2
- package/src/converters/convertMessage.test.ts +616 -3
- package/src/converters/convertMessage.ts +187 -12
- package/src/index.native.ts +1 -1
- package/src/index.ts +1 -1
- package/src/runtime/AISDKChat.test.ts +4 -5
- package/src/runtime/AISDKThreads.cloud.test.ts +60 -2
- package/src/runtime/AISDKThreads.test.ts +88 -51
- package/src/runtime/AISDKThreads.ts +4 -13
- package/src/runtime/useAISDKRuntime.approval.integration.test.tsx +338 -0
- package/src/runtime/useAISDKRuntime.approval.test.tsx +476 -4
- package/src/runtime/useAISDKRuntime.test.ts +282 -8
- package/src/runtime/useAISDKRuntime.ts +274 -46
- package/src/runtime/useAISDKRuntime.voice.test.tsx +319 -0
- package/src/runtime/useChatRuntime.integration.test.tsx +56 -0
- package/src/runtime/useChatRuntime.test.ts +77 -5
- package/src/runtime/useChatRuntime.ts +6 -2
- package/src/runtime/useChatThread.transport.test.tsx +149 -0
- package/src/runtime/useChatThread.ts +77 -10
- package/src/runtime/useExternalHistory.test.ts +989 -3
- package/src/runtime/useExternalHistory.ts +390 -24
- package/src/runtime/useResourceCleanup.ts +41 -15
- package/src/tools/generativeTools.test.ts +7 -1
- package/src/usage.ts +1 -1
|
@@ -14,7 +14,7 @@ import type {
|
|
|
14
14
|
CreateUIMessage,
|
|
15
15
|
UseChatHelpers,
|
|
16
16
|
} from "@ai-sdk/react";
|
|
17
|
-
import { isToolUIPart, generateId } from "ai";
|
|
17
|
+
import { isToolUIPart, generateId, getToolName } from "ai";
|
|
18
18
|
import {
|
|
19
19
|
useExternalStoreRuntime,
|
|
20
20
|
useRuntimeAdapters,
|
|
@@ -37,12 +37,15 @@ import type {
|
|
|
37
37
|
AppendMessage,
|
|
38
38
|
RunConfig,
|
|
39
39
|
McpAppMetadata,
|
|
40
|
+
RespondToToolApprovalOptions,
|
|
41
|
+
Unstable_ToolInteractionLog,
|
|
40
42
|
} from "@assistant-ui/core";
|
|
41
43
|
import {
|
|
42
44
|
getExternalStoreMessages,
|
|
43
45
|
pickExternalStoreSharedOptions,
|
|
44
46
|
} from "@assistant-ui/core";
|
|
45
47
|
import {
|
|
48
|
+
appendToolInteraction,
|
|
46
49
|
consumeSuggestionResult,
|
|
47
50
|
MessageRepository,
|
|
48
51
|
} from "@assistant-ui/core/internal";
|
|
@@ -52,7 +55,10 @@ import { sliceMessagesUntil } from "../utils/sliceMessagesUntil";
|
|
|
52
55
|
import { toCreateMessage } from "../converters/toCreateMessage";
|
|
53
56
|
import { vercelAttachmentAdapter } from "../adapters/vercelAttachmentAdapter";
|
|
54
57
|
import { getVercelAIMessages } from "../utils/getVercelAIMessages";
|
|
55
|
-
import {
|
|
58
|
+
import {
|
|
59
|
+
AISDKMessageConverter,
|
|
60
|
+
type AISDKMessageConverterMetadata,
|
|
61
|
+
} from "../converters/convertMessage";
|
|
56
62
|
import { wrapModelContentEnvelope } from "../converters/modelContentEnvelope";
|
|
57
63
|
import {
|
|
58
64
|
type AISDKStorageFormat,
|
|
@@ -81,6 +87,23 @@ const toUIMessage = <UI_MESSAGE extends UIMessage>(
|
|
|
81
87
|
role: createMessage.role ?? fallbackRole,
|
|
82
88
|
}) as UI_MESSAGE;
|
|
83
89
|
|
|
90
|
+
const toVoiceTranscriptUIMessage = <UI_MESSAGE extends UIMessage>(
|
|
91
|
+
message: ThreadMessage,
|
|
92
|
+
): UI_MESSAGE =>
|
|
93
|
+
({
|
|
94
|
+
id: message.id,
|
|
95
|
+
role: message.role,
|
|
96
|
+
parts: message.content
|
|
97
|
+
.filter((part) => part.type === "text")
|
|
98
|
+
.map((part) => ({ type: "text", text: part.text })),
|
|
99
|
+
metadata: {
|
|
100
|
+
...(message.metadata.modality && { modality: message.metadata.modality }),
|
|
101
|
+
...(Object.keys(message.metadata.custom).length > 0 && {
|
|
102
|
+
custom: message.metadata.custom,
|
|
103
|
+
}),
|
|
104
|
+
},
|
|
105
|
+
}) as UI_MESSAGE;
|
|
106
|
+
|
|
84
107
|
export type AISDKRuntimeAdapter<UI_MESSAGE extends UIMessage = UIMessage> =
|
|
85
108
|
ExternalStoreSharedOptions & {
|
|
86
109
|
adapters?:
|
|
@@ -115,6 +138,24 @@ export type AISDKRuntimeAdapter<UI_MESSAGE extends UIMessage = UIMessage> =
|
|
|
115
138
|
* Provide this to bridge resume-tool-call invocations into a custom handler.
|
|
116
139
|
*/
|
|
117
140
|
onResumeToolCall?: ExternalStoreAdapter["onResumeToolCall"];
|
|
141
|
+
/**
|
|
142
|
+
* Answers tool approval requests through a host-owned channel instead of the AI SDK's `addToolApprovalResponse`.
|
|
143
|
+
*
|
|
144
|
+
* Called for every approval request in the thread with the complete response, including option and free-form answers. Hand requests the host does not own to `respondViaAISDK`, which is what runs when this option is omitted. The answer applies to the approval when the handler starts and is removed if it throws. It is never written into the `useChat` messages, so `sendAutomaticallyWhen` cannot forward it. With a history adapter, the answer is stored with its message once the handler resolves and returns on reload, and a second response to the same request rejects; without one it lasts as long as this runtime, and a runtime mounted again over the same chat shows the request open until the resumed run records its resolution in the chat.
|
|
145
|
+
*
|
|
146
|
+
* While a handler is set, an approval's `display`, `allowFreeform`, `dismissible` and `options` reach the renderer, because the handler can receive answers the AI SDK cannot carry. A stream declares them through the `approvalDescriptor` of its `tool-approval-request` chunk, the one approval field the AI SDK keeps opaque; the converter reads the request and answer fields from that descriptor when the approval itself lacks them.
|
|
147
|
+
*/
|
|
148
|
+
onRespondToToolApproval?:
|
|
149
|
+
| ((
|
|
150
|
+
response: RespondToToolApprovalOptions,
|
|
151
|
+
context: {
|
|
152
|
+
toolCallId: string;
|
|
153
|
+
toolName: string;
|
|
154
|
+
/** Sends this response through the AI SDK's `addToolApprovalResponse`, which carries only `approved` and `reason`. */
|
|
155
|
+
respondViaAISDK: () => Promise<void>;
|
|
156
|
+
},
|
|
157
|
+
) => Promise<void> | void)
|
|
158
|
+
| undefined;
|
|
118
159
|
/**
|
|
119
160
|
* How consecutive assistant messages are rendered.
|
|
120
161
|
*
|
|
@@ -220,6 +261,11 @@ const useGeneratedSuggestions = (
|
|
|
220
261
|
|
|
221
262
|
const NO_CANCELLED_MESSAGE_IDS: ReadonlySet<string> = new Set();
|
|
222
263
|
|
|
264
|
+
const NO_TOOL_APPROVAL_RESPONSES: ReadonlyMap<
|
|
265
|
+
string,
|
|
266
|
+
RespondToToolApprovalOptions
|
|
267
|
+
> = new Map();
|
|
268
|
+
|
|
223
269
|
const toChatError = (error: Error): AssistantError => {
|
|
224
270
|
const code = (error as { code?: unknown }).code;
|
|
225
271
|
return {
|
|
@@ -243,6 +289,7 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
243
289
|
cancelPendingToolCallsOnSend = true,
|
|
244
290
|
onResume,
|
|
245
291
|
onResumeToolCall,
|
|
292
|
+
onRespondToToolApproval: customOnRespondToToolApproval,
|
|
246
293
|
joinStrategy,
|
|
247
294
|
messageRepository,
|
|
248
295
|
unstable_onBranchChange,
|
|
@@ -256,14 +303,36 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
256
303
|
chatId: string;
|
|
257
304
|
ids: ReadonlySet<string>;
|
|
258
305
|
} | null>(null);
|
|
306
|
+
const [toolApprovalResponses, setToolApprovalResponses] = useState<
|
|
307
|
+
ReadonlyMap<string, RespondToToolApprovalOptions>
|
|
308
|
+
>(NO_TOOL_APPROVAL_RESPONSES);
|
|
309
|
+
const [toolArtifactEpoch, setToolArtifactEpoch] = useState(0);
|
|
310
|
+
const [toolInteractionEpoch, setToolInteractionEpoch] = useState(0);
|
|
311
|
+
const hostApprovalIdsRef = useRef(new Set<string>());
|
|
312
|
+
const toolApprovalResponsesRef = useRef<
|
|
313
|
+
Map<string, RespondToToolApprovalOptions>
|
|
314
|
+
>(new Map());
|
|
259
315
|
const toolArgsKeyOrderCacheRef = useRef<Map<string, Map<string, string[]>>>(
|
|
260
316
|
new Map(),
|
|
261
317
|
);
|
|
262
318
|
const toolLastInputCacheRef = useRef<Map<string, ReadonlyJSONObject>>(
|
|
263
319
|
new Map(),
|
|
264
320
|
);
|
|
321
|
+
const toolArgsTextCacheRef = useRef<
|
|
322
|
+
WeakMap<ReadonlyJSONObject, Map<string, string>>
|
|
323
|
+
>(new WeakMap());
|
|
265
324
|
const mcpAppMetadataCacheRef = useRef<Map<string, McpAppMetadata>>(new Map());
|
|
325
|
+
const toolArtifactsRef = useRef<Map<string, unknown>>(new Map());
|
|
326
|
+
const toolInteractionsRef = useRef<Map<string, Unstable_ToolInteractionLog>>(
|
|
327
|
+
new Map(),
|
|
328
|
+
);
|
|
266
329
|
const lastRunConfigRef = useRef<RunConfig | undefined>(undefined);
|
|
330
|
+
const markToolArtifactsChanged = useCallback(() => {
|
|
331
|
+
setToolArtifactEpoch((epoch) => epoch + 1);
|
|
332
|
+
}, []);
|
|
333
|
+
const markToolInteractionsChanged = useCallback(() => {
|
|
334
|
+
setToolInteractionEpoch((epoch) => epoch + 1);
|
|
335
|
+
}, []);
|
|
267
336
|
|
|
268
337
|
const hasExecutingTools = Object.values(toolStatuses).some(
|
|
269
338
|
(s) => s?.type === "executing",
|
|
@@ -285,6 +354,25 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
285
354
|
cancelledMessages?.chatId === chatHelpers.id
|
|
286
355
|
? cancelledMessages.ids
|
|
287
356
|
: NO_CANCELLED_MESSAGE_IDS;
|
|
357
|
+
const supportsRichToolApprovalResponses =
|
|
358
|
+
customOnRespondToToolApproval != null;
|
|
359
|
+
|
|
360
|
+
const toThreadMessages = useCallback(
|
|
361
|
+
(sourceMessages: UI_MESSAGE[]) => {
|
|
362
|
+
const metadata: AISDKMessageConverterMetadata = {
|
|
363
|
+
supportsRichToolApprovalResponses,
|
|
364
|
+
toolArtifacts: toolArtifactsRef.current,
|
|
365
|
+
toolInteractions: toolInteractionsRef.current,
|
|
366
|
+
toolApprovalResponses: toolApprovalResponsesRef.current,
|
|
367
|
+
};
|
|
368
|
+
return AISDKMessageConverter.toThreadMessages(
|
|
369
|
+
sourceMessages,
|
|
370
|
+
false,
|
|
371
|
+
metadata,
|
|
372
|
+
);
|
|
373
|
+
},
|
|
374
|
+
[supportsRichToolApprovalResponses],
|
|
375
|
+
);
|
|
288
376
|
|
|
289
377
|
const retractCancellation = useCallback(
|
|
290
378
|
(chatId: string, messageId: string) => {
|
|
@@ -321,18 +409,23 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
321
409
|
isRunning,
|
|
322
410
|
messages: chatHelpers.messages,
|
|
323
411
|
joinStrategy,
|
|
324
|
-
metadata: useMemo(
|
|
412
|
+
metadata: useMemo<AISDKMessageConverterMetadata>(
|
|
325
413
|
() => ({
|
|
326
414
|
toolStatuses,
|
|
327
415
|
messageTiming,
|
|
328
416
|
toolArgsKeyOrderCache: toolArgsKeyOrderCacheRef.current,
|
|
417
|
+
toolArgsTextCache: toolArgsTextCacheRef.current,
|
|
329
418
|
toolLastInputCache: toolLastInputCacheRef.current,
|
|
330
419
|
mcpAppMetadataCache: mcpAppMetadataCacheRef.current,
|
|
420
|
+
toolArtifacts: toolArtifactsRef.current,
|
|
421
|
+
toolInteractions: toolInteractionsRef.current,
|
|
422
|
+
supportsRichToolApprovalResponses,
|
|
331
423
|
...(optimisticMessageId && { optimisticMessageId }),
|
|
332
424
|
...(chatHelpers.error && {
|
|
333
425
|
error: toChatError(chatHelpers.error),
|
|
334
426
|
}),
|
|
335
427
|
...(cancelledMessageIds.size > 0 && { cancelledMessageIds }),
|
|
428
|
+
...(toolApprovalResponses.size > 0 && { toolApprovalResponses }),
|
|
336
429
|
}),
|
|
337
430
|
[
|
|
338
431
|
toolStatuses,
|
|
@@ -340,6 +433,10 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
340
433
|
optimisticMessageId,
|
|
341
434
|
chatHelpers.error,
|
|
342
435
|
cancelledMessageIds,
|
|
436
|
+
toolApprovalResponses,
|
|
437
|
+
supportsRichToolApprovalResponses,
|
|
438
|
+
toolArtifactEpoch,
|
|
439
|
+
toolInteractionEpoch,
|
|
343
440
|
],
|
|
344
441
|
),
|
|
345
442
|
});
|
|
@@ -347,13 +444,11 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
347
444
|
const exportedMessageRepository = useMemo(() => {
|
|
348
445
|
if (!messageRepository) return undefined;
|
|
349
446
|
const converted = toExportedMessageRepository(
|
|
350
|
-
|
|
351
|
-
messages: UI_MESSAGE[],
|
|
352
|
-
) => ThreadMessage[],
|
|
447
|
+
toThreadMessages,
|
|
353
448
|
messageRepository,
|
|
354
449
|
);
|
|
355
450
|
return converted.messages.length > 0 ? converted : undefined;
|
|
356
|
-
}, [messageRepository]);
|
|
451
|
+
}, [messageRepository, toThreadMessages]);
|
|
357
452
|
|
|
358
453
|
const generatedSuggestions = useGeneratedSuggestions(
|
|
359
454
|
suggestionAdapter,
|
|
@@ -367,12 +462,15 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
367
462
|
},
|
|
368
463
|
}));
|
|
369
464
|
|
|
370
|
-
const {
|
|
465
|
+
const {
|
|
466
|
+
isLoading,
|
|
467
|
+
deleteMessage: deleteHistoryMessage,
|
|
468
|
+
persistToolInteractions,
|
|
469
|
+
persistToolApprovalResponses,
|
|
470
|
+
} = useExternalHistory(
|
|
371
471
|
runtimeRef,
|
|
372
472
|
adapters?.history ?? contextAdapters?.history,
|
|
373
|
-
|
|
374
|
-
messages: UI_MESSAGE[],
|
|
375
|
-
) => ThreadMessage[],
|
|
473
|
+
toThreadMessages,
|
|
376
474
|
aiSDKV6FormatAdapter as MessageFormatAdapter<
|
|
377
475
|
UI_MESSAGE,
|
|
378
476
|
AISDKStorageFormat
|
|
@@ -380,6 +478,17 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
380
478
|
(messages) => {
|
|
381
479
|
chatHelpers.setMessages(messages);
|
|
382
480
|
},
|
|
481
|
+
toolArtifactsRef.current,
|
|
482
|
+
markToolArtifactsChanged,
|
|
483
|
+
toolInteractionsRef.current,
|
|
484
|
+
markToolInteractionsChanged,
|
|
485
|
+
toolApprovalResponsesRef.current,
|
|
486
|
+
() => {
|
|
487
|
+
hostApprovalIdsRef.current = new Set(
|
|
488
|
+
toolApprovalResponsesRef.current.keys(),
|
|
489
|
+
);
|
|
490
|
+
setToolApprovalResponses(new Map(toolApprovalResponsesRef.current));
|
|
491
|
+
},
|
|
383
492
|
);
|
|
384
493
|
|
|
385
494
|
const {
|
|
@@ -402,38 +511,112 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
402
511
|
if (!cancelPendingToolCallsOnSend) return;
|
|
403
512
|
|
|
404
513
|
// The runtime auto-aborts in-flight tool invocations when a new run
|
|
405
|
-
// is dispatched (append() / startRun())
|
|
406
|
-
//
|
|
407
|
-
|
|
408
|
-
//
|
|
514
|
+
// is dispatched (append() / startRun()), so this only has to mark the
|
|
515
|
+
// abandoned tools cancelled in the UI message list. Every non-terminal
|
|
516
|
+
// tool call qualifies wherever it sits: the run that produced it is over,
|
|
517
|
+
// and a staged `startRun: false` message can sit between it and the tail.
|
|
518
|
+
// Uses setMessages to avoid triggering sendAutomaticallyWhen.
|
|
409
519
|
chatHelpers.setMessages((messages) => {
|
|
410
|
-
const lastMessage = messages.at(-1);
|
|
411
|
-
if (lastMessage?.role !== "assistant") return messages;
|
|
412
|
-
|
|
413
520
|
let hasChanges = false;
|
|
414
|
-
const parts = lastMessage.parts?.map((part) => {
|
|
415
|
-
if (!isToolUIPart(part)) return part;
|
|
416
|
-
if (
|
|
417
|
-
part.state === "output-available" ||
|
|
418
|
-
part.state === "output-error" ||
|
|
419
|
-
part.state === "output-denied"
|
|
420
|
-
)
|
|
421
|
-
return part;
|
|
422
521
|
|
|
522
|
+
const next = messages.map((message) => {
|
|
523
|
+
if (message.role !== "assistant") return message;
|
|
524
|
+
|
|
525
|
+
let messageChanged = false;
|
|
526
|
+
const parts = message.parts?.map((part) => {
|
|
527
|
+
if (!isToolUIPart(part)) return part;
|
|
528
|
+
if (
|
|
529
|
+
part.state === "output-available" ||
|
|
530
|
+
part.state === "output-error" ||
|
|
531
|
+
part.state === "output-denied"
|
|
532
|
+
)
|
|
533
|
+
return part;
|
|
534
|
+
|
|
535
|
+
messageChanged = true;
|
|
536
|
+
const { approval: _approval, ...rest } = part;
|
|
537
|
+
return {
|
|
538
|
+
...rest,
|
|
539
|
+
state: "output-error" as const,
|
|
540
|
+
errorText: "User cancelled tool call by sending a new message.",
|
|
541
|
+
};
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
if (!messageChanged) return message;
|
|
423
545
|
hasChanges = true;
|
|
424
|
-
|
|
425
|
-
return {
|
|
426
|
-
...rest,
|
|
427
|
-
state: "output-error" as const,
|
|
428
|
-
errorText: "User cancelled tool call by sending a new message.",
|
|
429
|
-
};
|
|
546
|
+
return { ...message, parts };
|
|
430
547
|
});
|
|
431
548
|
|
|
432
549
|
if (!hasChanges) return messages;
|
|
433
|
-
return
|
|
550
|
+
return next;
|
|
434
551
|
});
|
|
435
552
|
};
|
|
436
553
|
|
|
554
|
+
const respondViaAISDK = ({
|
|
555
|
+
approvalId,
|
|
556
|
+
approved,
|
|
557
|
+
reason,
|
|
558
|
+
}: RespondToToolApprovalOptions) =>
|
|
559
|
+
Promise.resolve(
|
|
560
|
+
chatHelpers.addToolApprovalResponse({
|
|
561
|
+
id: approvalId,
|
|
562
|
+
approved,
|
|
563
|
+
...(reason != null && { reason }),
|
|
564
|
+
options: { metadata: lastRunConfigRef.current },
|
|
565
|
+
}),
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
const respondViaHost = async (
|
|
569
|
+
onRespond: NonNullable<AISDKRuntimeAdapter["onRespondToToolApproval"]>,
|
|
570
|
+
response: RespondToToolApprovalOptions,
|
|
571
|
+
) => {
|
|
572
|
+
const { approvalId } = response;
|
|
573
|
+
const requested = chatHelpers.messages
|
|
574
|
+
.flatMap((message) =>
|
|
575
|
+
message.parts.flatMap((part) =>
|
|
576
|
+
isToolUIPart(part) ? [{ messageId: message.id, part }] : [],
|
|
577
|
+
),
|
|
578
|
+
)
|
|
579
|
+
.find(
|
|
580
|
+
({ part }) =>
|
|
581
|
+
part.state === "approval-requested" &&
|
|
582
|
+
part.approval.id === approvalId,
|
|
583
|
+
);
|
|
584
|
+
if (!requested || hostApprovalIdsRef.current.has(approvalId))
|
|
585
|
+
throw new Error(
|
|
586
|
+
`Tool approval ${approvalId} is not waiting for a response.`,
|
|
587
|
+
);
|
|
588
|
+
|
|
589
|
+
// A host answer stays out of the useChat messages, where sendAutomaticallyWhen would forward it to the chat route.
|
|
590
|
+
const applyResponse = (applied: boolean) => {
|
|
591
|
+
if (applied) hostApprovalIdsRef.current.add(approvalId);
|
|
592
|
+
else hostApprovalIdsRef.current.delete(approvalId);
|
|
593
|
+
if (applied) toolApprovalResponsesRef.current.set(approvalId, response);
|
|
594
|
+
else toolApprovalResponsesRef.current.delete(approvalId);
|
|
595
|
+
setToolApprovalResponses(new Map(toolApprovalResponsesRef.current));
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
applyResponse(true);
|
|
599
|
+
try {
|
|
600
|
+
await onRespond(response, {
|
|
601
|
+
toolCallId: requested.part.toolCallId,
|
|
602
|
+
toolName: getToolName(requested.part),
|
|
603
|
+
respondViaAISDK: async () => {
|
|
604
|
+
try {
|
|
605
|
+
await respondViaAISDK(response);
|
|
606
|
+
} finally {
|
|
607
|
+
applyResponse(false);
|
|
608
|
+
}
|
|
609
|
+
},
|
|
610
|
+
});
|
|
611
|
+
} catch (error) {
|
|
612
|
+
if (hostApprovalIdsRef.current.has(approvalId)) applyResponse(false);
|
|
613
|
+
throw error;
|
|
614
|
+
}
|
|
615
|
+
if (hostApprovalIdsRef.current.has(approvalId)) {
|
|
616
|
+
await persistToolApprovalResponses(requested.messageId);
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
|
|
437
620
|
const hasSeededRepositoryRef = useRef(false);
|
|
438
621
|
const shouldFeedRepository =
|
|
439
622
|
exportedMessageRepository != null &&
|
|
@@ -441,6 +624,7 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
441
624
|
messages.length === 0;
|
|
442
625
|
|
|
443
626
|
const runtime = useExternalStoreRuntime({
|
|
627
|
+
unstable_persistsHistory: true,
|
|
444
628
|
isRunning: providerIsRunning,
|
|
445
629
|
...(shouldFeedRepository
|
|
446
630
|
? { messageRepository: exportedMessageRepository }
|
|
@@ -461,6 +645,11 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
461
645
|
.filter(Boolean)
|
|
462
646
|
.flat(),
|
|
463
647
|
),
|
|
648
|
+
onVoiceTranscript: (message: ThreadMessage) =>
|
|
649
|
+
chatHelpers.setMessages((current) => [
|
|
650
|
+
...current,
|
|
651
|
+
toVoiceTranscriptUIMessage<UI_MESSAGE>(message),
|
|
652
|
+
]),
|
|
464
653
|
onExportExternalState: (): MessageFormatRepository<UI_MESSAGE> => {
|
|
465
654
|
const exported = runtimeRef.current.thread.export();
|
|
466
655
|
|
|
@@ -501,10 +690,7 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
501
690
|
},
|
|
502
691
|
onLoadExternalState: (repo: MessageFormatRepository<UI_MESSAGE>) => {
|
|
503
692
|
// Convert MessageFormatRepository to ExportedMessageRepository
|
|
504
|
-
const exportedRepo = toExportedMessageRepository(
|
|
505
|
-
AISDKMessageConverter.toThreadMessages,
|
|
506
|
-
repo,
|
|
507
|
-
);
|
|
693
|
+
const exportedRepo = toExportedMessageRepository(toThreadMessages, repo);
|
|
508
694
|
|
|
509
695
|
// Import into the thread's MessageRepository
|
|
510
696
|
runtimeRef.current.thread.import(exportedRepo);
|
|
@@ -584,6 +770,34 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
584
770
|
|
|
585
771
|
await deleteHistoryMessage(messageId);
|
|
586
772
|
|
|
773
|
+
let removedToolArtifact = false;
|
|
774
|
+
let removedToolInteractions = false;
|
|
775
|
+
let removedToolApprovalResponse = false;
|
|
776
|
+
let removedHostApprovalId = false;
|
|
777
|
+
for (const part of threadMessages[messageIndex]!.content) {
|
|
778
|
+
if (part.type === "tool-call") {
|
|
779
|
+
removedToolArtifact =
|
|
780
|
+
toolArtifactsRef.current.delete(part.toolCallId) ||
|
|
781
|
+
removedToolArtifact;
|
|
782
|
+
removedToolInteractions =
|
|
783
|
+
toolInteractionsRef.current.delete(part.toolCallId) ||
|
|
784
|
+
removedToolInteractions;
|
|
785
|
+
if (part.approval) {
|
|
786
|
+
removedToolApprovalResponse =
|
|
787
|
+
toolApprovalResponsesRef.current.delete(part.approval.id) ||
|
|
788
|
+
removedToolApprovalResponse;
|
|
789
|
+
removedHostApprovalId =
|
|
790
|
+
hostApprovalIdsRef.current.delete(part.approval.id) ||
|
|
791
|
+
removedHostApprovalId;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
if (removedToolArtifact) markToolArtifactsChanged();
|
|
796
|
+
if (removedToolInteractions) markToolInteractionsChanged();
|
|
797
|
+
if (removedToolApprovalResponse || removedHostApprovalId) {
|
|
798
|
+
setToolApprovalResponses(new Map(toolApprovalResponsesRef.current));
|
|
799
|
+
}
|
|
800
|
+
|
|
587
801
|
const deleteIds = new Set(
|
|
588
802
|
getExternalStoreMessages<UI_MESSAGE>(threadMessages[messageIndex]!).map(
|
|
589
803
|
(message) => message.id,
|
|
@@ -605,8 +819,13 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
605
819
|
toolName,
|
|
606
820
|
result,
|
|
607
821
|
isError,
|
|
822
|
+
artifact,
|
|
608
823
|
modelContent,
|
|
609
824
|
}) => {
|
|
825
|
+
if (artifact !== undefined) {
|
|
826
|
+
toolArtifactsRef.current.set(toolCallId, artifact);
|
|
827
|
+
markToolArtifactsChanged();
|
|
828
|
+
}
|
|
610
829
|
const options = { metadata: lastRunConfigRef.current };
|
|
611
830
|
if (isError) {
|
|
612
831
|
return Promise.resolve(
|
|
@@ -634,15 +853,24 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
|
|
|
634
853
|
);
|
|
635
854
|
}
|
|
636
855
|
},
|
|
637
|
-
onRespondToToolApproval:
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
856
|
+
onRespondToToolApproval: customOnRespondToToolApproval
|
|
857
|
+
? (response) => respondViaHost(customOnRespondToToolApproval, response)
|
|
858
|
+
: respondViaAISDK,
|
|
859
|
+
unstable_onRecordToolInteraction: ({
|
|
860
|
+
messageId,
|
|
861
|
+
toolCallId,
|
|
862
|
+
interaction,
|
|
863
|
+
}) => {
|
|
864
|
+
toolInteractionsRef.current.set(
|
|
865
|
+
toolCallId,
|
|
866
|
+
appendToolInteraction(
|
|
867
|
+
toolInteractionsRef.current.get(toolCallId),
|
|
868
|
+
interaction,
|
|
869
|
+
),
|
|
870
|
+
);
|
|
871
|
+
markToolInteractionsChanged();
|
|
872
|
+
return persistToolInteractions(messageId);
|
|
873
|
+
},
|
|
646
874
|
...pickExternalStoreSharedOptions(adapter),
|
|
647
875
|
...(adapter.unstable_messageRepositoryInstance && {
|
|
648
876
|
unstable_messageRepositoryInstance:
|