Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 20s
Build & Deploy / 🧪 QA (push) Failing after 34s
Build & Deploy / 🏗️ Build (push) Has started running
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Smoke Test (push) Has been cancelled
Build & Deploy / ⚡ Lighthouse (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
1 line
20 KiB
Plaintext
1 line
20 KiB
Plaintext
{"version":3,"file":"messageTruncation.js","sources":["../../../../src/tracing/ai/messageTruncation.ts"],"sourcesContent":["/**\n * Default maximum size in bytes for GenAI messages.\n * Messages exceeding this limit will be truncated.\n */\nexport const DEFAULT_GEN_AI_MESSAGES_BYTE_LIMIT = 20000;\n\n/**\n * Message format used by OpenAI and Anthropic APIs.\n */\ntype ContentMessage = {\n [key: string]: unknown;\n content: string;\n};\n\n/**\n * Message format used by OpenAI and Anthropic APIs for media.\n */\ntype ContentArrayMessage = {\n [key: string]: unknown;\n content: {\n [key: string]: unknown;\n type: string;\n }[];\n};\n\n/**\n * Inline media content source, with a potentially very large base64\n * blob or data: uri.\n */\ntype ContentMedia = Record<string, unknown> &\n (\n | {\n media_type: string;\n data: string;\n }\n | {\n image_url: `data:${string}`;\n }\n | {\n type: 'blob' | 'base64';\n content: string;\n }\n | {\n b64_json: string;\n }\n | {\n uri: `data:${string}`;\n }\n );\n\n/**\n * Message format used by Google GenAI API.\n * Parts can be strings or objects with a text property.\n */\ntype PartsMessage = {\n [key: string]: unknown;\n parts: Array<TextPart | MediaPart>;\n};\n\n/**\n * A part in a Google GenAI message that contains text.\n */\ntype TextPart = string | { text: string };\n\n/**\n * A part in a Google GenAI that contains media.\n */\ntype MediaPart = {\n type: string;\n content: string;\n};\n\n/**\n * Calculate the UTF-8 byte length of a string.\n */\nconst utf8Bytes = (text: string): number => {\n return new TextEncoder().encode(text).length;\n};\n\n/**\n * Calculate the UTF-8 byte length of a value's JSON representation.\n */\nconst jsonBytes = (value: unknown): number => {\n return utf8Bytes(JSON.stringify(value));\n};\n\n/**\n * Truncate a string to fit within maxBytes when encoded as UTF-8.\n * Uses binary search for efficiency with multi-byte characters.\n *\n * @param text - The string to truncate\n * @param maxBytes - Maximum byte length (UTF-8 encoded)\n * @returns Truncated string that fits within maxBytes\n */\nfunction truncateTextByBytes(text: string, maxBytes: number): string {\n if (utf8Bytes(text) <= maxBytes) {\n return text;\n }\n\n let low = 0;\n let high = text.length;\n let bestFit = '';\n\n while (low <= high) {\n const mid = Math.floor((low + high) / 2);\n const candidate = text.slice(0, mid);\n const byteSize = utf8Bytes(candidate);\n\n if (byteSize <= maxBytes) {\n bestFit = candidate;\n low = mid + 1;\n } else {\n high = mid - 1;\n }\n }\n\n return bestFit;\n}\n\n/**\n * Extract text content from a Google GenAI message part.\n * Parts are either plain strings or objects with a text property.\n *\n * @returns The text content\n */\nfunction getPartText(part: TextPart | MediaPart): string {\n if (typeof part === 'string') {\n return part;\n }\n if ('text' in part) return part.text;\n return '';\n}\n\n/**\n * Create a new part with updated text content while preserving the original structure.\n *\n * @param part - Original part (string or object)\n * @param text - New text content\n * @returns New part with updated text\n */\nfunction withPartText(part: TextPart | MediaPart, text: string): TextPart {\n if (typeof part === 'string') {\n return text;\n }\n return { ...part, text };\n}\n\n/**\n * Check if a message has the OpenAI/Anthropic content format.\n */\nfunction isContentMessage(message: unknown): message is ContentMessage {\n return (\n message !== null &&\n typeof message === 'object' &&\n 'content' in message &&\n typeof (message as ContentMessage).content === 'string'\n );\n}\n\n/**\n * Check if a message has the OpenAI/Anthropic content array format.\n */\nfunction isContentArrayMessage(message: unknown): message is ContentArrayMessage {\n return message !== null && typeof message === 'object' && 'content' in message && Array.isArray(message.content);\n}\n\n/**\n * Check if a content part is an OpenAI/Anthropic media source\n */\nfunction isContentMedia(part: unknown): part is ContentMedia {\n if (!part || typeof part !== 'object') return false;\n\n return (\n isContentMediaSource(part) ||\n hasInlineData(part) ||\n ('media_type' in part && typeof part.media_type === 'string' && 'data' in part) ||\n ('image_url' in part && typeof part.image_url === 'string' && part.image_url.startsWith('data:')) ||\n ('type' in part && (part.type === 'blob' || part.type === 'base64')) ||\n 'b64_json' in part ||\n ('type' in part && 'result' in part && part.type === 'image_generation') ||\n ('uri' in part && typeof part.uri === 'string' && part.uri.startsWith('data:'))\n );\n}\nfunction isContentMediaSource(part: NonNullable<unknown>): boolean {\n return 'type' in part && typeof part.type === 'string' && 'source' in part && isContentMedia(part.source);\n}\nfunction hasInlineData(part: NonNullable<unknown>): part is { inlineData: { data?: string } } {\n return (\n 'inlineData' in part &&\n !!part.inlineData &&\n typeof part.inlineData === 'object' &&\n 'data' in part.inlineData &&\n typeof part.inlineData.data === 'string'\n );\n}\n\n/**\n * Check if a message has the Google GenAI parts format.\n */\nfunction isPartsMessage(message: unknown): message is PartsMessage {\n return (\n message !== null &&\n typeof message === 'object' &&\n 'parts' in message &&\n Array.isArray((message as PartsMessage).parts) &&\n (message as PartsMessage).parts.length > 0\n );\n}\n\n/**\n * Truncate a message with `content: string` format (OpenAI/Anthropic).\n *\n * @param message - Message with content property\n * @param maxBytes - Maximum byte limit\n * @returns Array with truncated message, or empty array if it doesn't fit\n */\nfunction truncateContentMessage(message: ContentMessage, maxBytes: number): unknown[] {\n // Calculate overhead (message structure without content)\n const emptyMessage = { ...message, content: '' };\n const overhead = jsonBytes(emptyMessage);\n const availableForContent = maxBytes - overhead;\n\n if (availableForContent <= 0) {\n return [];\n }\n\n const truncatedContent = truncateTextByBytes(message.content, availableForContent);\n return [{ ...message, content: truncatedContent }];\n}\n\n/**\n * Truncate a message with `parts: [...]` format (Google GenAI).\n * Keeps as many complete parts as possible, only truncating the first part if needed.\n *\n * @param message - Message with parts array\n * @param maxBytes - Maximum byte limit\n * @returns Array with truncated message, or empty array if it doesn't fit\n */\nfunction truncatePartsMessage(message: PartsMessage, maxBytes: number): unknown[] {\n const { parts } = message;\n\n // Calculate overhead by creating empty text parts\n const emptyParts = parts.map(part => withPartText(part, ''));\n const overhead = jsonBytes({ ...message, parts: emptyParts });\n let remainingBytes = maxBytes - overhead;\n\n if (remainingBytes <= 0) {\n return [];\n }\n\n // Include parts until we run out of space\n const includedParts: (TextPart | MediaPart)[] = [];\n\n for (const part of parts) {\n const text = getPartText(part);\n const textSize = utf8Bytes(text);\n\n if (textSize <= remainingBytes) {\n // Part fits: include it as-is\n includedParts.push(part);\n remainingBytes -= textSize;\n } else if (includedParts.length === 0) {\n // First part doesn't fit: truncate it\n const truncated = truncateTextByBytes(text, remainingBytes);\n if (truncated) {\n includedParts.push(withPartText(part, truncated));\n }\n break;\n } else {\n // Subsequent part doesn't fit: stop here\n break;\n }\n }\n\n /* c8 ignore start\n * for type safety only, algorithm guarantees SOME text included */\n if (includedParts.length <= 0) {\n return [];\n } else {\n /* c8 ignore stop */\n return [{ ...message, parts: includedParts }];\n }\n}\n\n/**\n * Truncate a single message to fit within maxBytes.\n *\n * Supports two message formats:\n * - OpenAI/Anthropic: `{ ..., content: string }`\n * - Google GenAI: `{ ..., parts: Array<string | {text: string} | non-text> }`\n *\n * @param message - The message to truncate\n * @param maxBytes - Maximum byte limit for the message\n * @returns Array containing the truncated message, or empty array if truncation fails\n */\nfunction truncateSingleMessage(message: unknown, maxBytes: number): unknown[] {\n if (!message) return [];\n\n // Handle plain strings (e.g., embeddings input)\n if (typeof message === 'string') {\n const truncated = truncateTextByBytes(message, maxBytes);\n return truncated ? [truncated] : [];\n }\n\n if (typeof message !== 'object') {\n return [];\n }\n\n if (isContentMessage(message)) {\n return truncateContentMessage(message, maxBytes);\n }\n\n if (isPartsMessage(message)) {\n return truncatePartsMessage(message, maxBytes);\n }\n\n // Unknown message format: cannot truncate safely\n return [];\n}\n\nconst REMOVED_STRING = '[Filtered]';\n\nconst MEDIA_FIELDS = ['image_url', 'data', 'content', 'b64_json', 'result', 'uri'] as const;\n\nfunction stripInlineMediaFromSingleMessage(part: ContentMedia): ContentMedia {\n const strip = { ...part };\n if (isContentMedia(strip.source)) {\n strip.source = stripInlineMediaFromSingleMessage(strip.source);\n }\n // google genai inline data blob objects\n if (hasInlineData(part)) {\n strip.inlineData = { ...part.inlineData, data: REMOVED_STRING };\n }\n for (const field of MEDIA_FIELDS) {\n if (typeof strip[field] === 'string') strip[field] = REMOVED_STRING;\n }\n return strip;\n}\n\n/**\n * Strip the inline media from message arrays.\n *\n * This returns a stripped message. We do NOT want to mutate the data in place,\n * because of course we still want the actual API/client to handle the media.\n */\nfunction stripInlineMediaFromMessages(messages: unknown[]): unknown[] {\n const stripped = messages.map(message => {\n let newMessage: Record<string, unknown> | undefined = undefined;\n if (!!message && typeof message === 'object') {\n if (isContentArrayMessage(message)) {\n newMessage = {\n ...message,\n content: stripInlineMediaFromMessages(message.content),\n };\n } else if ('content' in message && isContentMedia(message.content)) {\n newMessage = {\n ...message,\n content: stripInlineMediaFromSingleMessage(message.content),\n };\n }\n if (isPartsMessage(message)) {\n newMessage = {\n // might have to strip content AND parts\n ...(newMessage ?? message),\n parts: stripInlineMediaFromMessages(message.parts),\n };\n }\n if (isContentMedia(newMessage)) {\n newMessage = stripInlineMediaFromSingleMessage(newMessage);\n } else if (isContentMedia(message)) {\n newMessage = stripInlineMediaFromSingleMessage(message);\n }\n }\n return newMessage ?? message;\n });\n return stripped;\n}\n\n/**\n * Truncate an array of messages to fit within a byte limit.\n *\n * Strategy:\n * - Always keeps only the last (newest) message\n * - Strips inline media from the message\n * - Truncates the message content if it exceeds the byte limit\n *\n * @param messages - Array of messages to truncate\n * @param maxBytes - Maximum total byte limit for the message\n * @returns Array containing only the last message (possibly truncated)\n *\n * @example\n * ```ts\n * const messages = [msg1, msg2, msg3, msg4]; // newest is msg4\n * const truncated = truncateMessagesByBytes(messages, 10000);\n * // Returns [msg4] (truncated if needed)\n * ```\n */\nfunction truncateMessagesByBytes(messages: unknown[], maxBytes: number): unknown[] {\n // Early return for empty or invalid input\n if (!Array.isArray(messages) || messages.length === 0) {\n return messages;\n }\n\n // Always keep only the last message\n const lastMessage = messages[messages.length - 1];\n\n // Strip inline media from the single message\n const stripped = stripInlineMediaFromMessages([lastMessage]);\n const strippedMessage = stripped[0];\n\n // Check if it fits\n const messageBytes = jsonBytes(strippedMessage);\n if (messageBytes <= maxBytes) {\n return stripped;\n }\n\n // Truncate the single message if needed\n return truncateSingleMessage(strippedMessage, maxBytes);\n}\n\n/**\n * Truncate GenAI messages using the default byte limit.\n *\n * Convenience wrapper around `truncateMessagesByBytes` with the default limit.\n *\n * @param messages - Array of messages to truncate\n * @returns Truncated array of messages\n */\nexport function truncateGenAiMessages(messages: unknown[]): unknown[] {\n return truncateMessagesByBytes(messages, DEFAULT_GEN_AI_MESSAGES_BYTE_LIMIT);\n}\n\n/**\n * Truncate GenAI string input using the default byte limit.\n *\n * @param input - The string to truncate\n * @returns Truncated string\n */\nexport function truncateGenAiStringInput(input: string): string {\n return truncateTextByBytes(input, DEFAULT_GEN_AI_MESSAGES_BYTE_LIMIT);\n}\n"],"names":[],"mappings":"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"} |