From 29e4eda262a0e83bf5ad16d17c46fda7dd3c8636 Mon Sep 17 00:00:00 2001 From: naaa760 Date: Sun, 5 Oct 2025 13:29:50 +0530 Subject: [PATCH 01/10] add message truncation utility for gen_ai byte limits --- .../core/src/utils/ai/messageTruncation.ts | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 packages/core/src/utils/ai/messageTruncation.ts diff --git a/packages/core/src/utils/ai/messageTruncation.ts b/packages/core/src/utils/ai/messageTruncation.ts new file mode 100644 index 000000000000..e8fb61153250 --- /dev/null +++ b/packages/core/src/utils/ai/messageTruncation.ts @@ -0,0 +1,37 @@ +export function getByteSize(str: string): number { + return new TextEncoder().encode(str).length; +} + +export function truncateMessagesByBytes(messages: unknown[], maxBytes: number): unknown[] { + if (!Array.isArray(messages) || messages.length === 0) { + return messages; + } + + const messagesJson = JSON.stringify(messages); + const totalBytes = getByteSize(messagesJson); + + if (totalBytes <= maxBytes) { + return messages; + } + + let truncatedMessages = [...messages]; + + while (truncatedMessages.length > 0) { + const truncatedJson = JSON.stringify(truncatedMessages); + const truncatedBytes = getByteSize(truncatedJson); + + if (truncatedBytes <= maxBytes) { + break; + } + + truncatedMessages.shift(); + } + + return truncatedMessages; +} + +export const DEFAULT_GEN_AI_MESSAGES_BYTE_LIMIT = 100000; + +export function truncateGenAiMessages(messages: unknown[]): unknown[] { + return truncateMessagesByBytes(messages, DEFAULT_GEN_AI_MESSAGES_BYTE_LIMIT); +} From bf1003eee02fe3e9b823e48feee600a40483b710 Mon Sep 17 00:00:00 2001 From: naaa760 Date: Sun, 5 Oct 2025 13:30:51 +0530 Subject: [PATCH 02/10] packages/core/src/utils/ai/messageTruncation.ts --- packages/core/src/utils/openai/index.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/core/src/utils/openai/index.ts b/packages/core/src/utils/openai/index.ts index 4ecfad625062..58d760875a4f 100644 --- a/packages/core/src/utils/openai/index.ts +++ b/packages/core/src/utils/openai/index.ts @@ -19,6 +19,7 @@ import { GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE, GEN_AI_SYSTEM_ATTRIBUTE, } from '../ai/gen-ai-attributes'; +import { truncateGenAiMessages } from '../ai/messageTruncation'; import { OPENAI_INTEGRATION_NAME } from './constants'; import { instrumentStream } from './streaming'; import type { @@ -188,13 +189,16 @@ function addResponseAttributes(span: Span, result: unknown, recordOutputs?: bool } } -// Extract and record AI request inputs, if present. This is intentionally separate from response attributes. function addRequestAttributes(span: Span, params: Record): void { if ('messages' in params) { - span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(params.messages) }); + const messages = params.messages; + const truncatedMessages = truncateGenAiMessages(messages as unknown[]); + span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(truncatedMessages) }); } if ('input' in params) { - span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(params.input) }); + const input = params.input; + const truncatedInput = truncateGenAiMessages(input as unknown[]); + span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(truncatedInput) }); } } From c50ad1b6581fa37116e86d4b265425be9c53b4d6 Mon Sep 17 00:00:00 2001 From: naaa760 Date: Sun, 5 Oct 2025 13:31:32 +0530 Subject: [PATCH 03/10] apply message truncation to Google GenAI gen_ai.request.messages --- packages/core/src/utils/google-genai/index.ts | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/packages/core/src/utils/google-genai/index.ts b/packages/core/src/utils/google-genai/index.ts index 20e6e2a53606..66a979dcaca5 100644 --- a/packages/core/src/utils/google-genai/index.ts +++ b/packages/core/src/utils/google-genai/index.ts @@ -22,6 +22,7 @@ import { GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, } from '../ai/gen-ai-attributes'; +import { truncateGenAiMessages } from '../ai/messageTruncation'; import { buildMethodPath, getFinalOperationName, getSpanOperation } from '../ai/utils'; import { handleCallbackErrors } from '../handleCallbackErrors'; import { CHAT_PATH, CHATS_CREATE_METHOD, GOOGLE_GENAI_SYSTEM_NAME } from './constants'; @@ -128,25 +129,23 @@ function extractRequestAttributes( return attributes; } -/** - * Add private request attributes to spans. - * This is only recorded if recordInputs is true. - * Handles different parameter formats for different Google GenAI methods. - */ function addPrivateRequestAttributes(span: Span, params: Record): void { - // For models.generateContent: ContentListUnion: Content | Content[] | PartUnion | PartUnion[] if ('contents' in params) { - span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(params.contents) }); + const contents = params.contents; + const truncatedContents = truncateGenAiMessages(contents as unknown[]); + span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(truncatedContents) }); } - // For chat.sendMessage: message can be string or Part[] if ('message' in params) { - span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(params.message) }); + const message = params.message; + const truncatedMessage = truncateGenAiMessages(message as unknown[]); + span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(truncatedMessage) }); } - // For chats.create: history contains the conversation history if ('history' in params) { - span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(params.history) }); + const history = params.history; + const truncatedHistory = truncateGenAiMessages(history as unknown[]); + span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(truncatedHistory) }); } } From 18265ac02ca07c374cdd7c08d74ee1aa2b9d8ab6 Mon Sep 17 00:00:00 2001 From: naaa760 Date: Sun, 5 Oct 2025 13:32:04 +0530 Subject: [PATCH 04/10] apply message truncation to Anthropic gen_ai.request.messages --- packages/core/src/utils/anthropic-ai/index.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/core/src/utils/anthropic-ai/index.ts b/packages/core/src/utils/anthropic-ai/index.ts index 8e77dd76b34e..86512948bcd4 100644 --- a/packages/core/src/utils/anthropic-ai/index.ts +++ b/packages/core/src/utils/anthropic-ai/index.ts @@ -23,6 +23,7 @@ import { GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE, GEN_AI_SYSTEM_ATTRIBUTE, } from '../ai/gen-ai-attributes'; +import { truncateGenAiMessages } from '../ai/messageTruncation'; import { buildMethodPath, getFinalOperationName, getSpanOperation, setTokenUsageAttributes } from '../ai/utils'; import { handleCallbackErrors } from '../handleCallbackErrors'; import { instrumentAsyncIterableStream, instrumentMessageStream } from './streaming'; @@ -71,16 +72,16 @@ function extractRequestAttributes(args: unknown[], methodPath: string): Record): void { if ('messages' in params) { - span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(params.messages) }); + const messages = params.messages; + const truncatedMessages = truncateGenAiMessages(messages as unknown[]); + span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(truncatedMessages) }); } if ('input' in params) { - span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(params.input) }); + const input = params.input; + const truncatedInput = truncateGenAiMessages(input as unknown[]); + span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(truncatedInput) }); } if ('prompt' in params) { span.setAttributes({ [GEN_AI_PROMPT_ATTRIBUTE]: JSON.stringify(params.prompt) }); From f1b468fb0f088f0b7e42b88c6ff7ec50d34cdc61 Mon Sep 17 00:00:00 2001 From: naaa760 Date: Sun, 5 Oct 2025 13:32:54 +0530 Subject: [PATCH 05/10] apply message truncation to Vercel AI prompts and messages --- packages/core/src/utils/vercel-ai/index.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/core/src/utils/vercel-ai/index.ts b/packages/core/src/utils/vercel-ai/index.ts index 912dcaee3bc4..a367c12641ec 100644 --- a/packages/core/src/utils/vercel-ai/index.ts +++ b/packages/core/src/utils/vercel-ai/index.ts @@ -3,6 +3,7 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from ' import type { Event } from '../../types-hoist/event'; import type { Span, SpanAttributes, SpanAttributeValue, SpanJSON, SpanOrigin } from '../../types-hoist/span'; import { spanToJSON } from '../spanUtils'; +import { truncateGenAiMessages } from '../ai/messageTruncation'; import { toolCallSpanMap } from './constants'; import type { TokenSummary } from './types'; import { accumulateTokensForParent, applyAccumulatedTokens } from './utils'; @@ -187,7 +188,13 @@ function processGenerateSpan(span: Span, name: string, attributes: SpanAttribute } if (attributes[AI_PROMPT_ATTRIBUTE]) { - span.setAttribute('gen_ai.prompt', attributes[AI_PROMPT_ATTRIBUTE]); + const prompt = attributes[AI_PROMPT_ATTRIBUTE]; + if (Array.isArray(prompt)) { + const truncatedPrompt = truncateGenAiMessages(prompt); + span.setAttribute('gen_ai.prompt', JSON.stringify(truncatedPrompt)); + } else { + span.setAttribute('gen_ai.prompt', prompt); + } } if (attributes[AI_MODEL_ID_ATTRIBUTE] && !attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]) { span.setAttribute(GEN_AI_RESPONSE_MODEL_ATTRIBUTE, attributes[AI_MODEL_ID_ATTRIBUTE]); From 50d1d6d6c337fcc28b18c03389328ed150c9f07a Mon Sep 17 00:00:00 2001 From: naaa760 Date: Tue, 7 Oct 2025 11:02:06 +0530 Subject: [PATCH 06/10] add message truncation utility with 20KB limit and performance optimization --- .../core/src/utils/ai/messageTruncation.ts | 74 +++++++++++++++---- 1 file changed, 60 insertions(+), 14 deletions(-) diff --git a/packages/core/src/utils/ai/messageTruncation.ts b/packages/core/src/utils/ai/messageTruncation.ts index e8fb61153250..f72485cd8a40 100644 --- a/packages/core/src/utils/ai/messageTruncation.ts +++ b/packages/core/src/utils/ai/messageTruncation.ts @@ -1,5 +1,33 @@ +export const DEFAULT_GEN_AI_MESSAGES_BYTE_LIMIT = 20000; + export function getByteSize(str: string): number { - return new TextEncoder().encode(str).length; + let bytes = 0; + for (let i = 0; i < str.length; i++) { + const code = str.charCodeAt(i); + if (code < 0x80) { + bytes += 1; + } else if (code < 0x800) { + bytes += 2; + } else if (code < 0xd800 || code >= 0xe000) { + bytes += 3; + } else { + i++; + bytes += 4; + } + } + return bytes; +} + +function truncateStringByBytes(str: string, maxBytes: number): string { + if (getByteSize(str) <= maxBytes) { + return str; + } + + let truncatedStr = str; + while (getByteSize(truncatedStr) > maxBytes && truncatedStr.length > 0) { + truncatedStr = truncatedStr.slice(0, -1); + } + return truncatedStr; } export function truncateMessagesByBytes(messages: unknown[], maxBytes: number): unknown[] { @@ -7,30 +35,48 @@ export function truncateMessagesByBytes(messages: unknown[], maxBytes: number): return messages; } - const messagesJson = JSON.stringify(messages); - const totalBytes = getByteSize(messagesJson); + let currentSize = getByteSize(JSON.stringify(messages)); - if (totalBytes <= maxBytes) { + if (currentSize <= maxBytes) { return messages; } - let truncatedMessages = [...messages]; + let startIndex = 0; + + while (startIndex < messages.length - 1 && currentSize > maxBytes) { + const messageSize = getByteSize(JSON.stringify(messages[startIndex])); + currentSize -= messageSize; + startIndex++; + } - while (truncatedMessages.length > 0) { - const truncatedJson = JSON.stringify(truncatedMessages); - const truncatedBytes = getByteSize(truncatedJson); + const remainingMessages = messages.slice(startIndex); - if (truncatedBytes <= maxBytes) { - break; - } + if (remainingMessages.length === 1) { + const singleMessage = remainingMessages[0]; + const singleMessageSize = getByteSize(JSON.stringify(singleMessage)); - truncatedMessages.shift(); + if (singleMessageSize > maxBytes) { + if (typeof singleMessage === 'object' && singleMessage !== null && 'content' in singleMessage && typeof (singleMessage as { content: unknown }).content === 'string') { + const originalContent = (singleMessage as { content: string }).content; + const messageWithoutContent = { ...singleMessage, content: '' }; + const otherMessagePartsSize = getByteSize(JSON.stringify(messageWithoutContent)); + const availableContentBytes = maxBytes - otherMessagePartsSize; + + if (availableContentBytes <= 0) { + return []; + } + + const truncatedContent = truncateStringByBytes(originalContent, availableContentBytes); + return [{ ...singleMessage, content: truncatedContent }]; + } else { + return []; + } + } } - return truncatedMessages; + return remainingMessages; } -export const DEFAULT_GEN_AI_MESSAGES_BYTE_LIMIT = 100000; export function truncateGenAiMessages(messages: unknown[]): unknown[] { return truncateMessagesByBytes(messages, DEFAULT_GEN_AI_MESSAGES_BYTE_LIMIT); From 7df99937b21e19c7e6936a605cfaaa24eb5ff7f1 Mon Sep 17 00:00:00 2001 From: naaa760 Date: Tue, 7 Oct 2025 11:03:18 +0530 Subject: [PATCH 07/10] apply message truncation to Google GenAI with proper type handling --- packages/core/src/utils/google-genai/index.ts | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/packages/core/src/utils/google-genai/index.ts b/packages/core/src/utils/google-genai/index.ts index 66a979dcaca5..610806b52626 100644 --- a/packages/core/src/utils/google-genai/index.ts +++ b/packages/core/src/utils/google-genai/index.ts @@ -132,20 +132,32 @@ function extractRequestAttributes( function addPrivateRequestAttributes(span: Span, params: Record): void { if ('contents' in params) { const contents = params.contents; - const truncatedContents = truncateGenAiMessages(contents as unknown[]); - span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(truncatedContents) }); + if (Array.isArray(contents)) { + const truncatedContents = truncateGenAiMessages(contents); + span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(truncatedContents) }); + } else { + span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(contents) }); + } } if ('message' in params) { const message = params.message; - const truncatedMessage = truncateGenAiMessages(message as unknown[]); - span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(truncatedMessage) }); + if (Array.isArray(message)) { + const truncatedMessage = truncateGenAiMessages(message); + span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(truncatedMessage) }); + } else { + span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(message) }); + } } if ('history' in params) { const history = params.history; - const truncatedHistory = truncateGenAiMessages(history as unknown[]); - span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(truncatedHistory) }); + if (Array.isArray(history)) { + const truncatedHistory = truncateGenAiMessages(history); + span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(truncatedHistory) }); + } else { + span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(history) }); + } } } From c91de63739ab79a773c9a8ea9591cda92764c273 Mon Sep 17 00:00:00 2001 From: naaa760 Date: Wed, 8 Oct 2025 09:23:56 +0530 Subject: [PATCH 08/10] fix(ai): add type safety for message truncation --- packages/core/src/utils/anthropic-ai/index.ts | 16 ++++++++++++---- packages/core/src/utils/openai/index.ts | 16 ++++++++++++---- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/packages/core/src/utils/anthropic-ai/index.ts b/packages/core/src/utils/anthropic-ai/index.ts index 86512948bcd4..4dc4df27dcaf 100644 --- a/packages/core/src/utils/anthropic-ai/index.ts +++ b/packages/core/src/utils/anthropic-ai/index.ts @@ -75,13 +75,21 @@ function extractRequestAttributes(args: unknown[], methodPath: string): Record): void { if ('messages' in params) { const messages = params.messages; - const truncatedMessages = truncateGenAiMessages(messages as unknown[]); - span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(truncatedMessages) }); + if (Array.isArray(messages)) { + const truncatedMessages = truncateGenAiMessages(messages); + span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(truncatedMessages) }); + } else { + span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(messages) }); + } } if ('input' in params) { const input = params.input; - const truncatedInput = truncateGenAiMessages(input as unknown[]); - span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(truncatedInput) }); + if (Array.isArray(input)) { + const truncatedInput = truncateGenAiMessages(input); + span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(truncatedInput) }); + } else { + span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(input) }); + } } if ('prompt' in params) { span.setAttributes({ [GEN_AI_PROMPT_ATTRIBUTE]: JSON.stringify(params.prompt) }); diff --git a/packages/core/src/utils/openai/index.ts b/packages/core/src/utils/openai/index.ts index 58d760875a4f..7613dde5038b 100644 --- a/packages/core/src/utils/openai/index.ts +++ b/packages/core/src/utils/openai/index.ts @@ -192,13 +192,21 @@ function addResponseAttributes(span: Span, result: unknown, recordOutputs?: bool function addRequestAttributes(span: Span, params: Record): void { if ('messages' in params) { const messages = params.messages; - const truncatedMessages = truncateGenAiMessages(messages as unknown[]); - span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(truncatedMessages) }); + if (Array.isArray(messages)) { + const truncatedMessages = truncateGenAiMessages(messages); + span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(truncatedMessages) }); + } else { + span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(messages) }); + } } if ('input' in params) { const input = params.input; - const truncatedInput = truncateGenAiMessages(input as unknown[]); - span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(truncatedInput) }); + if (Array.isArray(input)) { + const truncatedInput = truncateGenAiMessages(input); + span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(truncatedInput) }); + } else { + span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(input) }); + } } } From 903de8a45323b8a265a2e6f451446916ff8ba001 Mon Sep 17 00:00:00 2001 From: Rola Abuhasna Date: Fri, 10 Oct 2025 13:39:11 +0200 Subject: [PATCH 09/10] Update messageTruncation.ts --- .../core/src/utils/ai/messageTruncation.ts | 123 ++++++++++++------ 1 file changed, 82 insertions(+), 41 deletions(-) diff --git a/packages/core/src/utils/ai/messageTruncation.ts b/packages/core/src/utils/ai/messageTruncation.ts index f72485cd8a40..87c1a0d1204b 100644 --- a/packages/core/src/utils/ai/messageTruncation.ts +++ b/packages/core/src/utils/ai/messageTruncation.ts @@ -1,83 +1,124 @@ export const DEFAULT_GEN_AI_MESSAGES_BYTE_LIMIT = 20000; +/** + * Calculates the UTF-8 byte size of a string. + */ export function getByteSize(str: string): number { - let bytes = 0; - for (let i = 0; i < str.length; i++) { - const code = str.charCodeAt(i); - if (code < 0x80) { - bytes += 1; - } else if (code < 0x800) { - bytes += 2; - } else if (code < 0xd800 || code >= 0xe000) { - bytes += 3; - } else { - i++; - bytes += 4; - } - } - return bytes; + return new TextEncoder().encode(str).length; } +/** + * Truncates a string to fit within maxBytes using binary search. + */ function truncateStringByBytes(str: string, maxBytes: number): string { if (getByteSize(str) <= maxBytes) { return str; } - let truncatedStr = str; - while (getByteSize(truncatedStr) > maxBytes && truncatedStr.length > 0) { - truncatedStr = truncatedStr.slice(0, -1); + // Binary search for the longest substring that fits + let left = 0; + let right = str.length; + let result = ''; + + while (left <= right) { + const mid = Math.floor((left + right) / 2); + const candidate = str.slice(0, mid); + const candidateSize = getByteSize(candidate); + + if (candidateSize <= maxBytes) { + result = candidate; + left = mid + 1; + } else { + right = mid - 1; + } } - return truncatedStr; + + return result; } +/** + * Truncates messages array using binary search to find optimal starting point. + * Removes oldest messages first until the array fits within maxBytes + * It also tries to truncate the latest message's content if it's too large. + * + */ export function truncateMessagesByBytes(messages: unknown[], maxBytes: number): unknown[] { if (!Array.isArray(messages) || messages.length === 0) { return messages; } - let currentSize = getByteSize(JSON.stringify(messages)); + const fullSize = getByteSize(JSON.stringify(messages)); - if (currentSize <= maxBytes) { + if (fullSize <= maxBytes) { return messages; } - let startIndex = 0; + // Binary search for the minimum startIndex where remaining messages fit (works for single or multiple messages) + let left = 0; + let right = messages.length - 1; + let bestStartIndex = messages.length; + + while (left <= right) { + const mid = Math.floor((left + right) / 2); + const remainingMessages = messages.slice(mid); + const remainingSize = getByteSize(JSON.stringify(remainingMessages)); - while (startIndex < messages.length - 1 && currentSize > maxBytes) { - const messageSize = getByteSize(JSON.stringify(messages[startIndex])); - currentSize -= messageSize; - startIndex++; + if (remainingSize <= maxBytes) { + bestStartIndex = mid; + right = mid - 1; // Try to keep more messages + } else { + // If we're down to a single message and it doesn't fit, break and handle content truncation + if (remainingMessages.length === 1) { + bestStartIndex = mid; // Use this single message + break; + } + left = mid + 1; // Need to remove more messages + } } - const remainingMessages = messages.slice(startIndex); + const remainingMessages = messages.slice(bestStartIndex); + // SPECIAL CASE: Single message handling (either started with 1, or reduced to 1 after binary search) if (remainingMessages.length === 1) { const singleMessage = remainingMessages[0]; const singleMessageSize = getByteSize(JSON.stringify(singleMessage)); - if (singleMessageSize > maxBytes) { - if (typeof singleMessage === 'object' && singleMessage !== null && 'content' in singleMessage && typeof (singleMessage as { content: unknown }).content === 'string') { - const originalContent = (singleMessage as { content: string }).content; - const messageWithoutContent = { ...singleMessage, content: '' }; - const otherMessagePartsSize = getByteSize(JSON.stringify(messageWithoutContent)); - const availableContentBytes = maxBytes - otherMessagePartsSize; - - if (availableContentBytes <= 0) { - return []; - } + // If single message fits, return it + if (singleMessageSize <= maxBytes) { + return remainingMessages; + } - const truncatedContent = truncateStringByBytes(originalContent, availableContentBytes); - return [{ ...singleMessage, content: truncatedContent }]; - } else { + // Single message is too large, try to truncate its content + if ( + typeof singleMessage === 'object' && + singleMessage !== null && + 'content' in singleMessage && + typeof (singleMessage as { content: unknown }).content === 'string' + ) { + const originalContent = (singleMessage as { content: string }).content; + const messageWithoutContent = { ...singleMessage, content: '' }; + const otherMessagePartsSize = getByteSize(JSON.stringify(messageWithoutContent)); + const availableContentBytes = maxBytes - otherMessagePartsSize; + + if (availableContentBytes <= 0) { return []; } + + const truncatedContent = truncateStringByBytes(originalContent, availableContentBytes); + return [{ ...singleMessage, content: truncatedContent }]; + } else { + return []; } } + // Multiple messages remain and fit within limit return remainingMessages; } - +/** + * Truncates gen_ai messages to fit within the default byte limit. + * This is a convenience wrapper around truncateMessagesByBytes. + */ export function truncateGenAiMessages(messages: unknown[]): unknown[] { return truncateMessagesByBytes(messages, DEFAULT_GEN_AI_MESSAGES_BYTE_LIMIT); } From b05c3c5526ade222391b271b0691dc08653eb8f7 Mon Sep 17 00:00:00 2001 From: Rola Abuhasna Date: Fri, 10 Oct 2025 13:47:12 +0200 Subject: [PATCH 10/10] Update index.ts --- packages/core/src/utils/google-genai/index.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/core/src/utils/google-genai/index.ts b/packages/core/src/utils/google-genai/index.ts index 610806b52626..77466ccdeff1 100644 --- a/packages/core/src/utils/google-genai/index.ts +++ b/packages/core/src/utils/google-genai/index.ts @@ -128,10 +128,15 @@ function extractRequestAttributes( return attributes; } - +/** + * Add private request attributes to spans. + * This is only recorded if recordInputs is true. + * Handles different parameter formats for different Google GenAI methods. + */ function addPrivateRequestAttributes(span: Span, params: Record): void { if ('contents' in params) { const contents = params.contents; + // For models.generateContent: ContentListUnion: Content | Content[] | PartUnion | PartUnion[] if (Array.isArray(contents)) { const truncatedContents = truncateGenAiMessages(contents); span.setAttributes({ [GEN_AI_REQUEST_MESSAGES_ATTRIBUTE]: JSON.stringify(truncatedContents) });