veryfront 0.1.291 → 0.1.293

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/esm/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.291",
3
+ "version": "0.1.293",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "workspace": [
@@ -226,6 +226,10 @@ export declare function pushToolParts(parts: MessagePart[], toolName: string, to
226
226
  output?: unknown;
227
227
  errorText?: unknown;
228
228
  }): void;
229
+ export declare function toConversationPartsFromUiMessage(message: ChatUiMessage): MessagePart[];
230
+ export declare function hasIncompleteToolParts(message: ChatUiMessage): boolean;
231
+ export declare function markIncompleteToolPartsAsStopped(message: ChatUiMessage): ChatUiMessage;
232
+ export declare function markIncompleteToolPartsAsErrored(message: ChatUiMessage, errorText: string): ChatUiMessage;
229
233
  export declare function isToolCallPart(value: unknown): value is ToolCallLike;
230
234
  export declare function isToolResultPart(value: unknown): value is ToolResultLike;
231
235
  export declare function isTextPart(value: unknown): value is TextPartLike;
@@ -1 +1 @@
1
- {"version":3,"file":"conversation.d.ts","sourceRoot":"","sources":["../../../src/src/chat/conversation.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"conversation.d.ts","sourceRoot":"","sources":["../../../src/src/chat/conversation.ts"],"names":[],"mappings":"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"}
@@ -214,6 +214,129 @@ export function pushToolParts(parts, toolName, toolCallId, state, part) {
214
214
  state: mapToolState(state),
215
215
  });
216
216
  }
217
+ function pushFileConversationPart(parts, part) {
218
+ const uploadId = part.uploadId ?? extractUploadId(part.url);
219
+ if (!uploadId)
220
+ return;
221
+ if (part.mediaType.startsWith("image/")) {
222
+ parts.push({
223
+ type: "image",
224
+ upload_id: uploadId,
225
+ media_type: part.mediaType,
226
+ ...(part.url ? { url: part.url } : {}),
227
+ });
228
+ return;
229
+ }
230
+ parts.push({
231
+ type: "file",
232
+ upload_id: uploadId,
233
+ media_type: part.mediaType,
234
+ ...(part.url ? { url: part.url } : {}),
235
+ });
236
+ }
237
+ export function toConversationPartsFromUiMessage(message) {
238
+ const parts = [];
239
+ for (const part of message.parts) {
240
+ if (part.type === "text") {
241
+ parts.push({ type: "text", text: part.text });
242
+ continue;
243
+ }
244
+ if (part.type === "reasoning") {
245
+ parts.push({ type: "reasoning", text: part.text });
246
+ continue;
247
+ }
248
+ if (part.type === "step-start") {
249
+ continue;
250
+ }
251
+ if (part.type === "source-url") {
252
+ parts.push({
253
+ type: "citation",
254
+ source_id: part.sourceId,
255
+ title: part.title,
256
+ url: part.url,
257
+ });
258
+ continue;
259
+ }
260
+ if (part.type === "source-document") {
261
+ parts.push({
262
+ type: "citation",
263
+ source_id: part.sourceId,
264
+ title: part.title,
265
+ });
266
+ continue;
267
+ }
268
+ if (part.type === "file") {
269
+ pushFileConversationPart(parts, part);
270
+ continue;
271
+ }
272
+ if (isDataUiPart(part)) {
273
+ const name = part.type.replace(/^data-/, "");
274
+ if (name.length > 0) {
275
+ parts.push({
276
+ type: "data",
277
+ name,
278
+ value: part.data,
279
+ });
280
+ }
281
+ continue;
282
+ }
283
+ if (isToolUiPart(part)) {
284
+ const toolName = getUiToolName(part);
285
+ if (!toolName) {
286
+ continue;
287
+ }
288
+ pushToolParts(parts, toolName, part.toolCallId, part.state, part);
289
+ }
290
+ }
291
+ return parts.filter((part) => messagePartSchema.safeParse(part).success);
292
+ }
293
+ function isToolComplete(part) {
294
+ return part.state === "output-available" || part.state === "output-error" ||
295
+ part.state === "output-denied";
296
+ }
297
+ export function hasIncompleteToolParts(message) {
298
+ return message.parts.some((part) => isToolUiPart(part) && !isToolComplete(part));
299
+ }
300
+ export function markIncompleteToolPartsAsStopped(message) {
301
+ return markIncompleteToolPartsAsErrored(message, "Stopped by user");
302
+ }
303
+ export function markIncompleteToolPartsAsErrored(message, errorText) {
304
+ let mutated = false;
305
+ const parts = message.parts.map((part) => {
306
+ if (!isToolUiPart(part) || isToolComplete(part)) {
307
+ return part;
308
+ }
309
+ mutated = true;
310
+ return markToolPartAsErrored(part, errorText);
311
+ });
312
+ return mutated ? { ...message, parts } : message;
313
+ }
314
+ function markToolPartAsErrored(part, errorText) {
315
+ if (part.type === "dynamic-tool") {
316
+ return {
317
+ type: "dynamic-tool",
318
+ toolName: part.toolName,
319
+ toolCallId: part.toolCallId,
320
+ ...(part.title ? { title: part.title } : {}),
321
+ ...(part.providerExecuted !== undefined ? { providerExecuted: part.providerExecuted } : {}),
322
+ ...(part.callProviderMetadata ? { callProviderMetadata: part.callProviderMetadata } : {}),
323
+ input: part.input,
324
+ state: "output-error",
325
+ errorText,
326
+ };
327
+ }
328
+ return {
329
+ type: part.type,
330
+ toolCallId: part.toolCallId,
331
+ ...(part.toolName ? { toolName: part.toolName } : {}),
332
+ ...(part.title ? { title: part.title } : {}),
333
+ ...(part.providerExecuted !== undefined ? { providerExecuted: part.providerExecuted } : {}),
334
+ ...(part.callProviderMetadata ? { callProviderMetadata: part.callProviderMetadata } : {}),
335
+ input: part.input,
336
+ state: "output-error",
337
+ errorText,
338
+ };
339
+ }
217
340
  export function isToolCallPart(value) {
218
341
  return (isRecord(value) &&
219
342
  value.type === "tool-call" &&
@@ -1,7 +1,13 @@
1
- import type { ChatModelMessage } from "./types.js";
1
+ import { type ChatModelMessage, type ChatUiMessage } from "./types.js";
2
2
  export declare function estimateTokens(value: unknown): number;
3
3
  export declare function compressTurn(messages: ChatModelMessage[], startIdx: number, endIdx: number): ChatModelMessage[];
4
4
  export declare function enforceTokenBudgetWithTurnCompression(messages: ChatModelMessage[], budget: number, overhead: number): ChatModelMessage[];
5
+ export declare function isModelSupportedFileMediaType(mediaType: string): boolean;
6
+ export declare function normalizeMessageFilePartMediaTypes(messages: ChatUiMessage[]): ChatUiMessage[];
7
+ export declare function rewriteUnsupportedFilePartsAsAnnotations(messages: ChatUiMessage[]): ChatUiMessage[];
8
+ export declare function stripPendingToolParts(messages: ChatUiMessage[]): ChatUiMessage[];
9
+ export declare function sanitizeModelMessages(messages: ChatModelMessage[]): ChatModelMessage[];
10
+ export declare function prepareModelMessagesFromUiMessages(messages: ChatUiMessage[]): ChatModelMessage[];
5
11
  export declare function maskOldToolOutputs(messages: ChatModelMessage[]): ChatModelMessage[];
6
12
  export declare function repairToolPairs(messages: ChatModelMessage[]): ChatModelMessage[];
7
13
  export declare function estimateOverhead(instructions: unknown, toolCount: number): number;
@@ -1 +1 @@
1
- {"version":3,"file":"message-prep.d.ts","sourceRoot":"","sources":["../../../src/src/chat/message-prep.ts"],"names":[],"mappings":"AAAA,OAAO,yBAAyB,CAAC;AAOjC,OAAO,KAAK,EAAE,gBAAgB,EAA4C,MAAM,YAAY,CAAC;AAI7F,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAErD;AAOD,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,gBAAgB,EAAE,EAC5B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,gBAAgB,EAAE,CA+BpB;AAkCD,wBAAgB,qCAAqC,CACnD,QAAQ,EAAE,gBAAgB,EAAE,EAC5B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,gBAAgB,EAAE,CAwDpB;AAyHD,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,GAAG,gBAAgB,EAAE,CAqEnF;AAWD,wBAAgB,eAAe,CAAC,QAAQ,EAAE,gBAAgB,EAAE,GAAG,gBAAgB,EAAE,CA2IhF;AAED,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAGjF;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,GAAG,gBAAgB,EAAE,CA4BrF;AAED,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,gBAAgB,EAAE,EAC5B,QAAQ,GAAE,MAAU,GACnB,gBAAgB,EAAE,CAepB;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,GAAG,gBAAgB,EAAE,CA0DlF;AAED,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,gBAAgB,EAAE,EAC5B,MAAM,GAAE,MAA6B,EACrC,QAAQ,GAAE,MAAU,GACnB,gBAAgB,EAAE,CAMpB"}
1
+ {"version":3,"file":"message-prep.d.ts","sourceRoot":"","sources":["../../../src/src/chat/message-prep.ts"],"names":[],"mappings":"AAAA,OAAO,yBAAyB,CAAC;AAQjC,OAAO,EAEL,KAAK,gBAAgB,EAGrB,KAAK,aAAa,EAInB,MAAM,YAAY,CAAC;AAIpB,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAErD;AAOD,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,gBAAgB,EAAE,EAC5B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,gBAAgB,EAAE,CA+BpB;AAkCD,wBAAgB,qCAAqC,CACnD,QAAQ,EAAE,gBAAgB,EAAE,EAC5B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,gBAAgB,EAAE,CAwDpB;AA+BD,wBAAgB,6BAA6B,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAGxE;AAED,wBAAgB,kCAAkC,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,aAAa,EAAE,CAwB7F;AAED,wBAAgB,wCAAwC,CACtD,QAAQ,EAAE,aAAa,EAAE,GACxB,aAAa,EAAE,CAmDjB;AAiBD,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,aAAa,EAAE,CAiBhF;AA6CD,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,GAAG,gBAAgB,EAAE,CAwBtF;AAYD,wBAAgB,kCAAkC,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,gBAAgB,EAAE,CAchG;AA4FD,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,GAAG,gBAAgB,EAAE,CAqEnF;AAWD,wBAAgB,eAAe,CAAC,QAAQ,EAAE,gBAAgB,EAAE,GAAG,gBAAgB,EAAE,CA2IhF;AAED,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAGjF;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,GAAG,gBAAgB,EAAE,CA4BrF;AAED,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,gBAAgB,EAAE,EAC5B,QAAQ,GAAE,MAAU,GACnB,gBAAgB,EAAE,CAepB;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,GAAG,gBAAgB,EAAE,CA0DlF;AAED,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,gBAAgB,EAAE,EAC5B,MAAM,GAAE,MAA6B,EACrC,QAAQ,GAAE,MAAU,GACnB,gBAAgB,EAAE,CAMpB"}
@@ -1,4 +1,5 @@
1
- import { getStringField, isReasoningPart, isToolCallPart, isToolResultPart, } from "./conversation.js";
1
+ import { convertUiMessagesToModelMessages, getStringField, isReasoningPart, isToolCallPart, isToolResultPart, } from "./conversation.js";
2
+ import { buildDataFileAnnotation, normalizeInlineAttachmentMediaType, } from "./types.js";
2
3
  const CHARS_PER_TOKEN = 4;
3
4
  export function estimateTokens(value) {
4
5
  return Math.ceil(JSON.stringify(value ?? "").length / CHARS_PER_TOKEN);
@@ -132,6 +133,196 @@ function tryParseJson(value) {
132
133
  function isRecord(value) {
133
134
  return typeof value === "object" && value !== null && !Array.isArray(value);
134
135
  }
136
+ export function isModelSupportedFileMediaType(mediaType) {
137
+ return mediaType.startsWith("image/") || mediaType === "application/pdf" ||
138
+ mediaType === "text/plain";
139
+ }
140
+ export function normalizeMessageFilePartMediaTypes(messages) {
141
+ return messages.map((message) => {
142
+ if (!message.parts.some((part) => part.type === "file")) {
143
+ return message;
144
+ }
145
+ const parts = message.parts.map((part) => {
146
+ if (part.type !== "file") {
147
+ return part;
148
+ }
149
+ const mediaType = normalizeInlineAttachmentMediaType(part.filename, part.mediaType);
150
+ if (mediaType === part.mediaType) {
151
+ return part;
152
+ }
153
+ return {
154
+ ...part,
155
+ mediaType,
156
+ };
157
+ });
158
+ return { ...message, parts };
159
+ });
160
+ }
161
+ export function rewriteUnsupportedFilePartsAsAnnotations(messages) {
162
+ return messages.map((message) => {
163
+ if (message.parts.length === 0) {
164
+ return message;
165
+ }
166
+ const kept = [];
167
+ const dataFiles = [];
168
+ for (const part of message.parts) {
169
+ if (part.type !== "file") {
170
+ kept.push(part);
171
+ continue;
172
+ }
173
+ const normalizedMediaType = normalizeInlineAttachmentMediaType(part.filename, part.mediaType);
174
+ if (isModelSupportedFileMediaType(normalizedMediaType)) {
175
+ kept.push({
176
+ ...part,
177
+ mediaType: normalizedMediaType,
178
+ });
179
+ continue;
180
+ }
181
+ dataFiles.push({
182
+ name: part.filename || "file",
183
+ mediaType: normalizedMediaType,
184
+ ...(part.url ? { url: part.url } : {}),
185
+ ...(part.uploadId ? { uploadId: part.uploadId } : {}),
186
+ ...(part.uploadPath ? { path: part.uploadPath } : {}),
187
+ });
188
+ }
189
+ if (dataFiles.length === 0) {
190
+ return message;
191
+ }
192
+ const annotation = buildDataFileAnnotation(dataFiles);
193
+ const lastTextIndex = kept.findLastIndex((part) => part.type === "text");
194
+ if (lastTextIndex >= 0) {
195
+ const textPart = kept[lastTextIndex];
196
+ if (textPart.type === "text") {
197
+ kept[lastTextIndex] = { type: "text", text: textPart.text + annotation };
198
+ }
199
+ }
200
+ else {
201
+ kept.push({ type: "text", text: annotation.trimStart() });
202
+ }
203
+ return { ...message, parts: kept };
204
+ });
205
+ }
206
+ function isPendingToolPart(part) {
207
+ if (!isRecord(part) || typeof part.type !== "string") {
208
+ return false;
209
+ }
210
+ const state = typeof part.state === "string" ? part.state : null;
211
+ const isPendingState = state === "pending" || state === "input-available" ||
212
+ state === "input-streaming";
213
+ if (!isPendingState) {
214
+ return false;
215
+ }
216
+ return part.type === "dynamic-tool" || part.type === "tool_call" || part.type.startsWith("tool-");
217
+ }
218
+ export function stripPendingToolParts(messages) {
219
+ return messages.flatMap((message) => {
220
+ if (message.role !== "assistant" || message.parts.length === 0) {
221
+ return [message];
222
+ }
223
+ const parts = message.parts.filter((part) => !isPendingToolPart(part));
224
+ if (parts.length === message.parts.length) {
225
+ return [message];
226
+ }
227
+ if (parts.length === 0) {
228
+ return [];
229
+ }
230
+ return [{ ...message, parts }];
231
+ });
232
+ }
233
+ function isKeepableModelPart(part, includeReasoning) {
234
+ if (!isRecord(part) || typeof part.type !== "string")
235
+ return false;
236
+ switch (part.type) {
237
+ case "text":
238
+ return typeof part.text === "string" && part.text.trim().length > 0;
239
+ case "reasoning":
240
+ return includeReasoning;
241
+ case "tool-call":
242
+ case "tool-result":
243
+ case "image":
244
+ return true;
245
+ case "file": {
246
+ const hasMediaType = typeof part.mediaType === "string" && part.mediaType.length > 0;
247
+ if (!hasMediaType) {
248
+ return false;
249
+ }
250
+ const url = typeof part.url === "string" ? part.url : "";
251
+ if (url.startsWith("data:image/") && part.filename === "preview-screenshot.png") {
252
+ return false;
253
+ }
254
+ return true;
255
+ }
256
+ default:
257
+ return true;
258
+ }
259
+ }
260
+ function hasValidContent(message) {
261
+ const content = message.content;
262
+ if (content === undefined || content === null)
263
+ return false;
264
+ if (typeof content === "string")
265
+ return content.trim().length > 0;
266
+ if (Array.isArray(content))
267
+ return content.some((part) => isKeepableModelPart(part, false));
268
+ return true;
269
+ }
270
+ function cleanContent(content) {
271
+ const hasSubstantiveContent = content.some((part) => isKeepableModelPart(part, false));
272
+ return content.filter((part) => isKeepableModelPart(part, hasSubstantiveContent));
273
+ }
274
+ export function sanitizeModelMessages(messages) {
275
+ const result = [];
276
+ for (const message of messages) {
277
+ if (Array.isArray(message.content)) {
278
+ if (message.role === "user") {
279
+ const cleaned = cleanContent(message.content);
280
+ if (cleaned.length > 0)
281
+ result.push({ ...message, content: cleaned });
282
+ }
283
+ else if (message.role === "assistant") {
284
+ const cleaned = cleanContent(message.content);
285
+ if (cleaned.length > 0)
286
+ result.push({ ...message, content: cleaned });
287
+ }
288
+ else if (message.role === "tool") {
289
+ const cleaned = cleanContent(message.content);
290
+ if (cleaned.length > 0)
291
+ result.push({ ...message, content: cleaned });
292
+ }
293
+ continue;
294
+ }
295
+ if (hasValidContent(message)) {
296
+ result.push(message);
297
+ }
298
+ }
299
+ return result;
300
+ }
301
+ function filterValidMessages(messages) {
302
+ return messages.filter((message) => {
303
+ const content = message.content;
304
+ if (content === undefined || content === null)
305
+ return false;
306
+ if (typeof content === "string")
307
+ return content.trim().length > 0;
308
+ if (Array.isArray(content))
309
+ return content.length > 0;
310
+ return true;
311
+ });
312
+ }
313
+ export function prepareModelMessagesFromUiMessages(messages) {
314
+ const validMessages = messages.filter((message) => message && typeof message === "object" && "role" in message);
315
+ const normalizedMessages = normalizeMessageFilePartMediaTypes(validMessages);
316
+ const strippedPendingToolMessages = stripPendingToolParts(normalizedMessages);
317
+ const rewrittenMessages = rewriteUnsupportedFilePartsAsAnnotations(strippedPendingToolMessages);
318
+ const modelMessages = convertUiMessagesToModelMessages(rewrittenMessages);
319
+ const patchedMessages = ensureToolCallInputs(dedupeToolHistory(modelMessages));
320
+ const sanitized = sanitizeModelMessages(patchedMessages);
321
+ const masked = maskOldToolOutputs(sanitized);
322
+ const compacted = enforceTokenBudget(masked);
323
+ const filtered = filterValidMessages(compacted);
324
+ return repairToolPairs(filtered);
325
+ }
135
326
  function buildToolCallMap(messages) {
136
327
  const map = new Map();
137
328
  for (const msg of messages) {
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.1.291";
1
+ export declare const VERSION = "0.1.293";
2
2
  //# sourceMappingURL=version-constant.d.ts.map
@@ -1,3 +1,3 @@
1
1
  // Keep in sync with deno.json version.
2
2
  // scripts/release.ts updates this constant during releases.
3
- export const VERSION = "0.1.291";
3
+ export const VERSION = "0.1.293";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veryfront",
3
- "version": "0.1.291",
3
+ "version": "0.1.293",
4
4
  "description": "The simplest way to build AI-powered apps",
5
5
  "keywords": [
6
6
  "react",
package/src/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.291",
3
+ "version": "0.1.293",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "workspace": [
@@ -293,6 +293,157 @@ export function pushToolParts(
293
293
  });
294
294
  }
295
295
 
296
+ function pushFileConversationPart(
297
+ parts: MessagePart[],
298
+ part: Extract<ChatUiMessagePart, { type: "file" }>,
299
+ ): void {
300
+ const uploadId = part.uploadId ?? extractUploadId(part.url);
301
+ if (!uploadId) return;
302
+
303
+ if (part.mediaType.startsWith("image/")) {
304
+ parts.push({
305
+ type: "image",
306
+ upload_id: uploadId,
307
+ media_type: part.mediaType,
308
+ ...(part.url ? { url: part.url } : {}),
309
+ });
310
+ return;
311
+ }
312
+
313
+ parts.push({
314
+ type: "file",
315
+ upload_id: uploadId,
316
+ media_type: part.mediaType,
317
+ ...(part.url ? { url: part.url } : {}),
318
+ });
319
+ }
320
+
321
+ export function toConversationPartsFromUiMessage(message: ChatUiMessage): MessagePart[] {
322
+ const parts: MessagePart[] = [];
323
+
324
+ for (const part of message.parts) {
325
+ if (part.type === "text") {
326
+ parts.push({ type: "text", text: part.text });
327
+ continue;
328
+ }
329
+
330
+ if (part.type === "reasoning") {
331
+ parts.push({ type: "reasoning", text: part.text });
332
+ continue;
333
+ }
334
+
335
+ if (part.type === "step-start") {
336
+ continue;
337
+ }
338
+
339
+ if (part.type === "source-url") {
340
+ parts.push({
341
+ type: "citation",
342
+ source_id: part.sourceId,
343
+ title: part.title,
344
+ url: part.url,
345
+ });
346
+ continue;
347
+ }
348
+
349
+ if (part.type === "source-document") {
350
+ parts.push({
351
+ type: "citation",
352
+ source_id: part.sourceId,
353
+ title: part.title,
354
+ });
355
+ continue;
356
+ }
357
+
358
+ if (part.type === "file") {
359
+ pushFileConversationPart(parts, part);
360
+ continue;
361
+ }
362
+
363
+ if (isDataUiPart(part)) {
364
+ const name = part.type.replace(/^data-/, "");
365
+ if (name.length > 0) {
366
+ parts.push({
367
+ type: "data",
368
+ name,
369
+ value: part.data,
370
+ });
371
+ }
372
+ continue;
373
+ }
374
+
375
+ if (isToolUiPart(part)) {
376
+ const toolName = getUiToolName(part);
377
+ if (!toolName) {
378
+ continue;
379
+ }
380
+
381
+ pushToolParts(parts, toolName, part.toolCallId, part.state, part);
382
+ }
383
+ }
384
+
385
+ return parts.filter((part) => messagePartSchema.safeParse(part).success);
386
+ }
387
+
388
+ function isToolComplete(part: ToolUiPart): boolean {
389
+ return part.state === "output-available" || part.state === "output-error" ||
390
+ part.state === "output-denied";
391
+ }
392
+
393
+ export function hasIncompleteToolParts(message: ChatUiMessage): boolean {
394
+ return message.parts.some((part) => isToolUiPart(part) && !isToolComplete(part));
395
+ }
396
+
397
+ export function markIncompleteToolPartsAsStopped(message: ChatUiMessage): ChatUiMessage {
398
+ return markIncompleteToolPartsAsErrored(message, "Stopped by user");
399
+ }
400
+
401
+ export function markIncompleteToolPartsAsErrored(
402
+ message: ChatUiMessage,
403
+ errorText: string,
404
+ ): ChatUiMessage {
405
+ let mutated = false;
406
+
407
+ const parts = message.parts.map((part) => {
408
+ if (!isToolUiPart(part) || isToolComplete(part)) {
409
+ return part;
410
+ }
411
+
412
+ mutated = true;
413
+ return markToolPartAsErrored(part, errorText);
414
+ });
415
+
416
+ return mutated ? { ...message, parts } : message;
417
+ }
418
+
419
+ function markToolPartAsErrored(part: ToolUiPart, errorText: string): ChatUiMessagePart {
420
+ if (part.type === "dynamic-tool") {
421
+ return {
422
+ type: "dynamic-tool",
423
+ toolName: part.toolName,
424
+ toolCallId: part.toolCallId,
425
+ ...(part.title ? { title: part.title } : {}),
426
+ ...(part.providerExecuted !== undefined ? { providerExecuted: part.providerExecuted } : {}),
427
+ ...(part.callProviderMetadata ? { callProviderMetadata: part.callProviderMetadata } : {}),
428
+ input: part.input,
429
+ state: "output-error",
430
+ errorText,
431
+ };
432
+ }
433
+
434
+ return {
435
+ type: part.type,
436
+ toolCallId: part.toolCallId,
437
+ ...(part.toolName ? { toolName: part.toolName } : {}),
438
+ ...(part.title ? { title: part.title } : {}),
439
+ ...(part.providerExecuted !== undefined ? { providerExecuted: part.providerExecuted } : {}),
440
+ ...(part.callProviderMetadata ? { callProviderMetadata: part.callProviderMetadata } : {}),
441
+ input: part.input,
442
+ state: "output-error",
443
+ errorText,
444
+ };
445
+ }
446
+
296
447
  export function isToolCallPart(value: unknown): value is ToolCallLike {
297
448
  return (
298
449
  isRecord(value) &&
@@ -1,11 +1,21 @@
1
1
  import "../../_dnt.polyfills.js";
2
2
  import {
3
+ convertUiMessagesToModelMessages,
3
4
  getStringField,
4
5
  isReasoningPart,
5
6
  isToolCallPart,
6
7
  isToolResultPart,
7
8
  } from "./conversation.js";
8
- import type { ChatModelMessage, ChatToolResultOutput, ChatToolResultPart } from "./types.js";
9
+ import {
10
+ buildDataFileAnnotation,
11
+ type ChatModelMessage,
12
+ type ChatToolResultOutput,
13
+ type ChatToolResultPart,
14
+ type ChatUiMessage,
15
+ type ChatUiMessagePart,
16
+ normalizeInlineAttachmentMediaType,
17
+ type UploadedFileReference,
18
+ } from "./types.js";
9
19
 
10
20
  const CHARS_PER_TOKEN = 4;
11
21
 
@@ -178,6 +188,221 @@ interface ToolCallInfo {
178
188
  input: unknown;
179
189
  }
180
190
 
191
+ export function isModelSupportedFileMediaType(mediaType: string): boolean {
192
+ return mediaType.startsWith("image/") || mediaType === "application/pdf" ||
193
+ mediaType === "text/plain";
194
+ }
195
+
196
+ export function normalizeMessageFilePartMediaTypes(messages: ChatUiMessage[]): ChatUiMessage[] {
197
+ return messages.map((message) => {
198
+ if (!message.parts.some((part) => part.type === "file")) {
199
+ return message;
200
+ }
201
+
202
+ const parts = message.parts.map((part) => {
203
+ if (part.type !== "file") {
204
+ return part;
205
+ }
206
+
207
+ const mediaType = normalizeInlineAttachmentMediaType(part.filename, part.mediaType);
208
+ if (mediaType === part.mediaType) {
209
+ return part;
210
+ }
211
+
212
+ return {
213
+ ...part,
214
+ mediaType,
215
+ };
216
+ });
217
+
218
+ return { ...message, parts };
219
+ });
220
+ }
221
+
222
+ export function rewriteUnsupportedFilePartsAsAnnotations(
223
+ messages: ChatUiMessage[],
224
+ ): ChatUiMessage[] {
225
+ return messages.map((message) => {
226
+ if (message.parts.length === 0) {
227
+ return message;
228
+ }
229
+
230
+ const kept: ChatUiMessagePart[] = [];
231
+ const dataFiles: UploadedFileReference[] = [];
232
+
233
+ for (const part of message.parts) {
234
+ if (part.type !== "file") {
235
+ kept.push(part);
236
+ continue;
237
+ }
238
+
239
+ const normalizedMediaType = normalizeInlineAttachmentMediaType(part.filename, part.mediaType);
240
+ if (isModelSupportedFileMediaType(normalizedMediaType)) {
241
+ kept.push({
242
+ ...part,
243
+ mediaType: normalizedMediaType,
244
+ });
245
+ continue;
246
+ }
247
+
248
+ dataFiles.push({
249
+ name: part.filename || "file",
250
+ mediaType: normalizedMediaType,
251
+ ...(part.url ? { url: part.url } : {}),
252
+ ...(part.uploadId ? { uploadId: part.uploadId } : {}),
253
+ ...(part.uploadPath ? { path: part.uploadPath } : {}),
254
+ });
255
+ }
256
+
257
+ if (dataFiles.length === 0) {
258
+ return message;
259
+ }
260
+
261
+ const annotation = buildDataFileAnnotation(dataFiles);
262
+ const lastTextIndex = kept.findLastIndex((part) => part.type === "text");
263
+
264
+ if (lastTextIndex >= 0) {
265
+ const textPart = kept[lastTextIndex];
266
+ if (textPart.type === "text") {
267
+ kept[lastTextIndex] = { type: "text", text: textPart.text + annotation };
268
+ }
269
+ } else {
270
+ kept.push({ type: "text", text: annotation.trimStart() });
271
+ }
272
+
273
+ return { ...message, parts: kept };
274
+ });
275
+ }
276
+
277
+ function isPendingToolPart(part: unknown): boolean {
278
+ if (!isRecord(part) || typeof part.type !== "string") {
279
+ return false;
280
+ }
281
+
282
+ const state = typeof part.state === "string" ? part.state : null;
283
+ const isPendingState = state === "pending" || state === "input-available" ||
284
+ state === "input-streaming";
285
+ if (!isPendingState) {
286
+ return false;
287
+ }
288
+
289
+ return part.type === "dynamic-tool" || part.type === "tool_call" || part.type.startsWith("tool-");
290
+ }
291
+
292
+ export function stripPendingToolParts(messages: ChatUiMessage[]): ChatUiMessage[] {
293
+ return messages.flatMap((message) => {
294
+ if (message.role !== "assistant" || message.parts.length === 0) {
295
+ return [message];
296
+ }
297
+
298
+ const parts = message.parts.filter((part) => !isPendingToolPart(part));
299
+ if (parts.length === message.parts.length) {
300
+ return [message];
301
+ }
302
+
303
+ if (parts.length === 0) {
304
+ return [];
305
+ }
306
+
307
+ return [{ ...message, parts }];
308
+ });
309
+ }
310
+
311
+ function isKeepableModelPart(part: unknown, includeReasoning: boolean): boolean {
312
+ if (!isRecord(part) || typeof part.type !== "string") return false;
313
+
314
+ switch (part.type) {
315
+ case "text":
316
+ return typeof part.text === "string" && part.text.trim().length > 0;
317
+ case "reasoning":
318
+ return includeReasoning;
319
+ case "tool-call":
320
+ case "tool-result":
321
+ case "image":
322
+ return true;
323
+ case "file": {
324
+ const hasMediaType = typeof part.mediaType === "string" && part.mediaType.length > 0;
325
+ if (!hasMediaType) {
326
+ return false;
327
+ }
328
+
329
+ const url = typeof part.url === "string" ? part.url : "";
330
+ if (url.startsWith("data:image/") && part.filename === "preview-screenshot.png") {
331
+ return false;
332
+ }
333
+ return true;
334
+ }
335
+ default:
336
+ return true;
337
+ }
338
+ }
339
+
340
+ function hasValidContent(message: ChatModelMessage): boolean {
341
+ const content = message.content;
342
+
343
+ if (content === undefined || content === null) return false;
344
+ if (typeof content === "string") return content.trim().length > 0;
345
+ if (Array.isArray(content)) return content.some((part) => isKeepableModelPart(part, false));
346
+ return true;
347
+ }
348
+
349
+ function cleanContent<T>(content: T[]): T[] {
350
+ const hasSubstantiveContent = content.some((part) => isKeepableModelPart(part, false));
351
+ return content.filter((part) => isKeepableModelPart(part, hasSubstantiveContent));
352
+ }
353
+
354
+ export function sanitizeModelMessages(messages: ChatModelMessage[]): ChatModelMessage[] {
355
+ const result: ChatModelMessage[] = [];
356
+
357
+ for (const message of messages) {
358
+ if (Array.isArray(message.content)) {
359
+ if (message.role === "user") {
360
+ const cleaned = cleanContent(message.content);
361
+ if (cleaned.length > 0) result.push({ ...message, content: cleaned });
362
+ } else if (message.role === "assistant") {
363
+ const cleaned = cleanContent(message.content);
364
+ if (cleaned.length > 0) result.push({ ...message, content: cleaned });
365
+ } else if (message.role === "tool") {
366
+ const cleaned = cleanContent(message.content);
367
+ if (cleaned.length > 0) result.push({ ...message, content: cleaned });
368
+ }
369
+ continue;
370
+ }
371
+
372
+ if (hasValidContent(message)) {
373
+ result.push(message);
374
+ }
375
+ }
376
+
377
+ return result;
378
+ }
379
+
380
+ function filterValidMessages(messages: ChatModelMessage[]): ChatModelMessage[] {
381
+ return messages.filter((message) => {
382
+ const content = message.content;
383
+ if (content === undefined || content === null) return false;
384
+ if (typeof content === "string") return content.trim().length > 0;
385
+ if (Array.isArray(content)) return content.length > 0;
386
+ return true;
387
+ });
388
+ }
389
+
390
+ export function prepareModelMessagesFromUiMessages(messages: ChatUiMessage[]): ChatModelMessage[] {
391
+ const validMessages = messages.filter((message) =>
392
+ message && typeof message === "object" && "role" in message
393
+ );
394
+ const normalizedMessages = normalizeMessageFilePartMediaTypes(validMessages);
395
+ const strippedPendingToolMessages = stripPendingToolParts(normalizedMessages);
396
+ const rewrittenMessages = rewriteUnsupportedFilePartsAsAnnotations(strippedPendingToolMessages);
397
+ const modelMessages = convertUiMessagesToModelMessages(rewrittenMessages);
398
+ const patchedMessages = ensureToolCallInputs(dedupeToolHistory(modelMessages));
399
+ const sanitized = sanitizeModelMessages(patchedMessages);
400
+ const masked = maskOldToolOutputs(sanitized);
401
+ const compacted = enforceTokenBudget(masked);
402
+ const filtered = filterValidMessages(compacted);
403
+ return repairToolPairs(filtered);
404
+ }
405
+
181
406
  function buildToolCallMap(messages: ChatModelMessage[]): Map<string, ToolCallInfo> {
182
407
  const map = new Map<string, ToolCallInfo>();
183
408
 
@@ -1,3 +1,3 @@
1
1
  // Keep in sync with deno.json version.
2
2
  // scripts/release.ts updates this constant during releases.
3
- export const VERSION = "0.1.291";
3
+ export const VERSION = "0.1.293";