veryfront 0.1.291 → 0.1.292

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.292",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "workspace": [
@@ -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.292";
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.292";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veryfront",
3
- "version": "0.1.291",
3
+ "version": "0.1.292",
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.292",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "workspace": [
@@ -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.292";