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 +1 -1
- package/esm/src/chat/message-prep.d.ts +7 -1
- package/esm/src/chat/message-prep.d.ts.map +1 -1
- package/esm/src/chat/message-prep.js +192 -1
- package/esm/src/utils/version-constant.d.ts +1 -1
- package/esm/src/utils/version-constant.js +1 -1
- package/package.json +1 -1
- package/src/deno.js +1 -1
- package/src/src/chat/message-prep.ts +226 -1
- package/src/src/utils/version-constant.ts +1 -1
package/esm/deno.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import type
|
|
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;
|
|
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.
|
|
1
|
+
export declare const VERSION = "0.1.292";
|
|
2
2
|
//# sourceMappingURL=version-constant.d.ts.map
|
package/package.json
CHANGED
package/src/deno.js
CHANGED
|
@@ -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
|
|
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
|
|