@convex-dev/agent 0.6.0 → 0.6.1
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/UIMessages.d.ts +46 -0
- package/dist/UIMessages.d.ts.map +1 -0
- package/dist/UIMessages.js +634 -0
- package/dist/UIMessages.js.map +1 -0
- package/dist/client/createTool.d.ts +167 -0
- package/dist/client/createTool.d.ts.map +1 -0
- package/dist/client/createTool.js +116 -0
- package/dist/client/createTool.js.map +1 -0
- package/dist/client/defaultComponent.d.ts +11 -0
- package/dist/client/defaultComponent.d.ts.map +1 -0
- package/dist/client/defaultComponent.js +7 -0
- package/dist/client/defaultComponent.js.map +1 -0
- package/dist/client/definePlaygroundAPI.d.ts +1725 -0
- package/dist/client/definePlaygroundAPI.d.ts.map +1 -0
- package/dist/client/definePlaygroundAPI.js +271 -0
- package/dist/client/definePlaygroundAPI.js.map +1 -0
- package/dist/client/files.d.ts +69 -0
- package/dist/client/files.d.ts.map +1 -0
- package/dist/client/files.js +181 -0
- package/dist/client/files.js.map +1 -0
- package/dist/client/index.d.ts +2091 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +929 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/messages.d.ts +461 -0
- package/dist/client/messages.d.ts.map +1 -0
- package/dist/client/messages.js +106 -0
- package/dist/client/messages.js.map +1 -0
- package/dist/client/mockModel.d.ts +42 -0
- package/dist/client/mockModel.d.ts.map +1 -0
- package/dist/client/mockModel.js +182 -0
- package/dist/client/mockModel.js.map +1 -0
- package/dist/client/saveInputMessages.d.ts +20 -0
- package/dist/client/saveInputMessages.d.ts.map +1 -0
- package/dist/client/saveInputMessages.js +58 -0
- package/dist/client/saveInputMessages.js.map +1 -0
- package/dist/client/search.d.ts +493 -0
- package/dist/client/search.d.ts.map +1 -0
- package/dist/client/search.js +435 -0
- package/dist/client/search.js.map +1 -0
- package/dist/client/start.d.ts +84 -0
- package/dist/client/start.d.ts.map +1 -0
- package/dist/client/start.js +185 -0
- package/dist/client/start.js.map +1 -0
- package/dist/client/streamText.d.ts +46 -0
- package/dist/client/streamText.d.ts.map +1 -0
- package/dist/client/streamText.js +127 -0
- package/dist/client/streamText.js.map +1 -0
- package/dist/client/streaming.d.ts +3778 -0
- package/dist/client/streaming.d.ts.map +1 -0
- package/dist/client/streaming.js +330 -0
- package/dist/client/streaming.js.map +1 -0
- package/dist/client/threads.d.ts +46 -0
- package/dist/client/threads.d.ts.map +1 -0
- package/dist/client/threads.js +49 -0
- package/dist/client/threads.js.map +1 -0
- package/dist/client/types.d.ts +461 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +2 -0
- package/dist/client/types.js.map +1 -0
- package/dist/client/utils.d.ts +4 -0
- package/dist/client/utils.d.ts.map +1 -0
- package/dist/client/utils.js +21 -0
- package/dist/client/utils.js.map +1 -0
- package/dist/component/_generated/api.d.ts +48 -0
- package/dist/component/_generated/api.d.ts.map +1 -0
- package/dist/component/_generated/api.js +31 -0
- package/dist/component/_generated/api.js.map +1 -0
- package/dist/component/_generated/component.d.ts +3120 -0
- package/dist/component/_generated/component.d.ts.map +1 -0
- package/dist/component/_generated/component.js +11 -0
- package/dist/component/_generated/component.js.map +1 -0
- package/dist/component/_generated/dataModel.d.ts +46 -0
- package/dist/component/_generated/dataModel.d.ts.map +1 -0
- package/dist/component/_generated/dataModel.js +11 -0
- package/dist/component/_generated/dataModel.js.map +1 -0
- package/dist/component/_generated/server.d.ts +121 -0
- package/dist/component/_generated/server.d.ts.map +1 -0
- package/dist/component/_generated/server.js +78 -0
- package/dist/component/_generated/server.js.map +1 -0
- package/dist/component/apiKeys.d.ts +11 -0
- package/dist/component/apiKeys.d.ts.map +1 -0
- package/dist/component/apiKeys.js +69 -0
- package/dist/component/apiKeys.js.map +1 -0
- package/dist/component/convex.config.d.ts +3 -0
- package/dist/component/convex.config.d.ts.map +1 -0
- package/dist/component/convex.config.js +3 -0
- package/dist/component/convex.config.js.map +1 -0
- package/dist/component/files.d.ts +97 -0
- package/dist/component/files.d.ts.map +1 -0
- package/dist/component/files.js +190 -0
- package/dist/component/files.js.map +1 -0
- package/dist/component/messages.d.ts +3851 -0
- package/dist/component/messages.d.ts.map +1 -0
- package/dist/component/messages.js +757 -0
- package/dist/component/messages.js.map +1 -0
- package/dist/component/schema.d.ts +8029 -0
- package/dist/component/schema.d.ts.map +1 -0
- package/dist/component/schema.js +147 -0
- package/dist/component/schema.js.map +1 -0
- package/dist/component/streams.d.ts +128 -0
- package/dist/component/streams.d.ts.map +1 -0
- package/dist/component/streams.js +413 -0
- package/dist/component/streams.js.map +1 -0
- package/dist/component/threads.d.ts +115 -0
- package/dist/component/threads.d.ts.map +1 -0
- package/dist/component/threads.js +208 -0
- package/dist/component/threads.js.map +1 -0
- package/dist/component/users.d.ts +52 -0
- package/dist/component/users.d.ts.map +1 -0
- package/dist/component/users.js +229 -0
- package/dist/component/users.js.map +1 -0
- package/dist/component/vector/index.d.ts +61 -0
- package/dist/component/vector/index.d.ts.map +1 -0
- package/dist/component/vector/index.js +146 -0
- package/dist/component/vector/index.js.map +1 -0
- package/dist/component/vector/tables.d.ts +58 -0
- package/dist/component/vector/tables.d.ts.map +1 -0
- package/dist/component/vector/tables.js +56 -0
- package/dist/component/vector/tables.js.map +1 -0
- package/dist/deltas.d.ts +43 -0
- package/dist/deltas.d.ts.map +1 -0
- package/dist/deltas.js +446 -0
- package/dist/deltas.js.map +1 -0
- package/dist/mapping.d.ts +76 -0
- package/dist/mapping.d.ts.map +1 -0
- package/dist/mapping.js +720 -0
- package/dist/mapping.js.map +1 -0
- package/dist/react/SmoothText.d.ts +5 -0
- package/dist/react/SmoothText.d.ts.map +1 -0
- package/dist/react/SmoothText.js +6 -0
- package/dist/react/SmoothText.js.map +1 -0
- package/dist/react/index.d.ts +25 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +70 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/optimisticallySendMessage.d.ts +42 -0
- package/dist/react/optimisticallySendMessage.d.ts.map +1 -0
- package/dist/react/optimisticallySendMessage.js +74 -0
- package/dist/react/optimisticallySendMessage.js.map +1 -0
- package/dist/react/types.d.ts +12 -0
- package/dist/react/types.d.ts.map +1 -0
- package/dist/react/types.js +2 -0
- package/dist/react/types.js.map +1 -0
- package/dist/react/useDeltaStreams.d.ts +10 -0
- package/dist/react/useDeltaStreams.d.ts.map +1 -0
- package/dist/react/useDeltaStreams.js +106 -0
- package/dist/react/useDeltaStreams.js.map +1 -0
- package/dist/react/useSmoothText.d.ts +27 -0
- package/dist/react/useSmoothText.d.ts.map +1 -0
- package/dist/react/useSmoothText.js +68 -0
- package/dist/react/useSmoothText.js.map +1 -0
- package/dist/react/useStreamingUIMessages.d.ts +22 -0
- package/dist/react/useStreamingUIMessages.d.ts.map +1 -0
- package/dist/react/useStreamingUIMessages.js +92 -0
- package/dist/react/useStreamingUIMessages.js.map +1 -0
- package/dist/react/useThreadMessages.d.ts +104 -0
- package/dist/react/useThreadMessages.d.ts.map +1 -0
- package/dist/react/useThreadMessages.js +148 -0
- package/dist/react/useThreadMessages.js.map +1 -0
- package/dist/react/useUIMessages.d.ts +96 -0
- package/dist/react/useUIMessages.d.ts.map +1 -0
- package/dist/react/useUIMessages.js +108 -0
- package/dist/react/useUIMessages.js.map +1 -0
- package/dist/shared.d.ts +26 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +67 -0
- package/dist/shared.js.map +1 -0
- package/dist/validators.d.ts +24516 -0
- package/dist/validators.d.ts.map +1 -0
- package/dist/validators.js +475 -0
- package/dist/validators.js.map +1 -0
- package/package.json +39 -39
- package/src/UIMessages.ts +0 -9
- package/src/client/approval.test.ts +144 -0
- package/src/client/index.ts +17 -9
- package/src/client/search.ts +0 -29
- package/src/client/start.ts +2 -2
- package/src/client/streamText.ts +9 -0
- package/src/client/streaming.ts +30 -23
- package/src/component/_generated/dataModel.ts +1 -1
- package/src/component/_generated/server.ts +0 -5
- package/src/mapping.ts +38 -22
- package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
package/dist/mapping.js
ADDED
|
@@ -0,0 +1,720 @@
|
|
|
1
|
+
import {} from "ai";
|
|
2
|
+
import { vMessageWithMetadata, vToolResultOutput, vToolApprovalRequest, vToolApprovalResponse, } from "./validators.js";
|
|
3
|
+
import { MAX_FILE_SIZE, storeFile } from "./client/files.js";
|
|
4
|
+
import { convertUint8ArrayToBase64, } from "@ai-sdk/provider-utils";
|
|
5
|
+
import { parse, validate } from "convex-helpers/validators";
|
|
6
|
+
import { getModelName, getProviderName, } from "./shared.js";
|
|
7
|
+
export async function serializeMessage(ctx, component, message) {
|
|
8
|
+
const { content, fileIds } = await serializeContent(ctx, component, message.content);
|
|
9
|
+
return {
|
|
10
|
+
message: {
|
|
11
|
+
role: message.role,
|
|
12
|
+
content,
|
|
13
|
+
...(message.providerOptions
|
|
14
|
+
? { providerOptions: message.providerOptions }
|
|
15
|
+
: {}),
|
|
16
|
+
},
|
|
17
|
+
fileIds,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
// Similar to serializeMessage, but doesn't save any files and is looser
|
|
21
|
+
// For use on the frontend / in synchronous environments.
|
|
22
|
+
export function fromModelMessage(message) {
|
|
23
|
+
const content = fromModelMessageContent(message.content);
|
|
24
|
+
return {
|
|
25
|
+
role: message.role,
|
|
26
|
+
content,
|
|
27
|
+
...(message.providerOptions
|
|
28
|
+
? { providerOptions: message.providerOptions }
|
|
29
|
+
: {}),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export async function serializeOrThrow(message) {
|
|
33
|
+
const { content } = await serializeContent({}, {}, message.content);
|
|
34
|
+
return {
|
|
35
|
+
role: message.role,
|
|
36
|
+
content,
|
|
37
|
+
...(message.providerOptions
|
|
38
|
+
? { providerOptions: message.providerOptions }
|
|
39
|
+
: {}),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export function toModelMessage(message) {
|
|
43
|
+
return {
|
|
44
|
+
...message,
|
|
45
|
+
content: toModelMessageContent(message.content),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export function docsToModelMessages(messages) {
|
|
49
|
+
return messages
|
|
50
|
+
.map((m) => m.message)
|
|
51
|
+
.filter((m) => !!m)
|
|
52
|
+
.filter((m) => !!m.content.length)
|
|
53
|
+
.map(toModelMessage);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Scan messages for unresolved `tool-approval-request` parts and inject
|
|
57
|
+
* synthetic `tool-approval-response` denials so that the AI SDK receives
|
|
58
|
+
* a complete history (every tool-call has a corresponding result or denial).
|
|
59
|
+
*
|
|
60
|
+
* This handles the case where a user sends a new message instead of
|
|
61
|
+
* resolving pending approvals — the old approvals are auto-denied rather
|
|
62
|
+
* than silently dropped.
|
|
63
|
+
*/
|
|
64
|
+
export function autoDenyUnresolvedApprovals(messages) {
|
|
65
|
+
// Collect all approval requests: approvalId → { toolCallId, messageIndex }
|
|
66
|
+
const requests = new Map();
|
|
67
|
+
// Collect all resolved approval IDs
|
|
68
|
+
const resolvedIds = new Set();
|
|
69
|
+
for (let i = 0; i < messages.length; i++) {
|
|
70
|
+
const msg = messages[i];
|
|
71
|
+
if (!Array.isArray(msg.content))
|
|
72
|
+
continue;
|
|
73
|
+
for (const part of msg.content) {
|
|
74
|
+
if (part.type === "tool-approval-request") {
|
|
75
|
+
requests.set(part.approvalId, {
|
|
76
|
+
toolCallId: part.toolCallId,
|
|
77
|
+
messageIndex: i,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
else if (part.type === "tool-approval-response") {
|
|
81
|
+
resolvedIds.add(part.approvalId);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Find unresolved approvals
|
|
86
|
+
const unresolved = [];
|
|
87
|
+
for (const [approvalId, info] of requests) {
|
|
88
|
+
if (!resolvedIds.has(approvalId)) {
|
|
89
|
+
unresolved.push({ approvalId, ...info });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (unresolved.length === 0) {
|
|
93
|
+
return messages;
|
|
94
|
+
}
|
|
95
|
+
// Group unresolved approvals by the assistant message index they came from
|
|
96
|
+
const byMessageIndex = new Map();
|
|
97
|
+
for (const entry of unresolved) {
|
|
98
|
+
console.warn(`Auto-denying unresolved tool approval ${entry.approvalId} ` +
|
|
99
|
+
`(toolCallId: ${entry.toolCallId}): new generation started`);
|
|
100
|
+
let group = byMessageIndex.get(entry.messageIndex);
|
|
101
|
+
if (!group) {
|
|
102
|
+
group = [];
|
|
103
|
+
byMessageIndex.set(entry.messageIndex, group);
|
|
104
|
+
}
|
|
105
|
+
group.push(entry);
|
|
106
|
+
}
|
|
107
|
+
// Build result by inserting synthetic denial messages after each relevant
|
|
108
|
+
// assistant message
|
|
109
|
+
const result = [];
|
|
110
|
+
for (let i = 0; i < messages.length; i++) {
|
|
111
|
+
result.push(messages[i]);
|
|
112
|
+
const group = byMessageIndex.get(i);
|
|
113
|
+
if (group) {
|
|
114
|
+
result.push({
|
|
115
|
+
role: "tool",
|
|
116
|
+
content: group.map((entry) => ({
|
|
117
|
+
type: "tool-approval-response",
|
|
118
|
+
approvalId: entry.approvalId,
|
|
119
|
+
approved: false,
|
|
120
|
+
reason: "auto-denied: new generation started",
|
|
121
|
+
})),
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
export function serializeUsage(usage) {
|
|
128
|
+
return {
|
|
129
|
+
promptTokens: usage.inputTokens ?? 0,
|
|
130
|
+
completionTokens: usage.outputTokens ?? 0,
|
|
131
|
+
totalTokens: usage.totalTokens ?? 0,
|
|
132
|
+
reasoningTokens: usage.reasoningTokens,
|
|
133
|
+
cachedInputTokens: usage.cachedInputTokens,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
export function toModelMessageUsage(usage) {
|
|
137
|
+
return {
|
|
138
|
+
inputTokens: usage.promptTokens,
|
|
139
|
+
outputTokens: usage.completionTokens,
|
|
140
|
+
totalTokens: usage.totalTokens,
|
|
141
|
+
reasoningTokens: usage.reasoningTokens,
|
|
142
|
+
cachedInputTokens: usage.cachedInputTokens,
|
|
143
|
+
// These detail fields are required by LanguageModelUsage type but we don't
|
|
144
|
+
// have the granular data, so we provide empty objects with undefined values.
|
|
145
|
+
inputTokenDetails: {
|
|
146
|
+
cacheReadTokens: undefined,
|
|
147
|
+
cacheWriteTokens: undefined,
|
|
148
|
+
noCacheTokens: undefined,
|
|
149
|
+
},
|
|
150
|
+
outputTokenDetails: {
|
|
151
|
+
textTokens: undefined,
|
|
152
|
+
reasoningTokens: undefined,
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
export function serializeWarnings(warnings) {
|
|
157
|
+
if (!warnings) {
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
return warnings.map((warning) => {
|
|
161
|
+
if (warning.type === "compatibility") {
|
|
162
|
+
return {
|
|
163
|
+
type: "unsupported-setting",
|
|
164
|
+
setting: warning.feature,
|
|
165
|
+
details: warning.details,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
return warning;
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
export function toModelMessageWarnings(warnings) {
|
|
172
|
+
if (!warnings) {
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
return warnings.map((warning) => {
|
|
176
|
+
if (warning.type === "unsupported-setting") {
|
|
177
|
+
return {
|
|
178
|
+
type: "compatibility",
|
|
179
|
+
feature: warning.setting,
|
|
180
|
+
details: warning.details,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
return warning;
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Serialize explicitly provided response messages for a step.
|
|
188
|
+
* Used by the streaming/generation loop where the caller tracks which
|
|
189
|
+
* messages are new via slicing.
|
|
190
|
+
*/
|
|
191
|
+
export async function serializeResponseMessages(ctx, component, step, model, responseMessages) {
|
|
192
|
+
return serializeStepMessages(ctx, component, step, model, responseMessages);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Serialize the new messages from a step using a heuristic to determine
|
|
196
|
+
* which response messages are new (last 1-2 messages).
|
|
197
|
+
*/
|
|
198
|
+
export async function serializeNewMessagesInStep(ctx, component, step, model) {
|
|
199
|
+
const hasToolMessage = step.response.messages.at(-1)?.role === "tool";
|
|
200
|
+
let messagesToSerialize;
|
|
201
|
+
if (hasToolMessage) {
|
|
202
|
+
messagesToSerialize = step.response.messages.slice(-2);
|
|
203
|
+
}
|
|
204
|
+
else if (step.content.length) {
|
|
205
|
+
messagesToSerialize = step.response.messages.slice(-1);
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
messagesToSerialize = [{ role: "assistant", content: [] }];
|
|
209
|
+
}
|
|
210
|
+
return serializeStepMessages(ctx, component, step, model, messagesToSerialize);
|
|
211
|
+
}
|
|
212
|
+
async function serializeStepMessages(ctx, component, step, model, messagesToSerialize) {
|
|
213
|
+
// If there are tool results, there's another message with the tool results
|
|
214
|
+
// ref: https://github.com/vercel/ai/blob/main/packages/ai/src/generate-text/to-response-messages.ts#L120
|
|
215
|
+
const hasToolMessage = step.response.messages.at(-1)?.role === "tool";
|
|
216
|
+
const assistantFields = {
|
|
217
|
+
model: model ? getModelName(model) : undefined,
|
|
218
|
+
provider: model ? getProviderName(model) : undefined,
|
|
219
|
+
providerMetadata: step.providerMetadata,
|
|
220
|
+
reasoning: step.reasoningText,
|
|
221
|
+
reasoningDetails: step.reasoning,
|
|
222
|
+
usage: serializeUsage(step.usage),
|
|
223
|
+
warnings: serializeWarnings(step.warnings),
|
|
224
|
+
finishReason: step.finishReason,
|
|
225
|
+
// Only store the sources on one message
|
|
226
|
+
sources: hasToolMessage ? undefined : step.sources,
|
|
227
|
+
};
|
|
228
|
+
const toolFields = { sources: step.sources };
|
|
229
|
+
const messages = await Promise.all(messagesToSerialize.map(async (msg) => {
|
|
230
|
+
const { message, fileIds } = await serializeMessage(ctx, component, msg);
|
|
231
|
+
return parse(vMessageWithMetadata, {
|
|
232
|
+
message,
|
|
233
|
+
...(message.role === "tool" ? toolFields : assistantFields),
|
|
234
|
+
text: step.text,
|
|
235
|
+
fileIds,
|
|
236
|
+
});
|
|
237
|
+
}));
|
|
238
|
+
// TODO: capture step.files separately?
|
|
239
|
+
return { messages };
|
|
240
|
+
}
|
|
241
|
+
export async function serializeObjectResult(ctx, component, result, model) {
|
|
242
|
+
const text = JSON.stringify(result.object);
|
|
243
|
+
const { message, fileIds } = await serializeMessage(ctx, component, {
|
|
244
|
+
role: "assistant",
|
|
245
|
+
content: text,
|
|
246
|
+
});
|
|
247
|
+
return {
|
|
248
|
+
messages: [
|
|
249
|
+
{
|
|
250
|
+
message,
|
|
251
|
+
model: model ? getModelName(model) : undefined,
|
|
252
|
+
provider: model ? getProviderName(model) : undefined,
|
|
253
|
+
providerMetadata: result.providerMetadata,
|
|
254
|
+
finishReason: result.finishReason,
|
|
255
|
+
text,
|
|
256
|
+
usage: serializeUsage(result.usage),
|
|
257
|
+
warnings: serializeWarnings(result.warnings),
|
|
258
|
+
fileIds,
|
|
259
|
+
},
|
|
260
|
+
],
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
function getMimeOrMediaType(part) {
|
|
264
|
+
if ("mediaType" in part) {
|
|
265
|
+
return part.mediaType;
|
|
266
|
+
}
|
|
267
|
+
if ("mimeType" in part) {
|
|
268
|
+
return part.mimeType;
|
|
269
|
+
}
|
|
270
|
+
return undefined;
|
|
271
|
+
}
|
|
272
|
+
export async function serializeContent(ctx, component, content) {
|
|
273
|
+
if (typeof content === "string") {
|
|
274
|
+
return { content };
|
|
275
|
+
}
|
|
276
|
+
const fileIds = [];
|
|
277
|
+
const serialized = await Promise.all(content.map(async (part) => {
|
|
278
|
+
const metadata = {};
|
|
279
|
+
if ("providerOptions" in part) {
|
|
280
|
+
metadata.providerOptions = part.providerOptions;
|
|
281
|
+
}
|
|
282
|
+
if ("providerMetadata" in part) {
|
|
283
|
+
metadata.providerMetadata = part.providerMetadata;
|
|
284
|
+
}
|
|
285
|
+
switch (part.type) {
|
|
286
|
+
case "text": {
|
|
287
|
+
return {
|
|
288
|
+
type: part.type,
|
|
289
|
+
text: part.text,
|
|
290
|
+
...metadata,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
case "image": {
|
|
294
|
+
let image = serializeDataOrUrl(part.image);
|
|
295
|
+
if (image instanceof ArrayBuffer &&
|
|
296
|
+
image.byteLength > MAX_FILE_SIZE) {
|
|
297
|
+
const { file } = await storeFile(ctx, component, new Blob([image], {
|
|
298
|
+
type: getMimeOrMediaType(part) || guessMimeType(image),
|
|
299
|
+
}));
|
|
300
|
+
image = file.url;
|
|
301
|
+
fileIds.push(file.fileId);
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
type: part.type,
|
|
305
|
+
mediaType: getMimeOrMediaType(part),
|
|
306
|
+
...metadata,
|
|
307
|
+
image,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
case "file": {
|
|
311
|
+
let data = serializeDataOrUrl(part.data);
|
|
312
|
+
if (data instanceof ArrayBuffer && data.byteLength > MAX_FILE_SIZE) {
|
|
313
|
+
const { file } = await storeFile(ctx, component, new Blob([data], { type: getMimeOrMediaType(part) }));
|
|
314
|
+
data = file.url;
|
|
315
|
+
fileIds.push(file.fileId);
|
|
316
|
+
}
|
|
317
|
+
return {
|
|
318
|
+
type: part.type,
|
|
319
|
+
data,
|
|
320
|
+
filename: part.filename,
|
|
321
|
+
mediaType: getMimeOrMediaType(part),
|
|
322
|
+
...metadata,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
case "tool-call": {
|
|
326
|
+
// Handle legacy data where only args field exists
|
|
327
|
+
const input = part.input ?? part?.args ?? {};
|
|
328
|
+
return {
|
|
329
|
+
type: part.type,
|
|
330
|
+
input,
|
|
331
|
+
/** @deprecated Use `input` instead. */
|
|
332
|
+
args: input,
|
|
333
|
+
toolCallId: part.toolCallId,
|
|
334
|
+
toolName: part.toolName,
|
|
335
|
+
providerExecuted: part.providerExecuted,
|
|
336
|
+
...metadata,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
case "tool-result": {
|
|
340
|
+
return normalizeToolResult(part, metadata);
|
|
341
|
+
}
|
|
342
|
+
case "reasoning": {
|
|
343
|
+
return {
|
|
344
|
+
type: part.type,
|
|
345
|
+
text: part.text,
|
|
346
|
+
...metadata,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
// Not in current generation output, but could be in historical messages
|
|
350
|
+
case "redacted-reasoning": {
|
|
351
|
+
return {
|
|
352
|
+
type: part.type,
|
|
353
|
+
data: part.data,
|
|
354
|
+
...metadata,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
case "source": {
|
|
358
|
+
return part;
|
|
359
|
+
}
|
|
360
|
+
case "tool-approval-request": {
|
|
361
|
+
return {
|
|
362
|
+
type: part.type,
|
|
363
|
+
approvalId: part.approvalId,
|
|
364
|
+
toolCallId: part.toolCallId,
|
|
365
|
+
...metadata,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
case "tool-approval-response": {
|
|
369
|
+
return {
|
|
370
|
+
type: part.type,
|
|
371
|
+
approvalId: part.approvalId,
|
|
372
|
+
approved: part.approved,
|
|
373
|
+
reason: part.reason,
|
|
374
|
+
providerExecuted: part.providerExecuted,
|
|
375
|
+
...metadata,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
default:
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
}));
|
|
382
|
+
return {
|
|
383
|
+
content: serialized.filter((p) => p !== null),
|
|
384
|
+
fileIds: fileIds.length > 0 ? fileIds : undefined,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
export function fromModelMessageContent(content) {
|
|
388
|
+
if (typeof content === "string") {
|
|
389
|
+
return content;
|
|
390
|
+
}
|
|
391
|
+
return content
|
|
392
|
+
.map((part) => {
|
|
393
|
+
const metadata = {};
|
|
394
|
+
if ("providerOptions" in part) {
|
|
395
|
+
metadata.providerOptions = part.providerOptions;
|
|
396
|
+
}
|
|
397
|
+
if ("providerMetadata" in part) {
|
|
398
|
+
metadata.providerMetadata = part.providerMetadata;
|
|
399
|
+
}
|
|
400
|
+
switch (part.type) {
|
|
401
|
+
case "text":
|
|
402
|
+
return part;
|
|
403
|
+
case "image":
|
|
404
|
+
return {
|
|
405
|
+
type: part.type,
|
|
406
|
+
mediaType: getMimeOrMediaType(part),
|
|
407
|
+
...metadata,
|
|
408
|
+
image: serializeDataOrUrl(part.image),
|
|
409
|
+
};
|
|
410
|
+
case "file":
|
|
411
|
+
return {
|
|
412
|
+
type: part.type,
|
|
413
|
+
data: serializeDataOrUrl(part.data),
|
|
414
|
+
filename: part.filename,
|
|
415
|
+
mediaType: getMimeOrMediaType(part),
|
|
416
|
+
...metadata,
|
|
417
|
+
};
|
|
418
|
+
case "tool-call":
|
|
419
|
+
// Handle legacy data where only args field exists
|
|
420
|
+
return {
|
|
421
|
+
type: part.type,
|
|
422
|
+
input: part.input ?? part?.args ?? {},
|
|
423
|
+
/** @deprecated Use `input` instead. */
|
|
424
|
+
args: part.input ?? part?.args ?? {},
|
|
425
|
+
toolCallId: part.toolCallId,
|
|
426
|
+
toolName: part.toolName,
|
|
427
|
+
providerExecuted: part.providerExecuted,
|
|
428
|
+
...metadata,
|
|
429
|
+
};
|
|
430
|
+
case "tool-result":
|
|
431
|
+
return normalizeToolResult(part, metadata);
|
|
432
|
+
case "reasoning":
|
|
433
|
+
return {
|
|
434
|
+
type: part.type,
|
|
435
|
+
text: part.text,
|
|
436
|
+
...metadata,
|
|
437
|
+
};
|
|
438
|
+
case "tool-approval-request":
|
|
439
|
+
return {
|
|
440
|
+
type: part.type,
|
|
441
|
+
approvalId: part.approvalId,
|
|
442
|
+
toolCallId: part.toolCallId,
|
|
443
|
+
...metadata,
|
|
444
|
+
};
|
|
445
|
+
case "tool-approval-response":
|
|
446
|
+
return {
|
|
447
|
+
type: part.type,
|
|
448
|
+
approvalId: part.approvalId,
|
|
449
|
+
approved: part.approved,
|
|
450
|
+
reason: part.reason,
|
|
451
|
+
providerExecuted: part.providerExecuted,
|
|
452
|
+
...metadata,
|
|
453
|
+
};
|
|
454
|
+
// Not in current generation output, but could be in historical messages
|
|
455
|
+
default:
|
|
456
|
+
return null;
|
|
457
|
+
}
|
|
458
|
+
})
|
|
459
|
+
.filter((p) => p !== null);
|
|
460
|
+
}
|
|
461
|
+
export function toModelMessageContent(content) {
|
|
462
|
+
if (typeof content === "string") {
|
|
463
|
+
return content;
|
|
464
|
+
}
|
|
465
|
+
return content
|
|
466
|
+
.map((part) => {
|
|
467
|
+
const metadata = {};
|
|
468
|
+
if ("providerOptions" in part) {
|
|
469
|
+
metadata.providerOptions = part.providerOptions;
|
|
470
|
+
}
|
|
471
|
+
if ("providerMetadata" in part) {
|
|
472
|
+
metadata.providerMetadata = part.providerMetadata;
|
|
473
|
+
}
|
|
474
|
+
switch (part.type) {
|
|
475
|
+
case "text":
|
|
476
|
+
return {
|
|
477
|
+
type: part.type,
|
|
478
|
+
text: part.text,
|
|
479
|
+
...metadata,
|
|
480
|
+
};
|
|
481
|
+
case "image":
|
|
482
|
+
return {
|
|
483
|
+
type: part.type,
|
|
484
|
+
image: toModelMessageDataOrUrl(part.image),
|
|
485
|
+
mediaType: getMimeOrMediaType(part),
|
|
486
|
+
...metadata,
|
|
487
|
+
};
|
|
488
|
+
case "file":
|
|
489
|
+
return {
|
|
490
|
+
type: part.type,
|
|
491
|
+
data: toModelMessageDataOrUrl(part.data),
|
|
492
|
+
filename: part.filename,
|
|
493
|
+
mediaType: getMimeOrMediaType(part),
|
|
494
|
+
...metadata,
|
|
495
|
+
};
|
|
496
|
+
case "tool-call": {
|
|
497
|
+
// Handle legacy data where only args field exists
|
|
498
|
+
const input = part.input ?? part?.args ?? {};
|
|
499
|
+
return {
|
|
500
|
+
type: part.type,
|
|
501
|
+
input,
|
|
502
|
+
toolCallId: part.toolCallId,
|
|
503
|
+
toolName: part.toolName,
|
|
504
|
+
providerExecuted: part.providerExecuted,
|
|
505
|
+
...metadata,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
case "tool-result": {
|
|
509
|
+
return normalizeToolResult(part, metadata);
|
|
510
|
+
}
|
|
511
|
+
case "reasoning":
|
|
512
|
+
return {
|
|
513
|
+
type: part.type,
|
|
514
|
+
text: part.text,
|
|
515
|
+
...metadata,
|
|
516
|
+
};
|
|
517
|
+
case "redacted-reasoning":
|
|
518
|
+
// TODO: should we just drop this?
|
|
519
|
+
return {
|
|
520
|
+
type: "reasoning",
|
|
521
|
+
text: "",
|
|
522
|
+
...metadata,
|
|
523
|
+
providerOptions: metadata.providerOptions
|
|
524
|
+
? {
|
|
525
|
+
...Object.fromEntries(Object.entries(metadata.providerOptions ?? {}).map(([key, value]) => [
|
|
526
|
+
key,
|
|
527
|
+
{ ...value, redactedData: part.data },
|
|
528
|
+
])),
|
|
529
|
+
}
|
|
530
|
+
: undefined,
|
|
531
|
+
};
|
|
532
|
+
case "source":
|
|
533
|
+
return part;
|
|
534
|
+
case "tool-approval-request":
|
|
535
|
+
return {
|
|
536
|
+
type: part.type,
|
|
537
|
+
approvalId: part.approvalId,
|
|
538
|
+
toolCallId: part.toolCallId,
|
|
539
|
+
...metadata,
|
|
540
|
+
};
|
|
541
|
+
case "tool-approval-response":
|
|
542
|
+
return {
|
|
543
|
+
type: part.type,
|
|
544
|
+
approvalId: part.approvalId,
|
|
545
|
+
approved: part.approved,
|
|
546
|
+
reason: part.reason,
|
|
547
|
+
providerExecuted: part.providerExecuted,
|
|
548
|
+
...metadata,
|
|
549
|
+
};
|
|
550
|
+
default:
|
|
551
|
+
return null;
|
|
552
|
+
}
|
|
553
|
+
})
|
|
554
|
+
.filter((p) => p !== null);
|
|
555
|
+
}
|
|
556
|
+
export function normalizeToolOutput(result) {
|
|
557
|
+
if (typeof result === "string") {
|
|
558
|
+
return {
|
|
559
|
+
type: "text",
|
|
560
|
+
value: result,
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
if (validate(vToolResultOutput, result)) {
|
|
564
|
+
return result;
|
|
565
|
+
}
|
|
566
|
+
return {
|
|
567
|
+
type: "json",
|
|
568
|
+
value: result ?? null,
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
function normalizeToolResult(part, metadata) {
|
|
572
|
+
return {
|
|
573
|
+
type: part.type,
|
|
574
|
+
output: part.output
|
|
575
|
+
? validate(vToolResultOutput, part.output)
|
|
576
|
+
? part.output
|
|
577
|
+
: normalizeToolOutput(JSON.stringify(part.output))
|
|
578
|
+
: normalizeToolOutput("result" in part ? part.result : undefined),
|
|
579
|
+
toolCallId: part.toolCallId,
|
|
580
|
+
toolName: part.toolName,
|
|
581
|
+
// Preserve isError flag for error reporting
|
|
582
|
+
...("isError" in part && part.isError ? { isError: true } : {}),
|
|
583
|
+
...metadata,
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Return a best-guess MIME type based on the magic-number signature
|
|
588
|
+
* found at the start of an ArrayBuffer.
|
|
589
|
+
*
|
|
590
|
+
* @param buf – the source ArrayBuffer
|
|
591
|
+
* @returns the detected MIME type, or `"application/octet-stream"` if unknown
|
|
592
|
+
*/
|
|
593
|
+
export function guessMimeType(buf) {
|
|
594
|
+
if (typeof buf === "string") {
|
|
595
|
+
if (buf.match(/^data:\w+\/\w+;base64/)) {
|
|
596
|
+
return buf.split(";")[0].split(":")[1];
|
|
597
|
+
}
|
|
598
|
+
return "text/plain";
|
|
599
|
+
}
|
|
600
|
+
if (buf.byteLength < 4)
|
|
601
|
+
return "application/octet-stream";
|
|
602
|
+
// Read the first 12 bytes (enough for all signatures below)
|
|
603
|
+
const bytes = new Uint8Array(buf.slice(0, 12));
|
|
604
|
+
const hex = [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
605
|
+
// Helper so we can look at only the needed prefix
|
|
606
|
+
const startsWith = (sig) => hex.startsWith(sig.toLowerCase());
|
|
607
|
+
// --- image formats ---
|
|
608
|
+
if (startsWith("89504e47"))
|
|
609
|
+
return "image/png"; // PNG - 89 50 4E 47
|
|
610
|
+
if (startsWith("ffd8ffdb") ||
|
|
611
|
+
startsWith("ffd8ffe0") ||
|
|
612
|
+
startsWith("ffd8ffee") ||
|
|
613
|
+
startsWith("ffd8ffe1"))
|
|
614
|
+
return "image/jpeg"; // JPEG
|
|
615
|
+
if (startsWith("47494638"))
|
|
616
|
+
return "image/gif"; // GIF
|
|
617
|
+
if (startsWith("424d"))
|
|
618
|
+
return "image/bmp"; // BMP
|
|
619
|
+
if (startsWith("52494646") && hex.substr(16, 8) === "57454250")
|
|
620
|
+
return "image/webp"; // WEBP (RIFF....WEBP)
|
|
621
|
+
if (startsWith("49492a00"))
|
|
622
|
+
return "image/tiff"; // TIFF
|
|
623
|
+
// <svg in hex is 3c 3f 78 6d 6c
|
|
624
|
+
if (startsWith("3c737667"))
|
|
625
|
+
return "image/svg+xml"; // <svg
|
|
626
|
+
if (startsWith("3c3f786d"))
|
|
627
|
+
return "image/svg+xml"; // <?xm
|
|
628
|
+
// --- audio/video ---
|
|
629
|
+
if (startsWith("494433"))
|
|
630
|
+
return "audio/mpeg"; // MP3 (ID3)
|
|
631
|
+
if (startsWith("000001ba") || startsWith("000001b3"))
|
|
632
|
+
return "video/mpeg"; // MPEG container
|
|
633
|
+
if (startsWith("1a45dfa3"))
|
|
634
|
+
return "video/webm"; // WEBM / Matroska
|
|
635
|
+
if (startsWith("00000018") && hex.substr(16, 8) === "66747970")
|
|
636
|
+
return "video/mp4"; // MP4
|
|
637
|
+
if (startsWith("4f676753"))
|
|
638
|
+
return "audio/ogg"; // OGG / Opus
|
|
639
|
+
// --- documents & archives ---
|
|
640
|
+
if (startsWith("25504446"))
|
|
641
|
+
return "application/pdf"; // PDF
|
|
642
|
+
if (startsWith("504b0304") ||
|
|
643
|
+
startsWith("504b0506") ||
|
|
644
|
+
startsWith("504b0708"))
|
|
645
|
+
return "application/zip"; // ZIP / DOCX / PPTX / XLSX / EPUB
|
|
646
|
+
if (startsWith("52617221"))
|
|
647
|
+
return "application/x-rar-compressed"; // RAR
|
|
648
|
+
if (startsWith("7f454c46"))
|
|
649
|
+
return "application/x-elf"; // ELF binaries
|
|
650
|
+
if (startsWith("1f8b08"))
|
|
651
|
+
return "application/gzip"; // GZIP
|
|
652
|
+
if (startsWith("425a68"))
|
|
653
|
+
return "application/x-bzip2"; // BZIP2
|
|
654
|
+
if (startsWith("3c3f786d6c"))
|
|
655
|
+
return "application/xml"; // XML
|
|
656
|
+
// Plain text, JSON and others are trickier—fallback:
|
|
657
|
+
return "application/octet-stream";
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Serialize an AI SDK `DataContent` or `URL` to a Convex-serializable format.
|
|
661
|
+
* @param dataOrUrl - The data or URL to serialize.
|
|
662
|
+
* @returns The serialized data as an ArrayBuffer or the URL as a string.
|
|
663
|
+
*/
|
|
664
|
+
export function serializeDataOrUrl(dataOrUrl) {
|
|
665
|
+
if (typeof dataOrUrl === "string") {
|
|
666
|
+
return dataOrUrl;
|
|
667
|
+
}
|
|
668
|
+
if (dataOrUrl instanceof ArrayBuffer) {
|
|
669
|
+
return dataOrUrl; // Already an ArrayBuffer
|
|
670
|
+
}
|
|
671
|
+
if (dataOrUrl instanceof URL) {
|
|
672
|
+
return dataOrUrl.toString();
|
|
673
|
+
}
|
|
674
|
+
return dataOrUrl.buffer.slice(dataOrUrl.byteOffset, dataOrUrl.byteOffset + dataOrUrl.byteLength);
|
|
675
|
+
}
|
|
676
|
+
export function toModelMessageDataOrUrl(urlOrString) {
|
|
677
|
+
if (urlOrString instanceof URL) {
|
|
678
|
+
return urlOrString;
|
|
679
|
+
}
|
|
680
|
+
if (typeof urlOrString === "string") {
|
|
681
|
+
if (urlOrString.startsWith("http://") ||
|
|
682
|
+
urlOrString.startsWith("https://")) {
|
|
683
|
+
return new URL(urlOrString);
|
|
684
|
+
}
|
|
685
|
+
return urlOrString;
|
|
686
|
+
}
|
|
687
|
+
return urlOrString;
|
|
688
|
+
}
|
|
689
|
+
export function toUIFilePart(part) {
|
|
690
|
+
const dataOrUrl = part.type === "image" ? part.image : part.data;
|
|
691
|
+
const url = dataOrUrl instanceof ArrayBuffer
|
|
692
|
+
? convertUint8ArrayToBase64(new Uint8Array(dataOrUrl))
|
|
693
|
+
: dataOrUrl.toString();
|
|
694
|
+
return {
|
|
695
|
+
type: "file",
|
|
696
|
+
mediaType: part.mediaType,
|
|
697
|
+
filename: part.type === "file" ? part.filename : undefined,
|
|
698
|
+
url,
|
|
699
|
+
providerMetadata: part.providerOptions,
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
// Currently unused
|
|
703
|
+
// export function toModelMessages(args: {
|
|
704
|
+
// messages?: ModelMessage[] | AIMessageWithoutId[];
|
|
705
|
+
// }): ModelMessage[] {
|
|
706
|
+
// const messages: ModelMessage[] = [];
|
|
707
|
+
// if (args.messages) {
|
|
708
|
+
// if (
|
|
709
|
+
// args.messages.every(
|
|
710
|
+
// (m) => typeof m === "object" && m !== null && "parts" in m,
|
|
711
|
+
// )
|
|
712
|
+
// ) {
|
|
713
|
+
// messages.push(...convertToModelMessages(args.messages));
|
|
714
|
+
// } else {
|
|
715
|
+
// messages.push(...modelMessageSchema.array().parse(args.messages));
|
|
716
|
+
// }
|
|
717
|
+
// }
|
|
718
|
+
// return messages;
|
|
719
|
+
// }
|
|
720
|
+
//# sourceMappingURL=mapping.js.map
|