@assistant-ui/react-ai-sdk 1.3.23 → 1.3.25
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 +14 -40
- package/dist/frontendTools.d.ts +2 -6
- package/dist/frontendTools.d.ts.map +1 -1
- package/dist/frontendTools.js +35 -3
- package/dist/frontendTools.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/modelContentEnvelope.d.ts +14 -0
- package/dist/modelContentEnvelope.d.ts.map +1 -0
- package/dist/modelContentEnvelope.js +20 -0
- package/dist/modelContentEnvelope.js.map +1 -0
- package/dist/ui/resumable.d.ts +20 -0
- package/dist/ui/resumable.d.ts.map +1 -0
- package/dist/ui/resumable.js +25 -0
- package/dist/ui/resumable.js.map +1 -0
- package/dist/ui/use-chat/AssistantChatTransport.d.ts +7 -1
- package/dist/ui/use-chat/AssistantChatTransport.d.ts.map +1 -1
- package/dist/ui/use-chat/AssistantChatTransport.js +73 -2
- package/dist/ui/use-chat/AssistantChatTransport.js.map +1 -1
- package/dist/ui/use-chat/useAISDKRuntime.d.ts +17 -2
- package/dist/ui/use-chat/useAISDKRuntime.d.ts.map +1 -1
- package/dist/ui/use-chat/useAISDKRuntime.js +12 -2
- package/dist/ui/use-chat/useAISDKRuntime.js.map +1 -1
- package/dist/ui/use-chat/useChatRuntime.d.ts +2 -0
- package/dist/ui/use-chat/useChatRuntime.d.ts.map +1 -1
- package/dist/ui/use-chat/useChatRuntime.js +31 -1
- package/dist/ui/use-chat/useChatRuntime.js.map +1 -1
- package/dist/ui/utils/convertMessage.d.ts +4 -0
- package/dist/ui/utils/convertMessage.d.ts.map +1 -1
- package/dist/ui/utils/convertMessage.js +87 -5
- package/dist/ui/utils/convertMessage.js.map +1 -1
- package/package.json +4 -5
- package/src/frontendTools.test.ts +128 -0
- package/src/frontendTools.ts +41 -6
- package/src/index.ts +8 -0
- package/src/modelContentEnvelope.ts +39 -0
- package/src/ui/resumable.ts +42 -0
- package/src/ui/use-chat/AssistantChatTransport.ts +104 -3
- package/src/ui/use-chat/useAISDKRuntime.test.ts +50 -0
- package/src/ui/use-chat/useAISDKRuntime.ts +34 -1
- package/src/ui/use-chat/useChatRuntime.ts +38 -0
- package/src/ui/utils/convertMessage.test.ts +331 -0
- package/src/ui/utils/convertMessage.ts +107 -16
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
+
import type { ReadonlyJSONObject } from "assistant-stream/utils";
|
|
2
3
|
import { AISDKMessageConverter } from "./convertMessage";
|
|
3
4
|
|
|
4
5
|
describe("AISDKMessageConverter", () => {
|
|
@@ -37,6 +38,94 @@ describe("AISDKMessageConverter", () => {
|
|
|
37
38
|
expect(converted[0]?.attachments?.[1]?.type).toBe("file");
|
|
38
39
|
});
|
|
39
40
|
|
|
41
|
+
it("converts source-document parts into document sources", () => {
|
|
42
|
+
const converted = AISDKMessageConverter.toThreadMessages([
|
|
43
|
+
{
|
|
44
|
+
id: "a1",
|
|
45
|
+
role: "assistant",
|
|
46
|
+
parts: [
|
|
47
|
+
{
|
|
48
|
+
type: "source-document",
|
|
49
|
+
sourceId: "doc_123",
|
|
50
|
+
title: "proposal.pdf",
|
|
51
|
+
mediaType: "application/pdf",
|
|
52
|
+
filename: "proposal.pdf",
|
|
53
|
+
providerMetadata: {
|
|
54
|
+
openai: {
|
|
55
|
+
type: "file_citation",
|
|
56
|
+
fileId: "file_123",
|
|
57
|
+
index: 0,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
} as any,
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
expect(converted).toHaveLength(1);
|
|
66
|
+
expect(converted[0]?.role).toBe("assistant");
|
|
67
|
+
|
|
68
|
+
const sourcePart = converted[0]?.content.find(
|
|
69
|
+
(part): part is any => part.type === "source",
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
expect(sourcePart).toMatchObject({
|
|
73
|
+
type: "source",
|
|
74
|
+
sourceType: "document",
|
|
75
|
+
id: "doc_123",
|
|
76
|
+
title: "proposal.pdf",
|
|
77
|
+
mediaType: "application/pdf",
|
|
78
|
+
filename: "proposal.pdf",
|
|
79
|
+
providerMetadata: {
|
|
80
|
+
openai: {
|
|
81
|
+
type: "file_citation",
|
|
82
|
+
fileId: "file_123",
|
|
83
|
+
index: 0,
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("converts source-url parts without synthesizing missing optional fields", () => {
|
|
90
|
+
const converted = AISDKMessageConverter.toThreadMessages([
|
|
91
|
+
{
|
|
92
|
+
id: "a1",
|
|
93
|
+
role: "assistant",
|
|
94
|
+
parts: [
|
|
95
|
+
{
|
|
96
|
+
type: "source-url",
|
|
97
|
+
sourceId: "url_123",
|
|
98
|
+
url: "https://example.com/report",
|
|
99
|
+
providerMetadata: {
|
|
100
|
+
openai: {
|
|
101
|
+
type: "url_citation",
|
|
102
|
+
index: 1,
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
} as any,
|
|
108
|
+
]);
|
|
109
|
+
|
|
110
|
+
const sourcePart = converted[0]?.content.find(
|
|
111
|
+
(part): part is any => part.type === "source",
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
expect(sourcePart).toMatchObject({
|
|
115
|
+
type: "source",
|
|
116
|
+
sourceType: "url",
|
|
117
|
+
id: "url_123",
|
|
118
|
+
url: "https://example.com/report",
|
|
119
|
+
providerMetadata: {
|
|
120
|
+
openai: {
|
|
121
|
+
type: "url_citation",
|
|
122
|
+
index: 1,
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
expect(sourcePart).not.toHaveProperty("title");
|
|
127
|
+
});
|
|
128
|
+
|
|
40
129
|
it("converts assistant image file parts into file content", () => {
|
|
41
130
|
const converted = AISDKMessageConverter.toThreadMessages([
|
|
42
131
|
{
|
|
@@ -307,4 +396,246 @@ describe("AISDKMessageConverter", () => {
|
|
|
307
396
|
limit: 5,
|
|
308
397
|
});
|
|
309
398
|
});
|
|
399
|
+
|
|
400
|
+
it("preserves last good input when AI SDK briefly emits null input", () => {
|
|
401
|
+
const metadata = {
|
|
402
|
+
toolArgsKeyOrderCache: new Map<string, Map<string, string[]>>(),
|
|
403
|
+
toolLastInputCache: new Map<string, ReadonlyJSONObject>(),
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
const convertWithInput = (input: unknown) =>
|
|
407
|
+
AISDKMessageConverter.toThreadMessages(
|
|
408
|
+
[
|
|
409
|
+
{
|
|
410
|
+
id: "a1",
|
|
411
|
+
role: "assistant",
|
|
412
|
+
parts: [
|
|
413
|
+
{
|
|
414
|
+
type: "tool-weather",
|
|
415
|
+
toolCallId: "tc-1",
|
|
416
|
+
state: "input-streaming",
|
|
417
|
+
input,
|
|
418
|
+
},
|
|
419
|
+
],
|
|
420
|
+
} as any,
|
|
421
|
+
],
|
|
422
|
+
false,
|
|
423
|
+
metadata,
|
|
424
|
+
)[0]?.content.find((part): part is any => part.type === "tool-call");
|
|
425
|
+
|
|
426
|
+
const first = convertWithInput({ city: "NYC" });
|
|
427
|
+
expect(first?.argsText).toBe('{"city":"NYC');
|
|
428
|
+
expect(first?.args).toEqual({ city: "NYC" });
|
|
429
|
+
|
|
430
|
+
const dropped = convertWithInput(null);
|
|
431
|
+
expect(dropped?.argsText).toBe('{"city":"NYC');
|
|
432
|
+
expect(dropped?.args).toEqual({ city: "NYC" });
|
|
433
|
+
|
|
434
|
+
const undef = convertWithInput(undefined);
|
|
435
|
+
expect(undef?.argsText).toBe('{"city":"NYC');
|
|
436
|
+
expect(undef?.args).toEqual({ city: "NYC" });
|
|
437
|
+
|
|
438
|
+
const grown = convertWithInput({ city: "NYC", units: "F" });
|
|
439
|
+
expect(grown?.argsText).toBe('{"city":"NYC","units":"F');
|
|
440
|
+
expect(grown?.args).toEqual({ city: "NYC", units: "F" });
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
it("preserves last good input across terminal state transitions", () => {
|
|
444
|
+
const metadata = {
|
|
445
|
+
toolArgsKeyOrderCache: new Map<string, Map<string, string[]>>(),
|
|
446
|
+
toolLastInputCache: new Map<string, ReadonlyJSONObject>(),
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
AISDKMessageConverter.toThreadMessages(
|
|
450
|
+
[
|
|
451
|
+
{
|
|
452
|
+
id: "a1",
|
|
453
|
+
role: "assistant",
|
|
454
|
+
parts: [
|
|
455
|
+
{
|
|
456
|
+
type: "tool-weather",
|
|
457
|
+
toolCallId: "tc-1",
|
|
458
|
+
state: "input-available",
|
|
459
|
+
input: { city: "NYC" },
|
|
460
|
+
},
|
|
461
|
+
],
|
|
462
|
+
} as any,
|
|
463
|
+
],
|
|
464
|
+
false,
|
|
465
|
+
metadata,
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
const terminal = AISDKMessageConverter.toThreadMessages(
|
|
469
|
+
[
|
|
470
|
+
{
|
|
471
|
+
id: "a1",
|
|
472
|
+
role: "assistant",
|
|
473
|
+
parts: [
|
|
474
|
+
{
|
|
475
|
+
type: "tool-weather",
|
|
476
|
+
toolCallId: "tc-1",
|
|
477
|
+
state: "output-available",
|
|
478
|
+
input: null,
|
|
479
|
+
output: { temp: 70 },
|
|
480
|
+
},
|
|
481
|
+
],
|
|
482
|
+
} as any,
|
|
483
|
+
],
|
|
484
|
+
false,
|
|
485
|
+
metadata,
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
const call = terminal[0]?.content.find(
|
|
489
|
+
(part): part is any => part.type === "tool-call",
|
|
490
|
+
);
|
|
491
|
+
expect(call?.args).toEqual({ city: "NYC" });
|
|
492
|
+
expect(call?.result).toEqual({ temp: 70 });
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it("unwraps the modelContent envelope produced by frontend tool execution", () => {
|
|
496
|
+
const converted = AISDKMessageConverter.toThreadMessages([
|
|
497
|
+
{
|
|
498
|
+
id: "a1",
|
|
499
|
+
role: "assistant",
|
|
500
|
+
parts: [
|
|
501
|
+
{
|
|
502
|
+
type: "tool-readPdf",
|
|
503
|
+
toolCallId: "tc-pdf",
|
|
504
|
+
state: "output-available",
|
|
505
|
+
input: {},
|
|
506
|
+
output: {
|
|
507
|
+
__aui_modelContent: [
|
|
508
|
+
{ type: "text", text: "PDF contents:" },
|
|
509
|
+
{
|
|
510
|
+
type: "file",
|
|
511
|
+
data: "JVBERi0xLjQK",
|
|
512
|
+
mediaType: "application/pdf",
|
|
513
|
+
},
|
|
514
|
+
],
|
|
515
|
+
value: { mediaType: "application/pdf", base64: "JVBERi0xLjQK" },
|
|
516
|
+
},
|
|
517
|
+
},
|
|
518
|
+
],
|
|
519
|
+
} as any,
|
|
520
|
+
]);
|
|
521
|
+
|
|
522
|
+
const call = converted[0]?.content.find(
|
|
523
|
+
(part): part is any => part.type === "tool-call",
|
|
524
|
+
);
|
|
525
|
+
expect(call?.result).toEqual({
|
|
526
|
+
mediaType: "application/pdf",
|
|
527
|
+
base64: "JVBERi0xLjQK",
|
|
528
|
+
});
|
|
529
|
+
expect(call?.modelContent).toEqual([
|
|
530
|
+
{ type: "text", text: "PDF contents:" },
|
|
531
|
+
{
|
|
532
|
+
type: "file",
|
|
533
|
+
data: "JVBERi0xLjQK",
|
|
534
|
+
mediaType: "application/pdf",
|
|
535
|
+
},
|
|
536
|
+
]);
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
it("leaves a plain output untouched when no envelope is present", () => {
|
|
540
|
+
const converted = AISDKMessageConverter.toThreadMessages([
|
|
541
|
+
{
|
|
542
|
+
id: "a1",
|
|
543
|
+
role: "assistant",
|
|
544
|
+
parts: [
|
|
545
|
+
{
|
|
546
|
+
type: "tool-weather",
|
|
547
|
+
toolCallId: "tc-1",
|
|
548
|
+
state: "output-available",
|
|
549
|
+
input: { city: "NYC" },
|
|
550
|
+
output: { temp: 72 },
|
|
551
|
+
},
|
|
552
|
+
],
|
|
553
|
+
} as any,
|
|
554
|
+
]);
|
|
555
|
+
|
|
556
|
+
const call = converted[0]?.content.find(
|
|
557
|
+
(part): part is any => part.type === "tool-call",
|
|
558
|
+
);
|
|
559
|
+
expect(call?.result).toEqual({ temp: 72 });
|
|
560
|
+
expect(call?.modelContent).toBeUndefined();
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
it("forwards callProviderMetadata.mcp.app onto ToolCallMessagePart.mcp.app", () => {
|
|
564
|
+
const converted = AISDKMessageConverter.toThreadMessages([
|
|
565
|
+
{
|
|
566
|
+
id: "a1",
|
|
567
|
+
role: "assistant",
|
|
568
|
+
parts: [
|
|
569
|
+
{
|
|
570
|
+
type: "tool-search",
|
|
571
|
+
toolCallId: "tc-1",
|
|
572
|
+
state: "output-available",
|
|
573
|
+
input: { query: "hi" },
|
|
574
|
+
output: { results: [] },
|
|
575
|
+
callProviderMetadata: {
|
|
576
|
+
mcp: {
|
|
577
|
+
app: {
|
|
578
|
+
resourceUri: "ui://example/search",
|
|
579
|
+
mimeType: "text/html;profile=mcp-app",
|
|
580
|
+
visibility: ["app", "model", "bogus"],
|
|
581
|
+
},
|
|
582
|
+
},
|
|
583
|
+
},
|
|
584
|
+
},
|
|
585
|
+
],
|
|
586
|
+
} as any,
|
|
587
|
+
]);
|
|
588
|
+
|
|
589
|
+
const call = converted[0]?.content.find(
|
|
590
|
+
(part): part is any => part.type === "tool-call",
|
|
591
|
+
);
|
|
592
|
+
expect(call?.mcp?.app).toEqual({
|
|
593
|
+
resourceUri: "ui://example/search",
|
|
594
|
+
mimeType: "text/html;profile=mcp-app",
|
|
595
|
+
visibility: ["app", "model"],
|
|
596
|
+
});
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
it("memoizes MCP app metadata across conversions by resourceUri", () => {
|
|
600
|
+
const metadata = {
|
|
601
|
+
mcpAppMetadataCache: new Map(),
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
const buildMessage = (id: string) => ({
|
|
605
|
+
id,
|
|
606
|
+
role: "assistant" as const,
|
|
607
|
+
parts: [
|
|
608
|
+
{
|
|
609
|
+
type: "tool-search",
|
|
610
|
+
toolCallId: `${id}-call`,
|
|
611
|
+
state: "output-available",
|
|
612
|
+
input: { q: "hi" },
|
|
613
|
+
output: {},
|
|
614
|
+
callProviderMetadata: {
|
|
615
|
+
mcp: { app: { resourceUri: "ui://example/search" } },
|
|
616
|
+
},
|
|
617
|
+
} as any,
|
|
618
|
+
],
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
const first = AISDKMessageConverter.toThreadMessages(
|
|
622
|
+
[buildMessage("a1")],
|
|
623
|
+
false,
|
|
624
|
+
metadata,
|
|
625
|
+
);
|
|
626
|
+
const second = AISDKMessageConverter.toThreadMessages(
|
|
627
|
+
[buildMessage("a2")],
|
|
628
|
+
false,
|
|
629
|
+
metadata,
|
|
630
|
+
);
|
|
631
|
+
|
|
632
|
+
const firstApp = first[0]?.content.find(
|
|
633
|
+
(p): p is any => p.type === "tool-call",
|
|
634
|
+
)?.mcp?.app;
|
|
635
|
+
const secondApp = second[0]?.content.find(
|
|
636
|
+
(p): p is any => p.type === "tool-call",
|
|
637
|
+
)?.mcp?.app;
|
|
638
|
+
expect(firstApp).toBeDefined();
|
|
639
|
+
expect(firstApp).toBe(secondApp);
|
|
640
|
+
});
|
|
310
641
|
});
|
|
@@ -3,27 +3,75 @@ import {
|
|
|
3
3
|
createMessageConverter as unstable_createMessageConverter,
|
|
4
4
|
type useExternalMessageConverter,
|
|
5
5
|
} from "@assistant-ui/core/react";
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
import {
|
|
7
|
+
isMcpAppUri,
|
|
8
|
+
type ReasoningMessagePart,
|
|
9
|
+
type ToolCallMessagePart,
|
|
10
|
+
type TextMessagePart,
|
|
11
|
+
type DataMessagePart,
|
|
12
|
+
type SourceMessagePart,
|
|
13
|
+
type SourceProviderMetadata,
|
|
14
|
+
type FileMessagePart,
|
|
15
|
+
type ThreadMessageLike,
|
|
16
|
+
type McpAppMetadata,
|
|
14
17
|
} from "@assistant-ui/core";
|
|
15
18
|
import type { ReadonlyJSONObject } from "assistant-stream/utils";
|
|
19
|
+
import { unwrapModelContentEnvelope } from "../../modelContentEnvelope";
|
|
16
20
|
|
|
17
21
|
type MessageMetadata = ThreadMessageLike["metadata"];
|
|
18
22
|
export type AISDKMessageConverterMetadata =
|
|
19
23
|
useExternalMessageConverter.Metadata & {
|
|
20
24
|
toolArgsKeyOrderCache?: Map<string, Map<string, string[]>>;
|
|
25
|
+
toolLastInputCache?: Map<string, ReadonlyJSONObject>;
|
|
26
|
+
mcpAppMetadataCache?: Map<string, McpAppMetadata>;
|
|
21
27
|
};
|
|
22
28
|
|
|
23
29
|
function stripClosingDelimiters(json: string): string {
|
|
24
30
|
return json.replace(/[}\]"]+$/, "");
|
|
25
31
|
}
|
|
26
32
|
|
|
33
|
+
const MCP_APP_METADATA_CACHE_MAX = 100;
|
|
34
|
+
|
|
35
|
+
function extractMcpAppMetadata(
|
|
36
|
+
part: unknown,
|
|
37
|
+
cache: Map<string, McpAppMetadata> | undefined,
|
|
38
|
+
): McpAppMetadata | undefined {
|
|
39
|
+
if (!part || typeof part !== "object") return undefined;
|
|
40
|
+
const meta = (part as { callProviderMetadata?: unknown })
|
|
41
|
+
.callProviderMetadata;
|
|
42
|
+
if (!meta || typeof meta !== "object") return undefined;
|
|
43
|
+
const mcp = (meta as { mcp?: unknown }).mcp;
|
|
44
|
+
if (!mcp || typeof mcp !== "object") return undefined;
|
|
45
|
+
const app = (mcp as { app?: unknown }).app;
|
|
46
|
+
if (!app || typeof app !== "object") return undefined;
|
|
47
|
+
const a = app as Record<string, unknown>;
|
|
48
|
+
if (typeof a["resourceUri"] !== "string") return undefined;
|
|
49
|
+
if (!isMcpAppUri(a["resourceUri"])) return undefined;
|
|
50
|
+
const cached = cache?.get(a["resourceUri"]);
|
|
51
|
+
if (cached) {
|
|
52
|
+
cache!.delete(a["resourceUri"]);
|
|
53
|
+
cache!.set(a["resourceUri"], cached);
|
|
54
|
+
return cached;
|
|
55
|
+
}
|
|
56
|
+
const out: { -readonly [K in keyof McpAppMetadata]: McpAppMetadata[K] } = {
|
|
57
|
+
resourceUri: a["resourceUri"],
|
|
58
|
+
};
|
|
59
|
+
if (typeof a["mimeType"] === "string") out.mimeType = a["mimeType"];
|
|
60
|
+
if (Array.isArray(a["visibility"])) {
|
|
61
|
+
out.visibility = a["visibility"].filter(
|
|
62
|
+
(v): v is "model" | "app" => v === "model" || v === "app",
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
if (cache) {
|
|
66
|
+
if (cache.size >= MCP_APP_METADATA_CACHE_MAX) {
|
|
67
|
+
const oldest = cache.keys().next().value;
|
|
68
|
+
if (oldest !== undefined) cache.delete(oldest);
|
|
69
|
+
}
|
|
70
|
+
cache.set(a["resourceUri"], out);
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
|
|
27
75
|
const hasOwn = (value: object, key: string) => Object.hasOwn(value, key);
|
|
28
76
|
|
|
29
77
|
const stabilizeToolArgsValue = (
|
|
@@ -147,14 +195,28 @@ function convertParts(
|
|
|
147
195
|
const toolName = getToolName(part);
|
|
148
196
|
const toolCallId = part.toolCallId;
|
|
149
197
|
const argsKeyOrderCacheKey = `${message.id}:${toolCallId}`;
|
|
150
|
-
|
|
151
|
-
|
|
198
|
+
|
|
199
|
+
const rawInput = part.input as ReadonlyJSONObject | null | undefined;
|
|
200
|
+
let args: ReadonlyJSONObject;
|
|
201
|
+
if (
|
|
202
|
+
rawInput != null &&
|
|
203
|
+
typeof rawInput === "object" &&
|
|
204
|
+
!Array.isArray(rawInput)
|
|
205
|
+
) {
|
|
206
|
+
args = rawInput;
|
|
207
|
+
metadata.toolLastInputCache?.set(argsKeyOrderCacheKey, args);
|
|
208
|
+
} else {
|
|
209
|
+
args = metadata.toolLastInputCache?.get(argsKeyOrderCacheKey) ?? {};
|
|
210
|
+
}
|
|
152
211
|
|
|
153
212
|
let result: unknown;
|
|
213
|
+
let modelContent: ToolCallMessagePart["modelContent"];
|
|
154
214
|
let isError = false;
|
|
155
215
|
|
|
156
216
|
if (part.state === "output-available") {
|
|
157
|
-
|
|
217
|
+
const unwrapped = unwrapModelContentEnvelope(part.output);
|
|
218
|
+
result = unwrapped.result;
|
|
219
|
+
modelContent = unwrapped.modelContent;
|
|
158
220
|
} else if (part.state === "output-error") {
|
|
159
221
|
isError = true;
|
|
160
222
|
result = { error: part.errorText };
|
|
@@ -177,9 +239,20 @@ function convertParts(
|
|
|
177
239
|
argsText = stripClosingDelimiters(argsText);
|
|
178
240
|
} else {
|
|
179
241
|
metadata.toolArgsKeyOrderCache?.delete(argsKeyOrderCacheKey);
|
|
242
|
+
if (
|
|
243
|
+
part.state === "output-available" ||
|
|
244
|
+
part.state === "output-error" ||
|
|
245
|
+
part.state === "output-denied"
|
|
246
|
+
) {
|
|
247
|
+
metadata.toolLastInputCache?.delete(argsKeyOrderCacheKey);
|
|
248
|
+
}
|
|
180
249
|
}
|
|
181
250
|
|
|
182
251
|
const toolStatus = metadata.toolStatuses?.[toolCallId];
|
|
252
|
+
const mcpApp = extractMcpAppMetadata(
|
|
253
|
+
part,
|
|
254
|
+
metadata.mcpAppMetadataCache,
|
|
255
|
+
);
|
|
183
256
|
return {
|
|
184
257
|
type: "tool-call",
|
|
185
258
|
toolName,
|
|
@@ -188,6 +261,8 @@ function convertParts(
|
|
|
188
261
|
args,
|
|
189
262
|
result,
|
|
190
263
|
isError,
|
|
264
|
+
...(modelContent !== undefined && { modelContent }),
|
|
265
|
+
...(mcpApp && { mcp: { app: mcpApp } }),
|
|
191
266
|
...getToolInterrupt(part, toolStatus),
|
|
192
267
|
} satisfies ToolCallMessagePart;
|
|
193
268
|
}
|
|
@@ -198,7 +273,13 @@ function convertParts(
|
|
|
198
273
|
sourceType: "url",
|
|
199
274
|
id: part.sourceId,
|
|
200
275
|
url: part.url,
|
|
201
|
-
title: part.title
|
|
276
|
+
...(part.title != null ? { title: part.title } : undefined),
|
|
277
|
+
...(part.providerMetadata != null
|
|
278
|
+
? {
|
|
279
|
+
providerMetadata:
|
|
280
|
+
part.providerMetadata as SourceProviderMetadata,
|
|
281
|
+
}
|
|
282
|
+
: undefined),
|
|
202
283
|
} satisfies SourceMessagePart;
|
|
203
284
|
}
|
|
204
285
|
|
|
@@ -212,10 +293,20 @@ function convertParts(
|
|
|
212
293
|
}
|
|
213
294
|
|
|
214
295
|
if (part.type === "source-document") {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
296
|
+
return {
|
|
297
|
+
type: "source",
|
|
298
|
+
sourceType: "document",
|
|
299
|
+
id: part.sourceId,
|
|
300
|
+
title: part.title,
|
|
301
|
+
mediaType: part.mediaType,
|
|
302
|
+
...(part.filename != null ? { filename: part.filename } : undefined),
|
|
303
|
+
...(part.providerMetadata != null
|
|
304
|
+
? {
|
|
305
|
+
providerMetadata:
|
|
306
|
+
part.providerMetadata as SourceProviderMetadata,
|
|
307
|
+
}
|
|
308
|
+
: undefined),
|
|
309
|
+
} satisfies SourceMessagePart;
|
|
219
310
|
}
|
|
220
311
|
|
|
221
312
|
if (part.type.startsWith("data-")) {
|