@convex-dev/agent 0.6.3 → 0.7.0-alpha.0
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/MIGRATION.md +60 -116
- package/dist/client/messages.d.ts +1 -398
- package/dist/client/messages.d.ts.map +1 -1
- package/dist/client/messages.js +3 -47
- package/dist/client/messages.js.map +1 -1
- package/dist/client/types.d.ts +1 -455
- package/dist/client/types.d.ts.map +1 -1
- package/dist/component/_generated/component.d.ts +805 -45
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/apiKeys.js +5 -5
- package/dist/component/apiKeys.js.map +1 -1
- package/dist/component/files.d.ts.map +1 -1
- package/dist/component/files.js +13 -11
- package/dist/component/files.js.map +1 -1
- package/dist/component/messages.d.ts +1252 -201
- package/dist/component/messages.d.ts.map +1 -1
- package/dist/component/messages.js +83 -47
- package/dist/component/messages.js.map +1 -1
- package/dist/component/schema.d.ts +2593 -332
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/streams.d.ts +7 -1
- package/dist/component/streams.d.ts.map +1 -1
- package/dist/component/streams.js +46 -47
- package/dist/component/streams.js.map +1 -1
- package/dist/component/threads.d.ts.map +1 -1
- package/dist/component/threads.js +8 -9
- package/dist/component/threads.js.map +1 -1
- package/dist/component/users.js +2 -2
- package/dist/component/users.js.map +1 -1
- package/dist/component/vector/index.d.ts.map +1 -1
- package/dist/component/vector/index.js +14 -8
- package/dist/component/vector/index.js.map +1 -1
- package/dist/react/index.d.ts +5 -5
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +5 -5
- package/dist/react/index.js.map +1 -1
- package/dist/shared.d.ts +19 -7
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +4 -4
- package/dist/shared.js.map +1 -1
- package/dist/streaming/materializePersistedUIMessageChunks.d.ts +16 -0
- package/dist/streaming/materializePersistedUIMessageChunks.d.ts.map +1 -0
- package/dist/streaming/materializePersistedUIMessageChunks.js +302 -0
- package/dist/streaming/materializePersistedUIMessageChunks.js.map +1 -0
- package/dist/streaming/persistedUIMessageChunks.d.ts +106 -0
- package/dist/streaming/persistedUIMessageChunks.d.ts.map +1 -0
- package/dist/streaming/persistedUIMessageChunks.js +475 -0
- package/dist/streaming/persistedUIMessageChunks.js.map +1 -0
- package/dist/validators.d.ts +10026 -1055
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.js +71 -9
- package/dist/validators.js.map +1 -1
- package/dist/{UIMessages.d.ts → vercel/UIMessages.d.ts} +1 -1
- package/dist/vercel/UIMessages.d.ts.map +1 -0
- package/dist/{UIMessages.js → vercel/UIMessages.js} +63 -1
- package/dist/vercel/UIMessages.js.map +1 -0
- package/dist/{client → vercel/client}/createTool.d.ts +23 -16
- package/dist/vercel/client/createTool.d.ts.map +1 -0
- package/dist/{client → vercel/client}/createTool.js +2 -0
- package/dist/vercel/client/createTool.js.map +1 -0
- package/dist/{client → vercel/client}/definePlaygroundAPI.d.ts +593 -134
- package/dist/{client → vercel/client}/definePlaygroundAPI.d.ts.map +1 -1
- package/dist/{client → vercel/client}/definePlaygroundAPI.js +13 -9
- package/dist/vercel/client/definePlaygroundAPI.js.map +1 -0
- package/dist/{client → vercel/client}/files.d.ts +2 -2
- package/dist/vercel/client/files.d.ts.map +1 -0
- package/dist/vercel/client/files.js.map +1 -0
- package/dist/vercel/client/messages.d.ts +542 -0
- package/dist/vercel/client/messages.d.ts.map +1 -0
- package/dist/vercel/client/messages.js +66 -0
- package/dist/vercel/client/messages.js.map +1 -0
- package/dist/{client → vercel/client}/mockModel.d.ts +17 -17
- package/dist/vercel/client/mockModel.d.ts.map +1 -0
- package/dist/{client → vercel/client}/mockModel.js +12 -13
- package/dist/vercel/client/mockModel.js.map +1 -0
- package/dist/{client → vercel/client}/saveInputMessages.d.ts +2 -2
- package/dist/vercel/client/saveInputMessages.d.ts.map +1 -0
- package/dist/vercel/client/saveInputMessages.js.map +1 -0
- package/dist/{client → vercel/client}/search.d.ts +119 -22
- package/dist/vercel/client/search.d.ts.map +1 -0
- package/dist/{client → vercel/client}/search.js +2 -2
- package/dist/vercel/client/search.js.map +1 -0
- package/dist/{client → vercel/client}/start.d.ts +26 -11
- package/dist/vercel/client/start.d.ts.map +1 -0
- package/dist/{client → vercel/client}/start.js +64 -22
- package/dist/vercel/client/start.js.map +1 -0
- package/dist/{client → vercel/client}/streamText.d.ts +13 -12
- package/dist/vercel/client/streamText.d.ts.map +1 -0
- package/dist/{client → vercel/client}/streamText.js +43 -9
- package/dist/vercel/client/streamText.js.map +1 -0
- package/dist/{client → vercel/client}/streaming.d.ts +1410 -183
- package/dist/vercel/client/streaming.d.ts.map +1 -0
- package/dist/{client → vercel/client}/streaming.js +120 -52
- package/dist/vercel/client/streaming.js.map +1 -0
- package/dist/vercel/client/types.d.ts +456 -0
- package/dist/vercel/client/types.d.ts.map +1 -0
- package/dist/vercel/client/types.js +3 -0
- package/dist/vercel/client/types.js.map +1 -0
- package/dist/{client → vercel/client}/utils.d.ts +3 -2
- package/dist/vercel/client/utils.d.ts.map +1 -0
- package/dist/{client → vercel/client}/utils.js +2 -2
- package/dist/vercel/client/utils.js.map +1 -0
- package/dist/vercel/deltas.d.ts +31 -0
- package/dist/vercel/deltas.d.ts.map +1 -0
- package/dist/vercel/deltas.js +525 -0
- package/dist/vercel/deltas.js.map +1 -0
- package/dist/{client → vercel}/index.d.ts +484 -97
- package/dist/vercel/index.d.ts.map +1 -0
- package/dist/{client → vercel}/index.js +65 -38
- package/dist/vercel/index.js.map +1 -0
- package/dist/{mapping.d.ts → vercel/mapping.d.ts} +5 -13
- package/dist/vercel/mapping.d.ts.map +1 -0
- package/dist/{mapping.js → vercel/mapping.js} +411 -112
- package/dist/vercel/mapping.js.map +1 -0
- package/dist/{react → vercel/react}/optimisticallySendMessage.d.ts +2 -2
- package/dist/vercel/react/optimisticallySendMessage.d.ts.map +1 -0
- package/dist/vercel/react/optimisticallySendMessage.js.map +1 -0
- package/dist/{react → vercel/react}/types.d.ts +1 -1
- package/dist/vercel/react/types.d.ts.map +1 -0
- package/dist/{react → vercel/react}/types.js.map +1 -1
- package/dist/{react → vercel/react}/useDeltaStreams.d.ts +1 -1
- package/dist/vercel/react/useDeltaStreams.d.ts.map +1 -0
- package/dist/{react → vercel/react}/useDeltaStreams.js +1 -1
- package/dist/vercel/react/useDeltaStreams.js.map +1 -0
- package/dist/{react → vercel/react}/useStreamingUIMessages.d.ts +1 -1
- package/dist/vercel/react/useStreamingUIMessages.d.ts.map +1 -0
- package/dist/vercel/react/useStreamingUIMessages.js +112 -0
- package/dist/vercel/react/useStreamingUIMessages.js.map +1 -0
- package/dist/{react → vercel/react}/useThreadMessages.d.ts +1 -1
- package/dist/vercel/react/useThreadMessages.d.ts.map +1 -0
- package/dist/{react → vercel/react}/useThreadMessages.js +1 -1
- package/dist/vercel/react/useThreadMessages.js.map +1 -0
- package/dist/{react → vercel/react}/useUIMessages.d.ts +2 -1
- package/dist/vercel/react/useUIMessages.d.ts.map +1 -0
- package/dist/{react → vercel/react}/useUIMessages.js +8 -4
- package/dist/vercel/react/useUIMessages.js.map +1 -0
- package/package.json +30 -22
- package/src/client/messages.ts +5 -107
- package/src/client/types.ts +0 -621
- package/src/component/_generated/component.ts +1089 -48
- package/src/component/apiKeys.ts +5 -5
- package/src/component/files.test.ts +1 -1
- package/src/component/files.ts +14 -12
- package/src/component/messages.test.ts +273 -1
- package/src/component/messages.ts +99 -57
- package/src/component/streams.ts +80 -64
- package/src/component/threads.ts +12 -9
- package/src/component/users.ts +2 -2
- package/src/component/vector/index.ts +14 -7
- package/src/eslintConfig.test.ts +44 -0
- package/src/react/index.ts +8 -5
- package/src/shared.ts +26 -36
- package/src/streaming/materializePersistedUIMessageChunks.test.ts +513 -0
- package/src/streaming/materializePersistedUIMessageChunks.ts +398 -0
- package/src/streaming/persistedUIMessageChunks.test.ts +205 -0
- package/src/streaming/persistedUIMessageChunks.ts +658 -0
- package/src/validators.test.ts +4 -1
- package/src/validators.ts +89 -8
- package/src/{UIMessages.combineUIMessages.test.ts → vercel/UIMessages.combineUIMessages.test.ts} +4 -1
- package/src/{UIMessages.ts → vercel/UIMessages.ts} +96 -5
- package/src/{client → vercel/client}/approval.test.ts +48 -19
- package/src/{client → vercel/client}/createTool.ts +63 -23
- package/src/{client → vercel/client}/definePlaygroundAPI.ts +44 -29
- package/src/{client → vercel/client}/files.ts +2 -2
- package/src/{client → vercel/client}/index.test.ts +162 -3
- package/src/vercel/client/messages.ts +182 -0
- package/src/{client → vercel/client}/mockModel.ts +38 -39
- package/src/{client → vercel/client}/saveInputMessages.test.ts +1 -1
- package/src/{client → vercel/client}/saveInputMessages.ts +7 -4
- package/src/{client → vercel/client}/search.test.ts +3 -3
- package/src/{client → vercel/client}/search.ts +4 -4
- package/src/{client → vercel/client}/setup.test.ts +6 -3
- package/src/vercel/client/start.test.ts +14 -0
- package/src/{client → vercel/client}/start.ts +147 -36
- package/src/vercel/client/streamText.test.ts +223 -0
- package/src/{client → vercel/client}/streamText.ts +102 -31
- package/src/{client → vercel/client}/streaming.integration.test.ts +40 -115
- package/src/{client → vercel/client}/streaming.test.ts +178 -4
- package/src/{client → vercel/client}/streaming.ts +114 -52
- package/src/vercel/client/types.test.ts +13 -0
- package/src/vercel/client/types.ts +679 -0
- package/src/{client → vercel/client}/utils.ts +13 -7
- package/src/vercel/deltas.test.ts +898 -0
- package/src/vercel/deltas.ts +633 -0
- package/src/{fromUIMessages.test.ts → vercel/fromUIMessages.test.ts} +17 -1
- package/src/{client → vercel}/index.ts +169 -62
- package/src/vercel/mapping.test.ts +965 -0
- package/src/{mapping.ts → vercel/mapping.ts} +501 -131
- package/src/{react → vercel/react}/optimisticallySendMessage.ts +2 -2
- package/src/{react → vercel/react}/types.ts +1 -1
- package/src/{react → vercel/react}/useDeltaStreams.ts +6 -2
- package/src/{react → vercel/react}/useStreamingUIMessages.ts +67 -35
- package/src/{react → vercel/react}/useThreadMessages.ts +2 -2
- package/src/{react → vercel/react}/useUIMessages.test.ts +80 -1
- package/src/{react → vercel/react}/useUIMessages.ts +13 -5
- package/src/{toUIMessages.test.ts → vercel/toUIMessages.test.ts} +49 -2
- package/dist/UIMessages.d.ts.map +0 -1
- package/dist/UIMessages.js.map +0 -1
- package/dist/client/createTool.d.ts.map +0 -1
- package/dist/client/createTool.js.map +0 -1
- package/dist/client/definePlaygroundAPI.js.map +0 -1
- package/dist/client/files.d.ts.map +0 -1
- package/dist/client/files.js.map +0 -1
- package/dist/client/index.d.ts.map +0 -1
- package/dist/client/index.js.map +0 -1
- package/dist/client/mockModel.d.ts.map +0 -1
- package/dist/client/mockModel.js.map +0 -1
- package/dist/client/saveInputMessages.d.ts.map +0 -1
- package/dist/client/saveInputMessages.js.map +0 -1
- package/dist/client/search.d.ts.map +0 -1
- package/dist/client/search.js.map +0 -1
- package/dist/client/start.d.ts.map +0 -1
- package/dist/client/start.js.map +0 -1
- package/dist/client/streamText.d.ts.map +0 -1
- package/dist/client/streamText.js.map +0 -1
- package/dist/client/streaming.d.ts.map +0 -1
- package/dist/client/streaming.js.map +0 -1
- package/dist/client/utils.d.ts.map +0 -1
- package/dist/client/utils.js.map +0 -1
- package/dist/deltas.d.ts +0 -43
- package/dist/deltas.d.ts.map +0 -1
- package/dist/deltas.js +0 -446
- package/dist/deltas.js.map +0 -1
- package/dist/mapping.d.ts.map +0 -1
- package/dist/mapping.js.map +0 -1
- package/dist/react/optimisticallySendMessage.d.ts.map +0 -1
- package/dist/react/optimisticallySendMessage.js.map +0 -1
- package/dist/react/types.d.ts.map +0 -1
- package/dist/react/useDeltaStreams.d.ts.map +0 -1
- package/dist/react/useDeltaStreams.js.map +0 -1
- package/dist/react/useStreamingUIMessages.d.ts.map +0 -1
- package/dist/react/useStreamingUIMessages.js +0 -92
- package/dist/react/useStreamingUIMessages.js.map +0 -1
- package/dist/react/useThreadMessages.d.ts.map +0 -1
- package/dist/react/useThreadMessages.js.map +0 -1
- package/dist/react/useUIMessages.d.ts.map +0 -1
- package/dist/react/useUIMessages.js.map +0 -1
- package/src/client/streamText.test.ts +0 -102
- package/src/deltas.test.ts +0 -626
- package/src/deltas.ts +0 -569
- package/src/mapping.test.ts +0 -402
- /package/dist/{client → vercel/client}/files.js +0 -0
- /package/dist/{client → vercel/client}/saveInputMessages.js +0 -0
- /package/dist/{react → vercel/react}/optimisticallySendMessage.js +0 -0
- /package/dist/{react → vercel/react}/types.js +0 -0
- /package/src/{UIMessages.test.ts → vercel/UIMessages.test.ts} +0 -0
|
@@ -0,0 +1,633 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type CustomContentUIPart,
|
|
3
|
+
type DynamicToolUIPart,
|
|
4
|
+
type ProviderMetadata,
|
|
5
|
+
type ReasoningFileUIPart,
|
|
6
|
+
type ReasoningUIPart,
|
|
7
|
+
type TextUIPart,
|
|
8
|
+
type ToolUIPart,
|
|
9
|
+
type UIMessageChunk,
|
|
10
|
+
} from "ai";
|
|
11
|
+
import { type UIMessage } from "./UIMessages.js";
|
|
12
|
+
import { joinText, sorted } from "../shared.js";
|
|
13
|
+
import {
|
|
14
|
+
type MessageStatus,
|
|
15
|
+
type StreamDelta,
|
|
16
|
+
type StreamMessage,
|
|
17
|
+
} from "../validators.js";
|
|
18
|
+
|
|
19
|
+
export function blankUIMessage<METADATA = unknown>(
|
|
20
|
+
streamMessage: StreamMessage & { metadata?: METADATA },
|
|
21
|
+
threadId: string,
|
|
22
|
+
): UIMessage<METADATA> {
|
|
23
|
+
return {
|
|
24
|
+
id: `stream:${streamMessage.streamId}`,
|
|
25
|
+
key: `${threadId}-${streamMessage.order}-${streamMessage.stepOrder}`,
|
|
26
|
+
order: streamMessage.order,
|
|
27
|
+
stepOrder: streamMessage.stepOrder,
|
|
28
|
+
status: statusFromStreamStatus(streamMessage.status),
|
|
29
|
+
agentName: streamMessage.agentName,
|
|
30
|
+
text: "",
|
|
31
|
+
_creationTime: Date.now(),
|
|
32
|
+
role: "assistant",
|
|
33
|
+
parts: [],
|
|
34
|
+
...(streamMessage.metadata ? { metadata: streamMessage.metadata } : {}),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function statusFromStreamStatus(
|
|
39
|
+
status: StreamMessage["status"],
|
|
40
|
+
): MessageStatus | "streaming" {
|
|
41
|
+
switch (status) {
|
|
42
|
+
case "streaming":
|
|
43
|
+
return "streaming";
|
|
44
|
+
case "finished":
|
|
45
|
+
return "success";
|
|
46
|
+
case "aborted":
|
|
47
|
+
return "failed";
|
|
48
|
+
default:
|
|
49
|
+
return "pending";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
type ToolPart = ToolUIPart | DynamicToolUIPart;
|
|
54
|
+
|
|
55
|
+
function transitionToolPart<S extends ToolPart["state"]>(
|
|
56
|
+
part: ToolPart,
|
|
57
|
+
updates: { state: S } & Partial<Extract<ToolPart, { state: S }>>,
|
|
58
|
+
): void {
|
|
59
|
+
Object.assign(part, updates);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type IncrementalStreamState = {
|
|
63
|
+
// chunk id -> index of the streaming text part in message.parts
|
|
64
|
+
activeText: Record<string, number>;
|
|
65
|
+
// chunk id -> index of the streaming reasoning part in message.parts
|
|
66
|
+
activeReasoning: Record<string, number>;
|
|
67
|
+
// toolCallId -> raw accumulated input JSON text (kept separate from the
|
|
68
|
+
// parsed `input` so partial JSON can be repair-parsed each batch)
|
|
69
|
+
toolInputText: Record<string, string>;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export function emptyIncrementalStreamState(): IncrementalStreamState {
|
|
73
|
+
return { activeText: {}, activeReasoning: {}, toolInputText: {} };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Apply a batch of new UIMessageChunks to an existing UIMessage without
|
|
78
|
+
* replaying prior chunks. `prev` carries the ephemeral stream state that the
|
|
79
|
+
* UIMessage itself can't hold (which text/reasoning parts are still streaming,
|
|
80
|
+
* and the raw accumulated tool input text). Parts are append-only, so part
|
|
81
|
+
* indices stay stable across the structuredClone between batches. Behavior
|
|
82
|
+
* mirrors the AI SDK's processUIMessageStream.
|
|
83
|
+
*/
|
|
84
|
+
export function applyUIMessageChunksIncremental<
|
|
85
|
+
METADATA = unknown,
|
|
86
|
+
DATA_PARTS extends import("ai").UIDataTypes = import("ai").UIDataTypes,
|
|
87
|
+
TOOLS extends import("ai").UITools = import("ai").UITools,
|
|
88
|
+
>(
|
|
89
|
+
uiMessage: UIMessage<METADATA, DATA_PARTS, TOOLS>,
|
|
90
|
+
newParts: UIMessageChunk[],
|
|
91
|
+
prev: IncrementalStreamState,
|
|
92
|
+
): {
|
|
93
|
+
message: UIMessage<METADATA, DATA_PARTS, TOOLS>;
|
|
94
|
+
streamState: IncrementalStreamState;
|
|
95
|
+
} {
|
|
96
|
+
const message: UIMessage = structuredClone(uiMessage);
|
|
97
|
+
const activeText: Record<string, number> = { ...prev.activeText };
|
|
98
|
+
const activeReasoning: Record<string, number> = { ...prev.activeReasoning };
|
|
99
|
+
const toolInputText: Record<string, string> = { ...prev.toolInputText };
|
|
100
|
+
const touchedTools = new Set<string>();
|
|
101
|
+
|
|
102
|
+
const toolIndexById = new Map<string, number>();
|
|
103
|
+
message.parts.forEach((p, i) => {
|
|
104
|
+
if (
|
|
105
|
+
"toolCallId" in p &&
|
|
106
|
+
(p.type.startsWith("tool-") || p.type === "dynamic-tool")
|
|
107
|
+
) {
|
|
108
|
+
toolIndexById.set((p as ToolPart).toolCallId, i);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
const toolPartAt = (toolCallId: string): ToolPart | undefined => {
|
|
112
|
+
const idx = toolIndexById.get(toolCallId);
|
|
113
|
+
return idx === undefined ? undefined : (message.parts[idx] as ToolPart);
|
|
114
|
+
};
|
|
115
|
+
const mergeMetadata = (metadata: unknown) => {
|
|
116
|
+
if (metadata == null) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
message.metadata = {
|
|
120
|
+
...(message.metadata as Record<string, unknown> | undefined),
|
|
121
|
+
...(metadata as Record<string, unknown>),
|
|
122
|
+
} as typeof message.metadata;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
for (const part of newParts) {
|
|
126
|
+
switch (part.type) {
|
|
127
|
+
case "text-start": {
|
|
128
|
+
const newPart: TextUIPart = {
|
|
129
|
+
type: "text",
|
|
130
|
+
text: "",
|
|
131
|
+
state: "streaming",
|
|
132
|
+
...(part.providerMetadata
|
|
133
|
+
? { providerMetadata: part.providerMetadata }
|
|
134
|
+
: {}),
|
|
135
|
+
};
|
|
136
|
+
message.parts.push(newPart);
|
|
137
|
+
activeText[part.id] = message.parts.length - 1;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
case "text-delta": {
|
|
141
|
+
const idx = activeText[part.id];
|
|
142
|
+
if (idx !== undefined) {
|
|
143
|
+
const textPart = message.parts[idx] as TextUIPart;
|
|
144
|
+
textPart.text += part.delta;
|
|
145
|
+
const providerMetadata = mergeProviderMetadata(
|
|
146
|
+
textPart.providerMetadata,
|
|
147
|
+
part.providerMetadata,
|
|
148
|
+
);
|
|
149
|
+
if (providerMetadata) textPart.providerMetadata = providerMetadata;
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
case "text-end": {
|
|
154
|
+
const idx = activeText[part.id];
|
|
155
|
+
if (idx !== undefined) {
|
|
156
|
+
const textPart = message.parts[idx] as TextUIPart;
|
|
157
|
+
textPart.state = "done";
|
|
158
|
+
const providerMetadata = mergeProviderMetadata(
|
|
159
|
+
textPart.providerMetadata,
|
|
160
|
+
part.providerMetadata,
|
|
161
|
+
);
|
|
162
|
+
if (providerMetadata) textPart.providerMetadata = providerMetadata;
|
|
163
|
+
delete activeText[part.id];
|
|
164
|
+
}
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
case "reasoning-start": {
|
|
168
|
+
const newPart: ReasoningUIPart = {
|
|
169
|
+
type: "reasoning",
|
|
170
|
+
text: "",
|
|
171
|
+
state: "streaming",
|
|
172
|
+
...(part.providerMetadata
|
|
173
|
+
? { providerMetadata: part.providerMetadata }
|
|
174
|
+
: {}),
|
|
175
|
+
};
|
|
176
|
+
message.parts.push(newPart);
|
|
177
|
+
activeReasoning[part.id] = message.parts.length - 1;
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
case "reasoning-delta": {
|
|
181
|
+
const idx = activeReasoning[part.id];
|
|
182
|
+
if (idx !== undefined) {
|
|
183
|
+
const reasoningPart = message.parts[idx] as ReasoningUIPart;
|
|
184
|
+
reasoningPart.text += part.delta;
|
|
185
|
+
const providerMetadata = mergeProviderMetadata(
|
|
186
|
+
reasoningPart.providerMetadata,
|
|
187
|
+
part.providerMetadata,
|
|
188
|
+
);
|
|
189
|
+
if (providerMetadata)
|
|
190
|
+
reasoningPart.providerMetadata = providerMetadata;
|
|
191
|
+
}
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
case "reasoning-end": {
|
|
195
|
+
const idx = activeReasoning[part.id];
|
|
196
|
+
if (idx !== undefined) {
|
|
197
|
+
const reasoningPart = message.parts[idx] as ReasoningUIPart;
|
|
198
|
+
reasoningPart.state = "done";
|
|
199
|
+
const providerMetadata = mergeProviderMetadata(
|
|
200
|
+
reasoningPart.providerMetadata,
|
|
201
|
+
part.providerMetadata,
|
|
202
|
+
);
|
|
203
|
+
if (providerMetadata)
|
|
204
|
+
reasoningPart.providerMetadata = providerMetadata;
|
|
205
|
+
delete activeReasoning[part.id];
|
|
206
|
+
}
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
case "reasoning-file":
|
|
210
|
+
message.parts.push({
|
|
211
|
+
type: "reasoning-file",
|
|
212
|
+
url: part.url,
|
|
213
|
+
mediaType: part.mediaType,
|
|
214
|
+
...(part.providerMetadata
|
|
215
|
+
? { providerMetadata: part.providerMetadata }
|
|
216
|
+
: {}),
|
|
217
|
+
} satisfies ReasoningFileUIPart);
|
|
218
|
+
break;
|
|
219
|
+
case "custom":
|
|
220
|
+
message.parts.push({
|
|
221
|
+
type: "custom",
|
|
222
|
+
kind: part.kind,
|
|
223
|
+
...(part.providerMetadata
|
|
224
|
+
? { providerMetadata: part.providerMetadata }
|
|
225
|
+
: {}),
|
|
226
|
+
} satisfies CustomContentUIPart);
|
|
227
|
+
break;
|
|
228
|
+
case "tool-input-start": {
|
|
229
|
+
const newToolPart: ToolUIPart | DynamicToolUIPart = part.dynamic
|
|
230
|
+
? ({
|
|
231
|
+
type: "dynamic-tool",
|
|
232
|
+
toolCallId: part.toolCallId,
|
|
233
|
+
toolName: part.toolName,
|
|
234
|
+
state: "input-streaming",
|
|
235
|
+
input: undefined,
|
|
236
|
+
...(part.providerExecuted === undefined
|
|
237
|
+
? {}
|
|
238
|
+
: { providerExecuted: part.providerExecuted }),
|
|
239
|
+
...(part.title === undefined ? {} : { title: part.title }),
|
|
240
|
+
...(part.toolMetadata === undefined
|
|
241
|
+
? {}
|
|
242
|
+
: { toolMetadata: part.toolMetadata }),
|
|
243
|
+
...(part.providerMetadata
|
|
244
|
+
? { callProviderMetadata: part.providerMetadata }
|
|
245
|
+
: {}),
|
|
246
|
+
} satisfies DynamicToolUIPart)
|
|
247
|
+
: ({
|
|
248
|
+
type: `tool-${part.toolName}`,
|
|
249
|
+
toolCallId: part.toolCallId,
|
|
250
|
+
state: "input-streaming",
|
|
251
|
+
input: undefined,
|
|
252
|
+
...(part.providerExecuted === undefined
|
|
253
|
+
? {}
|
|
254
|
+
: { providerExecuted: part.providerExecuted }),
|
|
255
|
+
...(part.title === undefined ? {} : { title: part.title }),
|
|
256
|
+
...(part.toolMetadata === undefined
|
|
257
|
+
? {}
|
|
258
|
+
: { toolMetadata: part.toolMetadata }),
|
|
259
|
+
...(part.providerMetadata
|
|
260
|
+
? { callProviderMetadata: part.providerMetadata }
|
|
261
|
+
: {}),
|
|
262
|
+
} satisfies ToolUIPart);
|
|
263
|
+
message.parts.push(newToolPart);
|
|
264
|
+
toolIndexById.set(part.toolCallId, message.parts.length - 1);
|
|
265
|
+
toolInputText[part.toolCallId] = "";
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
case "tool-input-delta": {
|
|
269
|
+
if (toolIndexById.has(part.toolCallId)) {
|
|
270
|
+
toolInputText[part.toolCallId] =
|
|
271
|
+
(toolInputText[part.toolCallId] ?? "") + part.inputTextDelta;
|
|
272
|
+
touchedTools.add(part.toolCallId);
|
|
273
|
+
} else {
|
|
274
|
+
console.warn(
|
|
275
|
+
`tool-input-delta for unknown toolCallId ${part.toolCallId}`,
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
case "tool-input-available": {
|
|
281
|
+
let toolPart = toolPartAt(part.toolCallId);
|
|
282
|
+
if (!toolPart) {
|
|
283
|
+
toolPart = part.dynamic
|
|
284
|
+
? ({
|
|
285
|
+
type: "dynamic-tool",
|
|
286
|
+
toolCallId: part.toolCallId,
|
|
287
|
+
toolName: part.toolName,
|
|
288
|
+
state: "input-available",
|
|
289
|
+
input: part.input,
|
|
290
|
+
} satisfies DynamicToolUIPart)
|
|
291
|
+
: ({
|
|
292
|
+
type: `tool-${part.toolName}`,
|
|
293
|
+
toolCallId: part.toolCallId,
|
|
294
|
+
state: "input-available",
|
|
295
|
+
input: part.input,
|
|
296
|
+
} satisfies ToolUIPart);
|
|
297
|
+
message.parts.push(toolPart);
|
|
298
|
+
toolIndexById.set(part.toolCallId, message.parts.length - 1);
|
|
299
|
+
}
|
|
300
|
+
const callProviderMetadata = mergeProviderMetadata(
|
|
301
|
+
(toolPart as { callProviderMetadata?: ProviderMetadata })
|
|
302
|
+
.callProviderMetadata,
|
|
303
|
+
part.providerMetadata,
|
|
304
|
+
);
|
|
305
|
+
transitionToolPart(toolPart, {
|
|
306
|
+
state: "input-available",
|
|
307
|
+
input: part.input,
|
|
308
|
+
...(part.providerExecuted === undefined
|
|
309
|
+
? {}
|
|
310
|
+
: { providerExecuted: part.providerExecuted }),
|
|
311
|
+
...(part.title === undefined ? {} : { title: part.title }),
|
|
312
|
+
...(part.toolMetadata === undefined
|
|
313
|
+
? {}
|
|
314
|
+
: { toolMetadata: part.toolMetadata }),
|
|
315
|
+
...(callProviderMetadata ? { callProviderMetadata } : {}),
|
|
316
|
+
});
|
|
317
|
+
touchedTools.delete(part.toolCallId);
|
|
318
|
+
// The raw JSON buffer is no longer needed; drop it so it doesn't get
|
|
319
|
+
// carried through every later batch on the hot path.
|
|
320
|
+
delete toolInputText[part.toolCallId];
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
case "tool-input-error": {
|
|
324
|
+
let toolPart = toolPartAt(part.toolCallId);
|
|
325
|
+
if (!toolPart) {
|
|
326
|
+
toolPart = part.dynamic
|
|
327
|
+
? ({
|
|
328
|
+
type: "dynamic-tool",
|
|
329
|
+
toolCallId: part.toolCallId,
|
|
330
|
+
toolName: part.toolName,
|
|
331
|
+
state: "output-error",
|
|
332
|
+
input: part.input,
|
|
333
|
+
errorText: part.errorText,
|
|
334
|
+
} satisfies DynamicToolUIPart)
|
|
335
|
+
: ({
|
|
336
|
+
type: `tool-${part.toolName}`,
|
|
337
|
+
toolCallId: part.toolCallId,
|
|
338
|
+
state: "output-error",
|
|
339
|
+
input: undefined,
|
|
340
|
+
rawInput: part.input,
|
|
341
|
+
errorText: part.errorText,
|
|
342
|
+
} satisfies ToolUIPart);
|
|
343
|
+
message.parts.push(toolPart);
|
|
344
|
+
toolIndexById.set(part.toolCallId, message.parts.length - 1);
|
|
345
|
+
}
|
|
346
|
+
const resultProviderMetadata = mergeProviderMetadata(
|
|
347
|
+
(toolPart as { resultProviderMetadata?: ProviderMetadata })
|
|
348
|
+
.resultProviderMetadata,
|
|
349
|
+
part.providerMetadata,
|
|
350
|
+
);
|
|
351
|
+
transitionToolPart(toolPart, {
|
|
352
|
+
state: "output-error",
|
|
353
|
+
errorText: part.errorText,
|
|
354
|
+
...(part.providerExecuted === undefined
|
|
355
|
+
? {}
|
|
356
|
+
: { providerExecuted: part.providerExecuted }),
|
|
357
|
+
...(toolPart.type === "dynamic-tool"
|
|
358
|
+
? {
|
|
359
|
+
input: part.input,
|
|
360
|
+
}
|
|
361
|
+
: {
|
|
362
|
+
input: undefined,
|
|
363
|
+
rawInput: part.input,
|
|
364
|
+
}),
|
|
365
|
+
...(part.title === undefined ? {} : { title: part.title }),
|
|
366
|
+
...(part.toolMetadata === undefined
|
|
367
|
+
? {}
|
|
368
|
+
: { toolMetadata: part.toolMetadata }),
|
|
369
|
+
...(resultProviderMetadata ? { resultProviderMetadata } : {}),
|
|
370
|
+
});
|
|
371
|
+
touchedTools.delete(part.toolCallId);
|
|
372
|
+
delete toolInputText[part.toolCallId];
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
case "tool-output-available": {
|
|
376
|
+
const toolPart = toolPartAt(part.toolCallId);
|
|
377
|
+
if (toolPart) {
|
|
378
|
+
transitionToolPart(toolPart, {
|
|
379
|
+
state: "output-available",
|
|
380
|
+
output: part.output,
|
|
381
|
+
preliminary: part.preliminary,
|
|
382
|
+
...(part.providerExecuted === undefined
|
|
383
|
+
? {}
|
|
384
|
+
: { providerExecuted: part.providerExecuted }),
|
|
385
|
+
...(part.providerMetadata
|
|
386
|
+
? { resultProviderMetadata: part.providerMetadata }
|
|
387
|
+
: {}),
|
|
388
|
+
...(part.toolMetadata === undefined
|
|
389
|
+
? {}
|
|
390
|
+
: { toolMetadata: part.toolMetadata }),
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
case "tool-output-error": {
|
|
396
|
+
const toolPart = toolPartAt(part.toolCallId);
|
|
397
|
+
if (toolPart) {
|
|
398
|
+
transitionToolPart(toolPart, {
|
|
399
|
+
state: "output-error",
|
|
400
|
+
errorText: part.errorText,
|
|
401
|
+
output: undefined,
|
|
402
|
+
...(part.providerExecuted === undefined
|
|
403
|
+
? {}
|
|
404
|
+
: { providerExecuted: part.providerExecuted }),
|
|
405
|
+
...(part.providerMetadata
|
|
406
|
+
? { resultProviderMetadata: part.providerMetadata }
|
|
407
|
+
: {}),
|
|
408
|
+
...(part.toolMetadata === undefined
|
|
409
|
+
? {}
|
|
410
|
+
: { toolMetadata: part.toolMetadata }),
|
|
411
|
+
});
|
|
412
|
+
// The SDK clears a prior preliminary result when the final outcome
|
|
413
|
+
// is an error. `preliminary` is not part of the error-state type.
|
|
414
|
+
Object.assign(toolPart, { preliminary: undefined });
|
|
415
|
+
}
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
case "tool-output-denied": {
|
|
419
|
+
const toolPart = toolPartAt(part.toolCallId);
|
|
420
|
+
if (toolPart) {
|
|
421
|
+
transitionToolPart(toolPart, { state: "output-denied" });
|
|
422
|
+
}
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
case "tool-approval-request": {
|
|
426
|
+
const toolPart = toolPartAt(part.toolCallId);
|
|
427
|
+
if (toolPart) {
|
|
428
|
+
transitionToolPart(toolPart, {
|
|
429
|
+
state: "approval-requested",
|
|
430
|
+
approval: {
|
|
431
|
+
id: part.approvalId,
|
|
432
|
+
...(part.isAutomatic === undefined
|
|
433
|
+
? {}
|
|
434
|
+
: { isAutomatic: part.isAutomatic }),
|
|
435
|
+
...(part.signature === undefined
|
|
436
|
+
? {}
|
|
437
|
+
: { signature: part.signature }),
|
|
438
|
+
},
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
case "tool-approval-response": {
|
|
444
|
+
const toolPart = message.parts.find(
|
|
445
|
+
(candidate) =>
|
|
446
|
+
"approval" in candidate &&
|
|
447
|
+
(candidate as ToolPart).approval?.id === part.approvalId,
|
|
448
|
+
) as ToolPart | undefined;
|
|
449
|
+
if (toolPart) {
|
|
450
|
+
const callProviderMetadata = mergeProviderMetadata(
|
|
451
|
+
(toolPart as { callProviderMetadata?: ProviderMetadata })
|
|
452
|
+
.callProviderMetadata,
|
|
453
|
+
part.providerMetadata,
|
|
454
|
+
);
|
|
455
|
+
transitionToolPart(toolPart, {
|
|
456
|
+
state: "approval-responded",
|
|
457
|
+
approval: {
|
|
458
|
+
...toolPart.approval,
|
|
459
|
+
id: part.approvalId,
|
|
460
|
+
approved: part.approved,
|
|
461
|
+
...(part.reason === undefined ? {} : { reason: part.reason }),
|
|
462
|
+
},
|
|
463
|
+
...(part.providerExecuted === undefined
|
|
464
|
+
? {}
|
|
465
|
+
: { providerExecuted: part.providerExecuted }),
|
|
466
|
+
...(callProviderMetadata ? { callProviderMetadata } : {}),
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
case "source-url":
|
|
472
|
+
message.parts.push({
|
|
473
|
+
type: "source-url",
|
|
474
|
+
url: part.url,
|
|
475
|
+
sourceId: part.sourceId,
|
|
476
|
+
title: part.title,
|
|
477
|
+
providerMetadata: part.providerMetadata,
|
|
478
|
+
});
|
|
479
|
+
break;
|
|
480
|
+
case "source-document":
|
|
481
|
+
message.parts.push({
|
|
482
|
+
type: "source-document",
|
|
483
|
+
mediaType: part.mediaType,
|
|
484
|
+
sourceId: part.sourceId,
|
|
485
|
+
title: part.title,
|
|
486
|
+
filename: part.filename,
|
|
487
|
+
providerMetadata: part.providerMetadata,
|
|
488
|
+
});
|
|
489
|
+
break;
|
|
490
|
+
case "file":
|
|
491
|
+
message.parts.push({
|
|
492
|
+
type: "file",
|
|
493
|
+
mediaType: part.mediaType,
|
|
494
|
+
url: part.url,
|
|
495
|
+
providerMetadata: part.providerMetadata,
|
|
496
|
+
});
|
|
497
|
+
break;
|
|
498
|
+
case "start-step":
|
|
499
|
+
message.parts.push({ type: "step-start" });
|
|
500
|
+
break;
|
|
501
|
+
case "finish-step":
|
|
502
|
+
// Match the SDK: a new step starts fresh streaming parts; the prior
|
|
503
|
+
// parts keep their state rather than being forced to "done".
|
|
504
|
+
for (const id of Object.keys(activeText)) delete activeText[id];
|
|
505
|
+
for (const id of Object.keys(activeReasoning))
|
|
506
|
+
delete activeReasoning[id];
|
|
507
|
+
break;
|
|
508
|
+
case "start":
|
|
509
|
+
case "finish":
|
|
510
|
+
case "message-metadata":
|
|
511
|
+
mergeMetadata(part.messageMetadata);
|
|
512
|
+
break;
|
|
513
|
+
case "abort":
|
|
514
|
+
case "error":
|
|
515
|
+
// The stream-level status (statusFromStreamStatus) is authoritative and
|
|
516
|
+
// is applied by the caller; nothing to mutate on the message here.
|
|
517
|
+
break;
|
|
518
|
+
default: {
|
|
519
|
+
if (typeof part.type === "string" && part.type.startsWith("data-")) {
|
|
520
|
+
const dataPart = part as Extract<
|
|
521
|
+
UIMessageChunk,
|
|
522
|
+
{ type: `data-${string}` }
|
|
523
|
+
>;
|
|
524
|
+
const existingIdx =
|
|
525
|
+
dataPart.id != null
|
|
526
|
+
? message.parts.findIndex(
|
|
527
|
+
(p) =>
|
|
528
|
+
p.type === dataPart.type &&
|
|
529
|
+
(p as { id?: string }).id === dataPart.id,
|
|
530
|
+
)
|
|
531
|
+
: -1;
|
|
532
|
+
if (dataPart.transient) {
|
|
533
|
+
break;
|
|
534
|
+
}
|
|
535
|
+
if (existingIdx >= 0) {
|
|
536
|
+
(message.parts[existingIdx] as { data?: unknown }).data =
|
|
537
|
+
dataPart.data;
|
|
538
|
+
} else {
|
|
539
|
+
message.parts.push(
|
|
540
|
+
dataPart as unknown as UIMessage["parts"][number],
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
} else {
|
|
544
|
+
console.warn(
|
|
545
|
+
`applyUIMessageChunksIncremental: unhandled chunk type ${String(part.type)}`,
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
break;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
for (const toolCallId of touchedTools) {
|
|
554
|
+
const toolPart = toolPartAt(toolCallId);
|
|
555
|
+
if (toolPart && toolPart.state === "input-streaming") {
|
|
556
|
+
try {
|
|
557
|
+
toolPart.input = JSON.parse(toolInputText[toolCallId] ?? "");
|
|
558
|
+
} catch {
|
|
559
|
+
// partial JSON — leave input unset until complete
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
message.text = joinText(message.parts);
|
|
565
|
+
return {
|
|
566
|
+
message: message as UIMessage<METADATA, DATA_PARTS, TOOLS>,
|
|
567
|
+
streamState: { activeText, activeReasoning, toolInputText },
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
export async function deriveUIMessagesFromDeltas(
|
|
572
|
+
threadId: string,
|
|
573
|
+
streamMessages: StreamMessage[],
|
|
574
|
+
allDeltas: StreamDelta[],
|
|
575
|
+
): Promise<UIMessage[]> {
|
|
576
|
+
const messages: UIMessage[] = [];
|
|
577
|
+
for (const streamMessage of streamMessages) {
|
|
578
|
+
if (streamMessage.format !== "UIMessageChunk") {
|
|
579
|
+
throw new Error(
|
|
580
|
+
`deriveUIMessagesFromDeltas: unsupported stream format "${streamMessage.format ?? "text"}" for stream ${streamMessage.streamId}`,
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
const { parts } = getParts<UIMessageChunk>(
|
|
584
|
+
allDeltas.filter((d) => d.streamId === streamMessage.streamId),
|
|
585
|
+
0,
|
|
586
|
+
);
|
|
587
|
+
const uiMessage = applyUIMessageChunksIncremental(
|
|
588
|
+
blankUIMessage(streamMessage, threadId),
|
|
589
|
+
parts,
|
|
590
|
+
emptyIncrementalStreamState(),
|
|
591
|
+
).message;
|
|
592
|
+
messages.push(uiMessage);
|
|
593
|
+
}
|
|
594
|
+
return sorted(messages);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
export function getParts<T extends StreamDelta["parts"][number]>(
|
|
598
|
+
deltas: StreamDelta[],
|
|
599
|
+
fromCursor?: number,
|
|
600
|
+
): { parts: T[]; cursor: number } {
|
|
601
|
+
const parts: T[] = [];
|
|
602
|
+
let cursor = fromCursor ?? 0;
|
|
603
|
+
for (const delta of deltas.sort((a, b) => a.start - b.start)) {
|
|
604
|
+
if (delta.parts.length === 0) {
|
|
605
|
+
console.debug(`Got delta with no parts: ${JSON.stringify(delta)}`);
|
|
606
|
+
continue;
|
|
607
|
+
}
|
|
608
|
+
if (cursor !== delta.start) {
|
|
609
|
+
if (cursor >= delta.end) {
|
|
610
|
+
continue;
|
|
611
|
+
} else if (cursor < delta.start) {
|
|
612
|
+
console.warn(
|
|
613
|
+
`Got delta for stream ${delta.streamId} that has a gap ${cursor} -> ${delta.start}`,
|
|
614
|
+
);
|
|
615
|
+
break;
|
|
616
|
+
} else {
|
|
617
|
+
throw new Error(
|
|
618
|
+
`Got unexpected delta for stream ${delta.streamId}: delta: ${delta.start} -> ${delta.end} existing cursor: ${cursor}`,
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
parts.push(...delta.parts);
|
|
623
|
+
cursor = delta.end;
|
|
624
|
+
}
|
|
625
|
+
return { parts, cursor };
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function mergeProviderMetadata(
|
|
629
|
+
existing: ProviderMetadata | undefined,
|
|
630
|
+
part: ProviderMetadata | undefined,
|
|
631
|
+
): ProviderMetadata | undefined {
|
|
632
|
+
return part ?? existing;
|
|
633
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import type { MessageDoc } from "
|
|
2
|
+
import type { MessageDoc } from "../validators.js";
|
|
3
3
|
import type { UIMessage } from "./UIMessages.js";
|
|
4
4
|
import { fromUIMessages, toUIMessages } from "./UIMessages.js";
|
|
5
5
|
|
|
@@ -310,6 +310,7 @@ describe("fromUIMessages round-trip tests", () => {
|
|
|
310
310
|
id: "source2",
|
|
311
311
|
mediaType: "application/pdf",
|
|
312
312
|
title: "Document Source",
|
|
313
|
+
filename: "document.pdf",
|
|
313
314
|
},
|
|
314
315
|
],
|
|
315
316
|
}),
|
|
@@ -327,6 +328,13 @@ describe("fromUIMessages round-trip tests", () => {
|
|
|
327
328
|
(part) => part.type === "source-url" || part.type === "source-document",
|
|
328
329
|
);
|
|
329
330
|
expect(sourceParts).toHaveLength(2);
|
|
331
|
+
expect(sourceParts).toContainEqual({
|
|
332
|
+
type: "source-document",
|
|
333
|
+
sourceId: "source2",
|
|
334
|
+
mediaType: "application/pdf",
|
|
335
|
+
title: "Document Source",
|
|
336
|
+
filename: "document.pdf",
|
|
337
|
+
});
|
|
330
338
|
|
|
331
339
|
expect(backToMessageDocs).toHaveLength(1);
|
|
332
340
|
expect(backToMessageDocs[0].sources).toHaveLength(2);
|
|
@@ -337,6 +345,14 @@ describe("fromUIMessages round-trip tests", () => {
|
|
|
337
345
|
url: "https://example.com",
|
|
338
346
|
title: "Example Source",
|
|
339
347
|
});
|
|
348
|
+
expect(backToMessageDocs[0].sources![1]).toMatchObject({
|
|
349
|
+
type: "source",
|
|
350
|
+
sourceType: "document",
|
|
351
|
+
id: "source2",
|
|
352
|
+
mediaType: "application/pdf",
|
|
353
|
+
title: "Document Source",
|
|
354
|
+
filename: "document.pdf",
|
|
355
|
+
});
|
|
340
356
|
});
|
|
341
357
|
|
|
342
358
|
it("preserves metadata when provided", async () => {
|