@assistant-ui/ai-sdk 0.0.6 → 0.0.7
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/adapters/vercelAttachmentAdapter.d.ts +1 -2
- package/dist/adapters/vercelAttachmentAdapter.d.ts.map +1 -1
- package/dist/aiSDKExtras.d.ts +2 -3
- package/dist/aiSDKExtras.d.ts.map +1 -1
- package/dist/converters/convertMessage.d.ts +5 -4
- package/dist/converters/convertMessage.d.ts.map +1 -1
- package/dist/converters/convertMessage.js +39 -3
- package/dist/converters/convertMessage.js.map +1 -1
- package/dist/converters/modelContentEnvelope.d.ts +4 -5
- package/dist/converters/modelContentEnvelope.d.ts.map +1 -1
- package/dist/converters/toCreateMessage.d.ts +1 -2
- package/dist/converters/toCreateMessage.d.ts.map +1 -1
- package/dist/converters/toolOutputConversion.d.ts +2 -3
- package/dist/converters/toolOutputConversion.d.ts.map +1 -1
- package/dist/hooks.d.ts +2 -3
- package/dist/hooks.d.ts.map +1 -1
- package/dist/model-context/injectInteractableContext.d.ts +1 -2
- package/dist/model-context/injectInteractableContext.d.ts.map +1 -1
- package/dist/model-context/injectQuoteContext.d.ts +1 -2
- package/dist/model-context/injectQuoteContext.d.ts.map +1 -1
- package/dist/runtime/AISDKChat.d.ts +2 -3
- package/dist/runtime/AISDKChat.d.ts.map +1 -1
- package/dist/runtime/AISDKThreads.d.ts +2 -3
- package/dist/runtime/AISDKThreads.d.ts.map +1 -1
- package/dist/runtime/AISDKThreads.js +4 -10
- package/dist/runtime/AISDKThreads.js.map +1 -1
- package/dist/runtime/sdkIdentity.d.ts +1 -2
- package/dist/runtime/sdkIdentity.d.ts.map +1 -1
- package/dist/runtime/sdkIdentity.js +1 -1
- package/dist/runtime/useAISDKRuntime.d.ts +17 -5
- package/dist/runtime/useAISDKRuntime.d.ts.map +1 -1
- package/dist/runtime/useAISDKRuntime.js +95 -37
- package/dist/runtime/useAISDKRuntime.js.map +1 -1
- package/dist/runtime/useChatRuntime.d.ts +2 -3
- package/dist/runtime/useChatRuntime.d.ts.map +1 -1
- package/dist/runtime/useChatThread.d.ts +19 -5
- package/dist/runtime/useChatThread.d.ts.map +1 -1
- package/dist/runtime/useChatThread.js +30 -9
- package/dist/runtime/useChatThread.js.map +1 -1
- package/dist/runtime/useExternalHistory.d.ts +2 -3
- package/dist/runtime/useExternalHistory.d.ts.map +1 -1
- package/dist/runtime/useExternalHistory.js +28 -20
- package/dist/runtime/useExternalHistory.js.map +1 -1
- package/dist/runtime/useResourceCleanup.d.ts +1 -2
- package/dist/runtime/useResourceCleanup.d.ts.map +1 -1
- package/dist/runtime/useStreamingTiming.d.ts +2 -3
- package/dist/runtime/useStreamingTiming.d.ts.map +1 -1
- package/dist/tools/frontendTools.d.ts +3 -4
- package/dist/tools/frontendTools.d.ts.map +1 -1
- package/dist/tools/generativeTools.d.ts +5 -6
- package/dist/tools/generativeTools.d.ts.map +1 -1
- package/dist/tools/mcp-stdio.unsupported.d.ts +1 -2
- package/dist/tools/mcp-stdio.unsupported.d.ts.map +1 -1
- package/dist/transport/AssistantChatTransport.d.ts +3 -4
- package/dist/transport/AssistantChatTransport.d.ts.map +1 -1
- package/dist/transport/resumable.d.ts +4 -5
- package/dist/transport/resumable.d.ts.map +1 -1
- package/dist/usage.d.ts +4 -5
- package/dist/usage.d.ts.map +1 -1
- package/dist/utils/getVercelAIMessages.d.ts +1 -2
- package/dist/utils/getVercelAIMessages.d.ts.map +1 -1
- package/dist/utils/sliceMessagesUntil.d.ts +1 -2
- package/dist/utils/sliceMessagesUntil.d.ts.map +1 -1
- package/package.json +15 -14
- package/src/converters/convertMessage.test.ts +138 -0
- package/src/converters/convertMessage.ts +94 -5
- package/src/runtime/AISDKChat.test.ts +4 -5
- package/src/runtime/AISDKThreads.test.ts +9 -5
- package/src/runtime/AISDKThreads.ts +3 -12
- package/src/runtime/useAISDKRuntime.approval.integration.test.tsx +287 -0
- package/src/runtime/useAISDKRuntime.approval.test.tsx +257 -1
- package/src/runtime/useAISDKRuntime.test.ts +7 -3
- package/src/runtime/useAISDKRuntime.ts +147 -23
- package/src/runtime/useAISDKRuntime.voice.test.tsx +270 -0
- package/src/runtime/useChatRuntime.test.ts +72 -4
- package/src/runtime/useChatThread.ts +44 -5
- package/src/runtime/useExternalHistory.test.ts +46 -0
- package/src/runtime/useExternalHistory.ts +24 -10
- package/src/tools/generativeTools.test.ts +7 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
//#region src/transport/resumable.d.ts
|
|
2
2
|
/** Response header used by the [Resumable Streams](/docs/guides/resumable-streams) server and client wiring. */
|
|
3
|
-
declare const RESUMABLE_STREAM_ID_HEADER = "x-resumable-stream-id";
|
|
4
|
-
type ResumableClientStorage = {
|
|
3
|
+
export declare const RESUMABLE_STREAM_ID_HEADER = "x-resumable-stream-id";
|
|
4
|
+
export type ResumableClientStorage = {
|
|
5
5
|
getStreamId(threadId?: string): string | null;
|
|
6
6
|
setStreamId(id: string, threadId?: string): void;
|
|
7
7
|
clear(threadId?: string): void;
|
|
@@ -9,7 +9,7 @@ type ResumableClientStorage = {
|
|
|
9
9
|
subscribe?(listener: () => void, threadId?: string): () => void;
|
|
10
10
|
};
|
|
11
11
|
/** `sessionStorage`-backed storage for the pending resumable stream id. See the [Resumable Streams](/docs/guides/resumable-streams) guide for end-to-end wiring. */
|
|
12
|
-
declare function createResumableSessionStorage(options?: {
|
|
12
|
+
export declare function createResumableSessionStorage(options?: {
|
|
13
13
|
/**
|
|
14
14
|
* Storage key for the pending stream id. A static string namespaces per route
|
|
15
15
|
* or chat surface. A getter is read lazily on every access, so the key can be
|
|
@@ -24,7 +24,7 @@ declare function createResumableSessionStorage(options?: {
|
|
|
24
24
|
*/
|
|
25
25
|
key?: string | (() => string | undefined);
|
|
26
26
|
}): ResumableClientStorage;
|
|
27
|
-
type AssistantChatResumableOptions = {
|
|
27
|
+
export type AssistantChatResumableOptions = {
|
|
28
28
|
storage: ResumableClientStorage;
|
|
29
29
|
resumeApi: string | ((streamId: string) => string);
|
|
30
30
|
/**
|
|
@@ -34,5 +34,4 @@ type AssistantChatResumableOptions = {
|
|
|
34
34
|
isFinishEvent?: (chunk: Uint8Array, accumulator: string) => boolean;
|
|
35
35
|
};
|
|
36
36
|
//#endregion
|
|
37
|
-
export { AssistantChatResumableOptions, RESUMABLE_STREAM_ID_HEADER, ResumableClientStorage, createResumableSessionStorage };
|
|
38
37
|
//# sourceMappingURL=resumable.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resumable.d.ts","names":[],"sources":["../../src/transport/resumable.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"resumable.d.ts","names":[],"sources":["../../src/transport/resumable.ts"],"mappings":";;qBAKa;YAID;EACV,YAAY;EACZ,YAAY,YAAY;EACxB,MAAM;;EAEN,WAAW,sBAAsB;;;wBAkBnB,8BAA8B;;;;;;;;;;;;;EAa5C;IACE;YA0GQ;EACV,SAAS;EACT,sBAAsB;;;;;EAKtB,iBAAiB,OAAO,YAAY"}
|
package/dist/usage.d.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/// <reference types="@assistant-ui/core/react" />
|
|
2
2
|
//#region src/usage.d.ts
|
|
3
|
-
type ThreadTokenUsage = {
|
|
3
|
+
export type ThreadTokenUsage = {
|
|
4
4
|
totalTokens?: number;
|
|
5
5
|
inputTokens?: number;
|
|
6
6
|
outputTokens?: number;
|
|
7
7
|
reasoningTokens?: number;
|
|
8
8
|
cachedInputTokens?: number;
|
|
9
9
|
};
|
|
10
|
-
interface TokenUsageExtractableMessage {
|
|
10
|
+
export interface TokenUsageExtractableMessage {
|
|
11
11
|
role?: string;
|
|
12
12
|
metadata?: unknown;
|
|
13
13
|
}
|
|
14
|
-
declare function getThreadMessageTokenUsage(message: TokenUsageExtractableMessage | undefined): ThreadTokenUsage | undefined;
|
|
14
|
+
export declare function getThreadMessageTokenUsage(message: TokenUsageExtractableMessage | undefined): ThreadTokenUsage | undefined;
|
|
15
15
|
/**
|
|
16
16
|
* Reads token usage from the newest assistant message that reports any.
|
|
17
17
|
*
|
|
@@ -20,7 +20,6 @@ declare function getThreadMessageTokenUsage(message: TokenUsageExtractableMessag
|
|
|
20
20
|
* other key the route returns into `metadata.custom`, which is where this hook
|
|
21
21
|
* looks.
|
|
22
22
|
*/
|
|
23
|
-
declare function useThreadTokenUsage(): ThreadTokenUsage | undefined;
|
|
23
|
+
export declare function useThreadTokenUsage(): ThreadTokenUsage | undefined;
|
|
24
24
|
//#endregion
|
|
25
|
-
export { ThreadTokenUsage, TokenUsageExtractableMessage, getThreadMessageTokenUsage, useThreadTokenUsage };
|
|
26
25
|
//# sourceMappingURL=usage.d.ts.map
|
package/dist/usage.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usage.d.ts","names":[],"sources":["../src/usage.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"usage.d.ts","names":[],"sources":["../src/usage.ts"],"mappings":";YAIY;EACV;EACA;EACA;EACA;EACA;;iBAGe;EACf;EACA;;wBAuHc,2BACd,SAAS,2CACR;;;;;;;;;wBAsCa,uBAAuB"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { UIMessage } from "ai";
|
|
2
2
|
import { ThreadMessage } from "@assistant-ui/core";
|
|
3
3
|
//#region src/utils/getVercelAIMessages.d.ts
|
|
4
|
-
declare const getVercelAIMessages: <UI_MESSAGE extends UIMessage = UIMessage>(message: ThreadMessage) => UI_MESSAGE[];
|
|
4
|
+
export declare const getVercelAIMessages: <UI_MESSAGE extends UIMessage = UIMessage>(message: ThreadMessage) => UI_MESSAGE[];
|
|
5
5
|
//#endregion
|
|
6
|
-
export { getVercelAIMessages };
|
|
7
6
|
//# sourceMappingURL=getVercelAIMessages.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getVercelAIMessages.d.ts","names":[],"sources":["../../src/utils/getVercelAIMessages.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"getVercelAIMessages.d.ts","names":[],"sources":["../../src/utils/getVercelAIMessages.ts"],"mappings":";;;qBAMa,sBAAuB,mBAAmB,YAAY,WACjE,SAAS,kBAEmC"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { UIMessage } from "ai";
|
|
2
2
|
//#region src/utils/sliceMessagesUntil.d.ts
|
|
3
|
-
declare const sliceMessagesUntil: <UI_MESSAGE extends UIMessage = UIMessage>(messages: UI_MESSAGE[], messageId: string | null) => UI_MESSAGE[];
|
|
3
|
+
export declare const sliceMessagesUntil: <UI_MESSAGE extends UIMessage = UIMessage>(messages: UI_MESSAGE[], messageId: string | null) => UI_MESSAGE[];
|
|
4
4
|
//#endregion
|
|
5
|
-
export { sliceMessagesUntil };
|
|
6
5
|
//# sourceMappingURL=sliceMessagesUntil.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sliceMessagesUntil.d.ts","names":[],"sources":["../../src/utils/sliceMessagesUntil.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"sliceMessagesUntil.d.ts","names":[],"sources":["../../src/utils/sliceMessagesUntil.ts"],"mappings":";;qBAEa,qBAAsB,mBAAmB,YAAY,WAChE,UAAU,cACV,6BAAwB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assistant-ui/ai-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "Vercel AI SDK adapter for assistant-ui",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-sdk",
|
|
@@ -46,14 +46,14 @@
|
|
|
46
46
|
],
|
|
47
47
|
"sideEffects": false,
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@ai-sdk/mcp": "^2.0.
|
|
50
|
-
"@ai-sdk/react": "^4.0.
|
|
51
|
-
"@assistant-ui/core": "^0.3.
|
|
52
|
-
"@assistant-ui/store": "^0.3.
|
|
53
|
-
"@assistant-ui/tap": "^0.9.
|
|
54
|
-
"ai": "^7.0.
|
|
55
|
-
"assistant-cloud": "^0.2.
|
|
56
|
-
"assistant-stream": "^0.3.
|
|
49
|
+
"@ai-sdk/mcp": "^2.0.49",
|
|
50
|
+
"@ai-sdk/react": "^4.0.104",
|
|
51
|
+
"@assistant-ui/core": "^0.3.20",
|
|
52
|
+
"@assistant-ui/store": "^0.3.14",
|
|
53
|
+
"@assistant-ui/tap": "^0.9.18",
|
|
54
|
+
"ai": "^7.0.101",
|
|
55
|
+
"assistant-cloud": "^0.2.2",
|
|
56
|
+
"assistant-stream": "^0.3.44"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"@types/react": "*",
|
|
@@ -68,14 +68,14 @@
|
|
|
68
68
|
}
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
|
-
"@assistant-ui/x-buildutils": "0.0.
|
|
71
|
+
"@assistant-ui/x-buildutils": "0.0.28",
|
|
72
72
|
"@testing-library/react": "^16.3.3",
|
|
73
73
|
"@types/json-schema": "^7.0.15",
|
|
74
|
-
"@types/react": "^19.
|
|
75
|
-
"@types/react-dom": "^19.
|
|
74
|
+
"@types/react": "^19.3.0",
|
|
75
|
+
"@types/react-dom": "^19.3.0",
|
|
76
76
|
"jsdom": "^30.0.1",
|
|
77
|
-
"react": "^19.
|
|
78
|
-
"vitest": "^5.0.
|
|
77
|
+
"react": "^19.3.0",
|
|
78
|
+
"vitest": "^5.0.1"
|
|
79
79
|
},
|
|
80
80
|
"publishConfig": {
|
|
81
81
|
"access": "public",
|
|
@@ -93,6 +93,7 @@
|
|
|
93
93
|
"scripts": {
|
|
94
94
|
"build": "aui-build",
|
|
95
95
|
"test": "vitest run",
|
|
96
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json",
|
|
96
97
|
"test:react-compiler": "vitest run --config vitest.react-compiler.config.ts",
|
|
97
98
|
"test:standalone": "vitest run --project standalone",
|
|
98
99
|
"test:watch": "vitest"
|
|
@@ -487,6 +487,144 @@ describe("AISDKMessageConverter", () => {
|
|
|
487
487
|
});
|
|
488
488
|
});
|
|
489
489
|
|
|
490
|
+
it("preserves rich approval fields for a custom response channel", () => {
|
|
491
|
+
const metadata: AISDKMessageConverterMetadata = {
|
|
492
|
+
supportsRichToolApprovalResponses: true,
|
|
493
|
+
};
|
|
494
|
+
const converted = AISDKMessageConverter.toThreadMessages(
|
|
495
|
+
[
|
|
496
|
+
{
|
|
497
|
+
id: "a1",
|
|
498
|
+
role: "assistant",
|
|
499
|
+
parts: [
|
|
500
|
+
{
|
|
501
|
+
type: "tool-deploy",
|
|
502
|
+
toolCallId: "tc-1",
|
|
503
|
+
state: "approval-responded",
|
|
504
|
+
input: {},
|
|
505
|
+
approval: {
|
|
506
|
+
id: "approval-1",
|
|
507
|
+
display: "select",
|
|
508
|
+
allowFreeform: true,
|
|
509
|
+
options: [
|
|
510
|
+
{
|
|
511
|
+
id: "once",
|
|
512
|
+
kind: "allow-once",
|
|
513
|
+
label: "Only once",
|
|
514
|
+
grants: ["repository", 42],
|
|
515
|
+
confirm: {
|
|
516
|
+
title: "Confirm access",
|
|
517
|
+
description: { invalid: true },
|
|
518
|
+
},
|
|
519
|
+
},
|
|
520
|
+
"invalid",
|
|
521
|
+
{ id: 1, kind: "allow-always" },
|
|
522
|
+
{ id: "always", kind: 2 },
|
|
523
|
+
],
|
|
524
|
+
optionId: "once",
|
|
525
|
+
text: "an answer",
|
|
526
|
+
},
|
|
527
|
+
},
|
|
528
|
+
],
|
|
529
|
+
} as any,
|
|
530
|
+
],
|
|
531
|
+
false,
|
|
532
|
+
metadata,
|
|
533
|
+
);
|
|
534
|
+
|
|
535
|
+
const toolCall = converted[0]?.content.find(
|
|
536
|
+
(part): part is any => part.type === "tool-call",
|
|
537
|
+
);
|
|
538
|
+
expect(toolCall?.approval).toEqual({
|
|
539
|
+
id: "approval-1",
|
|
540
|
+
display: "select",
|
|
541
|
+
allowFreeform: true,
|
|
542
|
+
options: [
|
|
543
|
+
{
|
|
544
|
+
id: "once",
|
|
545
|
+
kind: "allow-once",
|
|
546
|
+
label: "Only once",
|
|
547
|
+
grants: ["repository"],
|
|
548
|
+
confirm: { title: "Confirm access" },
|
|
549
|
+
},
|
|
550
|
+
],
|
|
551
|
+
optionId: "once",
|
|
552
|
+
text: "an answer",
|
|
553
|
+
});
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
it("applies a host answer to an approval the message has not recorded", () => {
|
|
557
|
+
const metadata: AISDKMessageConverterMetadata = {
|
|
558
|
+
supportsRichToolApprovalResponses: true,
|
|
559
|
+
toolApprovalResponses: new Map([
|
|
560
|
+
[
|
|
561
|
+
"approval-1",
|
|
562
|
+
{
|
|
563
|
+
approvalId: "approval-1",
|
|
564
|
+
approved: true,
|
|
565
|
+
optionId: "staging",
|
|
566
|
+
text: "only staging",
|
|
567
|
+
},
|
|
568
|
+
],
|
|
569
|
+
["approval-2", { approvalId: "approval-2", approved: true }],
|
|
570
|
+
["approval-3", { approvalId: "approval-3", approved: true }],
|
|
571
|
+
]),
|
|
572
|
+
};
|
|
573
|
+
const converted = AISDKMessageConverter.toThreadMessages(
|
|
574
|
+
[
|
|
575
|
+
{
|
|
576
|
+
id: "a1",
|
|
577
|
+
role: "assistant",
|
|
578
|
+
parts: [
|
|
579
|
+
{
|
|
580
|
+
type: "tool-deploy",
|
|
581
|
+
toolCallId: "tc-1",
|
|
582
|
+
state: "approval-requested",
|
|
583
|
+
input: {},
|
|
584
|
+
approval: {
|
|
585
|
+
id: "approval-1",
|
|
586
|
+
display: "select",
|
|
587
|
+
options: [{ id: "staging", kind: "_target" }],
|
|
588
|
+
},
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
type: "tool-deploy",
|
|
592
|
+
toolCallId: "tc-2",
|
|
593
|
+
state: "approval-responded",
|
|
594
|
+
input: {},
|
|
595
|
+
approval: { id: "approval-2", approved: false, reason: "no" },
|
|
596
|
+
},
|
|
597
|
+
{
|
|
598
|
+
type: "tool-deploy",
|
|
599
|
+
toolCallId: "tc-3",
|
|
600
|
+
state: "approval-requested",
|
|
601
|
+
input: {},
|
|
602
|
+
approval: { id: "approval-3", resolution: "expired" },
|
|
603
|
+
},
|
|
604
|
+
],
|
|
605
|
+
} as any,
|
|
606
|
+
],
|
|
607
|
+
false,
|
|
608
|
+
metadata,
|
|
609
|
+
);
|
|
610
|
+
|
|
611
|
+
const approvals = converted[0]?.content.map(
|
|
612
|
+
(part) => (part as { approval?: unknown }).approval,
|
|
613
|
+
);
|
|
614
|
+
expect(approvals).toEqual([
|
|
615
|
+
{
|
|
616
|
+
id: "approval-1",
|
|
617
|
+
display: "select",
|
|
618
|
+
options: [{ id: "staging", kind: "_target" }],
|
|
619
|
+
approved: true,
|
|
620
|
+
optionId: "staging",
|
|
621
|
+
text: "only staging",
|
|
622
|
+
},
|
|
623
|
+
{ id: "approval-2", approved: false, reason: "no" },
|
|
624
|
+
{ id: "approval-3", resolution: "expired" },
|
|
625
|
+
]);
|
|
626
|
+
});
|
|
627
|
+
|
|
490
628
|
it("drops a resolution the core contract does not declare", () => {
|
|
491
629
|
const converted = AISDKMessageConverter.toThreadMessages([
|
|
492
630
|
{
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import {
|
|
13
13
|
isMcpAppUri,
|
|
14
14
|
type ReasoningMessagePart,
|
|
15
|
+
type ToolApprovalOption,
|
|
15
16
|
type ToolCallMessagePart,
|
|
16
17
|
type TextMessagePart,
|
|
17
18
|
type DataMessagePart,
|
|
@@ -22,6 +23,7 @@ import {
|
|
|
22
23
|
type ThreadMessageLike,
|
|
23
24
|
type McpAppMetadata,
|
|
24
25
|
type MessagePartStreamStatus,
|
|
26
|
+
type RespondToToolApprovalOptions,
|
|
25
27
|
} from "@assistant-ui/core";
|
|
26
28
|
import { stableStringifyToolArgs } from "@assistant-ui/core/internal";
|
|
27
29
|
import {
|
|
@@ -61,6 +63,8 @@ export type AISDKMessageConverterMetadata =
|
|
|
61
63
|
toolArgsKeyOrderCache?: Map<string, Map<string, string[]>>;
|
|
62
64
|
toolLastInputCache?: Map<string, ReadonlyJSONObject>;
|
|
63
65
|
mcpAppMetadataCache?: Map<string, McpAppMetadata>;
|
|
66
|
+
supportsRichToolApprovalResponses?: boolean;
|
|
67
|
+
toolApprovalResponses?: ReadonlyMap<string, RespondToToolApprovalOptions>;
|
|
64
68
|
/** Id of the currently-streaming message, flagged optimistic (#4037). */
|
|
65
69
|
optimisticMessageId?: string | undefined;
|
|
66
70
|
};
|
|
@@ -153,19 +157,79 @@ function extractMcpAppMetadata(
|
|
|
153
157
|
return out;
|
|
154
158
|
}
|
|
155
159
|
|
|
160
|
+
const normalizeToolApprovalOptions = (
|
|
161
|
+
options: unknown,
|
|
162
|
+
): readonly ToolApprovalOption[] | undefined => {
|
|
163
|
+
if (!Array.isArray(options)) return undefined;
|
|
164
|
+
|
|
165
|
+
return options.flatMap<ToolApprovalOption>((value) => {
|
|
166
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return [];
|
|
167
|
+
const option = value as Record<string, unknown>;
|
|
168
|
+
if (typeof option.id !== "string" || typeof option.kind !== "string")
|
|
169
|
+
return [];
|
|
170
|
+
|
|
171
|
+
const confirm = option.confirm;
|
|
172
|
+
const confirmDetails =
|
|
173
|
+
confirm && typeof confirm === "object" && !Array.isArray(confirm)
|
|
174
|
+
? (confirm as Record<string, unknown>)
|
|
175
|
+
: undefined;
|
|
176
|
+
|
|
177
|
+
return [
|
|
178
|
+
{
|
|
179
|
+
id: option.id,
|
|
180
|
+
kind: option.kind,
|
|
181
|
+
...(typeof option.label === "string" && { label: option.label }),
|
|
182
|
+
...(typeof option.description === "string" && {
|
|
183
|
+
description: option.description,
|
|
184
|
+
}),
|
|
185
|
+
...(Array.isArray(option.grants) && {
|
|
186
|
+
grants: option.grants.filter(
|
|
187
|
+
(grant): grant is string => typeof grant === "string",
|
|
188
|
+
),
|
|
189
|
+
}),
|
|
190
|
+
...(typeof confirm === "boolean"
|
|
191
|
+
? { confirm }
|
|
192
|
+
: confirmDetails
|
|
193
|
+
? {
|
|
194
|
+
confirm: {
|
|
195
|
+
...(typeof confirmDetails.title === "string" && {
|
|
196
|
+
title: confirmDetails.title,
|
|
197
|
+
}),
|
|
198
|
+
...(typeof confirmDetails.description === "string" && {
|
|
199
|
+
description: confirmDetails.description,
|
|
200
|
+
}),
|
|
201
|
+
},
|
|
202
|
+
}
|
|
203
|
+
: {}),
|
|
204
|
+
},
|
|
205
|
+
];
|
|
206
|
+
});
|
|
207
|
+
};
|
|
208
|
+
|
|
156
209
|
function getToolApprovalAndInterrupt(
|
|
157
210
|
part: {
|
|
158
211
|
approval?: Record<string, unknown> | undefined;
|
|
159
212
|
},
|
|
160
213
|
toolStatus: { type: string; payload?: unknown } | undefined,
|
|
214
|
+
supportsRichToolApprovalResponses: boolean,
|
|
215
|
+
toolApprovalResponses:
|
|
216
|
+
| ReadonlyMap<string, RespondToToolApprovalOptions>
|
|
217
|
+
| undefined,
|
|
161
218
|
): {
|
|
162
219
|
approval?: NonNullable<ToolCallMessagePart["approval"]>;
|
|
163
220
|
interrupt?: NonNullable<ToolCallMessagePart["interrupt"]>;
|
|
164
221
|
} {
|
|
165
222
|
if (part.approval) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
223
|
+
const response =
|
|
224
|
+
typeof part.approval.id === "string" &&
|
|
225
|
+
part.approval.approved === undefined &&
|
|
226
|
+
part.approval.resolution !== "cancelled" &&
|
|
227
|
+
part.approval.resolution !== "expired"
|
|
228
|
+
? toolApprovalResponses?.get(part.approval.id)
|
|
229
|
+
: undefined;
|
|
230
|
+
// The built-in AI SDK channel sends only id, approved and reason back to
|
|
231
|
+
// the server, so a request shape promising any other answer would render
|
|
232
|
+
// controls whose response cannot travel.
|
|
169
233
|
const {
|
|
170
234
|
id,
|
|
171
235
|
prompt,
|
|
@@ -179,7 +243,18 @@ function getToolApprovalAndInterrupt(
|
|
|
179
243
|
optionId,
|
|
180
244
|
text,
|
|
181
245
|
...additionalApprovalFields
|
|
182
|
-
} =
|
|
246
|
+
} = response
|
|
247
|
+
? {
|
|
248
|
+
...part.approval,
|
|
249
|
+
approved: response.approved,
|
|
250
|
+
...(response.reason != null && { reason: response.reason }),
|
|
251
|
+
...(response.optionId != null && { optionId: response.optionId }),
|
|
252
|
+
...(response.text != null && { text: response.text }),
|
|
253
|
+
}
|
|
254
|
+
: part.approval;
|
|
255
|
+
const normalizedOptions = supportsRichToolApprovalResponses
|
|
256
|
+
? normalizeToolApprovalOptions(options)
|
|
257
|
+
: undefined;
|
|
183
258
|
const requestReason = additionalApprovalFields.requestReason;
|
|
184
259
|
if (typeof id === "string")
|
|
185
260
|
return {
|
|
@@ -194,6 +269,15 @@ function getToolApprovalAndInterrupt(
|
|
|
194
269
|
...(typeof approved === "boolean" && { approved }),
|
|
195
270
|
...(typeof reason === "string" && { reason }),
|
|
196
271
|
...(isAutomatic === true && { isAutomatic: true }),
|
|
272
|
+
...(supportsRichToolApprovalResponses && {
|
|
273
|
+
...((display === "decision" ||
|
|
274
|
+
display === "select" ||
|
|
275
|
+
display === "text") && { display }),
|
|
276
|
+
...(typeof allowFreeform === "boolean" && { allowFreeform }),
|
|
277
|
+
...(normalizedOptions && { options: normalizedOptions }),
|
|
278
|
+
...(typeof optionId === "string" && { optionId }),
|
|
279
|
+
...(typeof text === "string" && { text }),
|
|
280
|
+
}),
|
|
197
281
|
...((resolution === "cancelled" || resolution === "expired") && {
|
|
198
282
|
resolution,
|
|
199
283
|
}),
|
|
@@ -348,7 +432,12 @@ function convertParts(
|
|
|
348
432
|
part.callProviderMetadata as PartProviderMetadata,
|
|
349
433
|
}
|
|
350
434
|
: undefined),
|
|
351
|
-
...getToolApprovalAndInterrupt(
|
|
435
|
+
...getToolApprovalAndInterrupt(
|
|
436
|
+
part,
|
|
437
|
+
toolStatus,
|
|
438
|
+
metadata.supportsRichToolApprovalResponses === true,
|
|
439
|
+
metadata.toolApprovalResponses,
|
|
440
|
+
),
|
|
352
441
|
} satisfies ToolCallMessagePart;
|
|
353
442
|
}
|
|
354
443
|
|
|
@@ -153,7 +153,9 @@ describe("AISDKChat as a standalone client config entry", () => {
|
|
|
153
153
|
]
|
|
154
154
|
.map((chunk) => `data: ${JSON.stringify(chunk)}\n\n`)
|
|
155
155
|
.join("");
|
|
156
|
-
const fetchMock = vi.fn
|
|
156
|
+
const fetchMock = vi.fn<
|
|
157
|
+
(input: RequestInfo | URL, init: RequestInit) => Promise<Response>
|
|
158
|
+
>(
|
|
157
159
|
async () =>
|
|
158
160
|
new Response(sse, {
|
|
159
161
|
headers: { "content-type": "text/event-stream" },
|
|
@@ -178,10 +180,7 @@ describe("AISDKChat as a standalone client config entry", () => {
|
|
|
178
180
|
expect(state.messages).toHaveLength(2);
|
|
179
181
|
});
|
|
180
182
|
|
|
181
|
-
const [url, init] = fetchMock.mock.calls[0]
|
|
182
|
-
RequestInfo,
|
|
183
|
-
RequestInit,
|
|
184
|
-
];
|
|
183
|
+
const [url, init] = fetchMock.mock.calls[0]!;
|
|
185
184
|
expect(String(url)).toContain("/api/chat");
|
|
186
185
|
const body = JSON.parse(init.body as string);
|
|
187
186
|
expect(body.id).toBe("test-thread-1");
|
|
@@ -455,7 +455,9 @@ describe("AISDKThreads", () => {
|
|
|
455
455
|
const list = vi.fn(async () => ({
|
|
456
456
|
threads: [cloudThread("cloud-1"), cloudThread("cloud-2")],
|
|
457
457
|
}));
|
|
458
|
-
const create = vi.fn(async () => ({
|
|
458
|
+
const create = vi.fn<AssistantCloud["threads"]["create"]>(async () => ({
|
|
459
|
+
thread_id: "cloud-created",
|
|
460
|
+
}));
|
|
459
461
|
const deleteThread = vi.fn(async () => {});
|
|
460
462
|
const cloud = {
|
|
461
463
|
threads: {
|
|
@@ -521,7 +523,9 @@ describe("AISDKThreads", () => {
|
|
|
521
523
|
external_id: null,
|
|
522
524
|
metadata: null,
|
|
523
525
|
});
|
|
524
|
-
const create = vi.fn
|
|
526
|
+
const create = vi.fn<AssistantCloud["threads"]["messages"]["create"]>(
|
|
527
|
+
async () => ({ message_id: "remote-message-1" }),
|
|
528
|
+
);
|
|
525
529
|
const cloud = {
|
|
526
530
|
threads: {
|
|
527
531
|
list: vi.fn(async () => ({
|
|
@@ -602,9 +606,9 @@ describe("AISDKThreads", () => {
|
|
|
602
606
|
external_id: null,
|
|
603
607
|
metadata: null,
|
|
604
608
|
});
|
|
605
|
-
const createMessage = vi.fn
|
|
606
|
-
|
|
607
|
-
}));
|
|
609
|
+
const createMessage = vi.fn<
|
|
610
|
+
AssistantCloud["threads"]["messages"]["create"]
|
|
611
|
+
>(async () => ({ message_id: "remote-message-1" }));
|
|
608
612
|
const cloud = {
|
|
609
613
|
threads: {
|
|
610
614
|
list: vi.fn(async () => ({ threads: [cloudThread("t1")] })),
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { resource, useResource, withKey } from "@assistant-ui/tap";
|
|
4
4
|
import { useEffect, useMemo, useState } from "react";
|
|
5
|
-
import { Chat,
|
|
5
|
+
import type { Chat, UIMessage } from "@ai-sdk/react";
|
|
6
6
|
import type { ChatTransport } from "ai";
|
|
7
7
|
import type { AssistantCloud } from "assistant-cloud";
|
|
8
8
|
import {
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
import { useAui } from "@assistant-ui/store";
|
|
21
21
|
import { AssistantChatTransport } from "../transport/AssistantChatTransport";
|
|
22
22
|
import {
|
|
23
|
+
createChat,
|
|
23
24
|
splitChatThreadOptions,
|
|
24
25
|
useChatThread,
|
|
25
26
|
type ChatThreadOptions,
|
|
@@ -91,17 +92,7 @@ const createChatEntry = <UI_MESSAGE extends UIMessage>(
|
|
|
91
92
|
? options.transport.__internal_clone()
|
|
92
93
|
: options.transport;
|
|
93
94
|
return {
|
|
94
|
-
chat:
|
|
95
|
-
...chatInit,
|
|
96
|
-
id: threadId,
|
|
97
|
-
transport,
|
|
98
|
-
onToolCall: (arg) => optionsRef.current?.onToolCall?.(arg),
|
|
99
|
-
onData: (arg) => optionsRef.current?.onData?.(arg),
|
|
100
|
-
onFinish: (arg) => optionsRef.current?.onFinish?.(arg),
|
|
101
|
-
onError: (arg) => optionsRef.current?.onError?.(arg),
|
|
102
|
-
sendAutomaticallyWhen: (arg) =>
|
|
103
|
-
optionsRef.current?.sendAutomaticallyWhen?.(arg) ?? false,
|
|
104
|
-
}),
|
|
95
|
+
chat: createChat({ ...chatInit, id: threadId, transport }, optionsRef),
|
|
105
96
|
transport,
|
|
106
97
|
repository: new MessageRepository(),
|
|
107
98
|
optionsRef,
|