@sentry/core 10.45.0 → 10.46.0
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/build/cjs/asyncContext/stackStrategy.js +6 -11
- package/build/cjs/asyncContext/stackStrategy.js.map +1 -1
- package/build/cjs/tracing/ai/messageTruncation.js +57 -39
- package/build/cjs/tracing/ai/messageTruncation.js.map +1 -1
- package/build/cjs/tracing/ai/utils.js +81 -0
- package/build/cjs/tracing/ai/utils.js.map +1 -1
- package/build/cjs/tracing/anthropic-ai/index.js +35 -19
- package/build/cjs/tracing/anthropic-ai/index.js.map +1 -1
- package/build/cjs/tracing/openai/index.js +3 -73
- package/build/cjs/tracing/openai/index.js.map +1 -1
- package/build/cjs/tracing/openai/utils.js +0 -8
- package/build/cjs/tracing/openai/utils.js.map +1 -1
- package/build/cjs/tracing/vercel-ai/index.js +9 -1
- package/build/cjs/tracing/vercel-ai/index.js.map +1 -1
- package/build/cjs/utils/chain-and-copy-promiselike.js +57 -0
- package/build/cjs/utils/chain-and-copy-promiselike.js.map +1 -0
- package/build/cjs/utils/handleCallbackErrors.js +15 -11
- package/build/cjs/utils/handleCallbackErrors.js.map +1 -1
- package/build/cjs/utils/version.js +1 -1
- package/build/esm/asyncContext/stackStrategy.js +6 -11
- package/build/esm/asyncContext/stackStrategy.js.map +1 -1
- package/build/esm/package.json +1 -1
- package/build/esm/tracing/ai/messageTruncation.js +57 -39
- package/build/esm/tracing/ai/messageTruncation.js.map +1 -1
- package/build/esm/tracing/ai/utils.js +81 -1
- package/build/esm/tracing/ai/utils.js.map +1 -1
- package/build/esm/tracing/anthropic-ai/index.js +36 -20
- package/build/esm/tracing/anthropic-ai/index.js.map +1 -1
- package/build/esm/tracing/openai/index.js +4 -74
- package/build/esm/tracing/openai/index.js.map +1 -1
- package/build/esm/tracing/openai/utils.js +1 -8
- package/build/esm/tracing/openai/utils.js.map +1 -1
- package/build/esm/tracing/vercel-ai/index.js +9 -1
- package/build/esm/tracing/vercel-ai/index.js.map +1 -1
- package/build/esm/utils/chain-and-copy-promiselike.js +55 -0
- package/build/esm/utils/chain-and-copy-promiselike.js.map +1 -0
- package/build/esm/utils/handleCallbackErrors.js +15 -11
- package/build/esm/utils/handleCallbackErrors.js.map +1 -1
- package/build/esm/utils/version.js +1 -1
- package/build/types/asyncContext/stackStrategy.d.ts.map +1 -1
- package/build/types/tracing/ai/messageTruncation.d.ts.map +1 -1
- package/build/types/tracing/ai/utils.d.ts +9 -0
- package/build/types/tracing/ai/utils.d.ts.map +1 -1
- package/build/types/tracing/anthropic-ai/index.d.ts.map +1 -1
- package/build/types/tracing/openai/index.d.ts.map +1 -1
- package/build/types/tracing/openai/utils.d.ts +0 -4
- package/build/types/tracing/openai/utils.d.ts.map +1 -1
- package/build/types/tracing/vercel-ai/index.d.ts.map +1 -1
- package/build/types/utils/chain-and-copy-promiselike.d.ts +6 -0
- package/build/types/utils/chain-and-copy-promiselike.d.ts.map +1 -0
- package/build/types/utils/handleCallbackErrors.d.ts.map +1 -1
- package/build/types-ts3.8/tracing/ai/utils.d.ts +9 -0
- package/build/types-ts3.8/tracing/openai/utils.d.ts +0 -4
- package/build/types-ts3.8/utils/chain-and-copy-promiselike.d.ts +6 -0
- package/package.json +1 -1
|
@@ -2,6 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
2
2
|
|
|
3
3
|
const defaultScopes = require('../defaultScopes.js');
|
|
4
4
|
const scope = require('../scope.js');
|
|
5
|
+
const chainAndCopyPromiselike = require('../utils/chain-and-copy-promiselike.js');
|
|
5
6
|
const is = require('../utils/is.js');
|
|
6
7
|
const carrier = require('../carrier.js');
|
|
7
8
|
|
|
@@ -45,17 +46,11 @@ class AsyncContextStack {
|
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
if (is.isThenable(maybePromiseResult)) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
},
|
|
54
|
-
e => {
|
|
55
|
-
this._popScope();
|
|
56
|
-
throw e;
|
|
57
|
-
},
|
|
58
|
-
);
|
|
49
|
+
return chainAndCopyPromiselike.chainAndCopyPromiseLike(
|
|
50
|
+
maybePromiseResult ,
|
|
51
|
+
() => this._popScope(),
|
|
52
|
+
() => this._popScope(),
|
|
53
|
+
) ;
|
|
59
54
|
}
|
|
60
55
|
|
|
61
56
|
this._popScope();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stackStrategy.js","sources":["../../../src/asyncContext/stackStrategy.ts"],"sourcesContent":["import type { Client } from '../client';\nimport { getDefaultCurrentScope, getDefaultIsolationScope } from '../defaultScopes';\nimport { Scope } from '../scope';\nimport { isThenable } from '../utils/is';\nimport { getMainCarrier, getSentryCarrier } from './../carrier';\nimport type { AsyncContextStrategy } from './types';\n\ninterface Layer {\n client?: Client;\n scope: Scope;\n}\n\n/**\n * This is an object that holds a stack of scopes.\n */\nexport class AsyncContextStack {\n private readonly _stack: [Layer, ...Layer[]];\n private _isolationScope: Scope;\n\n public constructor(scope?: Scope, isolationScope?: Scope) {\n let assignedScope;\n if (!scope) {\n assignedScope = new Scope();\n } else {\n assignedScope = scope;\n }\n\n let assignedIsolationScope;\n if (!isolationScope) {\n assignedIsolationScope = new Scope();\n } else {\n assignedIsolationScope = isolationScope;\n }\n\n // scope stack for domains or the process\n this._stack = [{ scope: assignedScope }];\n this._isolationScope = assignedIsolationScope;\n }\n\n /**\n * Fork a scope for the stack.\n */\n public withScope<T>(callback: (scope: Scope) => T): T {\n const scope = this._pushScope();\n\n let maybePromiseResult: T;\n try {\n maybePromiseResult = callback(scope);\n } catch (e) {\n this._popScope();\n throw e;\n }\n\n if (isThenable(maybePromiseResult)) {\n
|
|
1
|
+
{"version":3,"file":"stackStrategy.js","sources":["../../../src/asyncContext/stackStrategy.ts"],"sourcesContent":["import type { Client } from '../client';\nimport { getDefaultCurrentScope, getDefaultIsolationScope } from '../defaultScopes';\nimport { Scope } from '../scope';\nimport { chainAndCopyPromiseLike } from '../utils/chain-and-copy-promiselike';\nimport { isThenable } from '../utils/is';\nimport { getMainCarrier, getSentryCarrier } from './../carrier';\nimport type { AsyncContextStrategy } from './types';\n\ninterface Layer {\n client?: Client;\n scope: Scope;\n}\n\n/**\n * This is an object that holds a stack of scopes.\n */\nexport class AsyncContextStack {\n private readonly _stack: [Layer, ...Layer[]];\n private _isolationScope: Scope;\n\n public constructor(scope?: Scope, isolationScope?: Scope) {\n let assignedScope;\n if (!scope) {\n assignedScope = new Scope();\n } else {\n assignedScope = scope;\n }\n\n let assignedIsolationScope;\n if (!isolationScope) {\n assignedIsolationScope = new Scope();\n } else {\n assignedIsolationScope = isolationScope;\n }\n\n // scope stack for domains or the process\n this._stack = [{ scope: assignedScope }];\n this._isolationScope = assignedIsolationScope;\n }\n\n /**\n * Fork a scope for the stack.\n */\n public withScope<T>(callback: (scope: Scope) => T): T {\n const scope = this._pushScope();\n\n let maybePromiseResult: T;\n try {\n maybePromiseResult = callback(scope);\n } catch (e) {\n this._popScope();\n throw e;\n }\n\n if (isThenable(maybePromiseResult)) {\n return chainAndCopyPromiseLike(\n maybePromiseResult as PromiseLike<Awaited<typeof maybePromiseResult>> & Record<string, unknown>,\n () => this._popScope(),\n () => this._popScope(),\n ) as T;\n }\n\n this._popScope();\n return maybePromiseResult;\n }\n\n /**\n * Get the client of the stack.\n */\n public getClient<C extends Client>(): C | undefined {\n return this.getStackTop().client as C;\n }\n\n /**\n * Returns the scope of the top stack.\n */\n public getScope(): Scope {\n return this.getStackTop().scope;\n }\n\n /**\n * Get the isolation scope for the stack.\n */\n public getIsolationScope(): Scope {\n return this._isolationScope;\n }\n\n /**\n * Returns the topmost scope layer in the order domain > local > process.\n */\n public getStackTop(): Layer {\n return this._stack[this._stack.length - 1] as Layer;\n }\n\n /**\n * Push a scope to the stack.\n */\n private _pushScope(): Scope {\n // We want to clone the content of prev scope\n const scope = this.getScope().clone();\n this._stack.push({\n client: this.getClient(),\n scope,\n });\n return scope;\n }\n\n /**\n * Pop a scope from the stack.\n */\n private _popScope(): boolean {\n if (this._stack.length <= 1) return false;\n return !!this._stack.pop();\n }\n}\n\n/**\n * Get the global async context stack.\n * This will be removed during the v8 cycle and is only here to make migration easier.\n */\nfunction getAsyncContextStack(): AsyncContextStack {\n const registry = getMainCarrier();\n const sentry = getSentryCarrier(registry);\n\n return (sentry.stack = sentry.stack || new AsyncContextStack(getDefaultCurrentScope(), getDefaultIsolationScope()));\n}\n\nfunction withScope<T>(callback: (scope: Scope) => T): T {\n return getAsyncContextStack().withScope(callback);\n}\n\nfunction withSetScope<T>(scope: Scope, callback: (scope: Scope) => T): T {\n const stack = getAsyncContextStack();\n return stack.withScope(() => {\n stack.getStackTop().scope = scope;\n return callback(scope);\n });\n}\n\nfunction withIsolationScope<T>(callback: (isolationScope: Scope) => T): T {\n return getAsyncContextStack().withScope(() => {\n return callback(getAsyncContextStack().getIsolationScope());\n });\n}\n\n/**\n * Get the stack-based async context strategy.\n */\nexport function getStackAsyncContextStrategy(): AsyncContextStrategy {\n return {\n withIsolationScope,\n withScope,\n withSetScope,\n withSetIsolationScope: <T>(_isolationScope: Scope, callback: (isolationScope: Scope) => T) => {\n return withIsolationScope(callback);\n },\n getCurrentScope: () => getAsyncContextStack().getScope(),\n getIsolationScope: () => getAsyncContextStack().getIsolationScope(),\n };\n}\n"],"names":["scope","Scope","isThenable","chainAndCopyPromiseLike","getMainCarrier","getSentryCarrier","getDefaultCurrentScope","getDefaultIsolationScope"],"mappings":"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"}
|
|
@@ -60,31 +60,33 @@ function truncateTextByBytes(text, maxBytes) {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
|
-
* Extract text content from a
|
|
64
|
-
*
|
|
63
|
+
* Extract text content from a message item.
|
|
64
|
+
* Handles plain strings and objects with a text property.
|
|
65
65
|
*
|
|
66
66
|
* @returns The text content
|
|
67
67
|
*/
|
|
68
|
-
function
|
|
69
|
-
if (typeof
|
|
70
|
-
return
|
|
68
|
+
function getItemText(item) {
|
|
69
|
+
if (typeof item === 'string') {
|
|
70
|
+
return item;
|
|
71
|
+
}
|
|
72
|
+
if ('text' in item && typeof item.text === 'string') {
|
|
73
|
+
return item.text;
|
|
71
74
|
}
|
|
72
|
-
if ('text' in part) return part.text;
|
|
73
75
|
return '';
|
|
74
76
|
}
|
|
75
77
|
|
|
76
78
|
/**
|
|
77
|
-
* Create a new
|
|
79
|
+
* Create a new item with updated text content while preserving the original structure.
|
|
78
80
|
*
|
|
79
|
-
* @param
|
|
81
|
+
* @param item - Original item (string or object)
|
|
80
82
|
* @param text - New text content
|
|
81
|
-
* @returns New
|
|
83
|
+
* @returns New item with updated text
|
|
82
84
|
*/
|
|
83
|
-
function
|
|
84
|
-
if (typeof
|
|
85
|
+
function withItemText(item, text) {
|
|
86
|
+
if (typeof item === 'string') {
|
|
85
87
|
return text;
|
|
86
88
|
}
|
|
87
|
-
return { ...
|
|
89
|
+
return { ...item, text };
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
/**
|
|
@@ -141,56 +143,77 @@ function truncateContentMessage(message, maxBytes) {
|
|
|
141
143
|
}
|
|
142
144
|
|
|
143
145
|
/**
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
+
* Extracts the array items and their key from an array-based message.
|
|
147
|
+
* Returns `null` key if neither `parts` nor `content` is a valid array.
|
|
148
|
+
*/
|
|
149
|
+
function getArrayItems(message)
|
|
150
|
+
|
|
151
|
+
{
|
|
152
|
+
if ('parts' in message && Array.isArray(message.parts)) {
|
|
153
|
+
return { key: 'parts', items: message.parts };
|
|
154
|
+
}
|
|
155
|
+
if ('content' in message && Array.isArray(message.content)) {
|
|
156
|
+
return { key: 'content', items: message.content };
|
|
157
|
+
}
|
|
158
|
+
return { key: null, items: [] };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Truncate a message with an array-based format.
|
|
163
|
+
* Handles both `parts: [...]` (Google GenAI) and `content: [...]` (OpenAI/Anthropic multimodal).
|
|
164
|
+
* Keeps as many complete items as possible, only truncating the first item if needed.
|
|
146
165
|
*
|
|
147
|
-
* @param message - Message with parts array
|
|
166
|
+
* @param message - Message with parts or content array
|
|
148
167
|
* @param maxBytes - Maximum byte limit
|
|
149
168
|
* @returns Array with truncated message, or empty array if it doesn't fit
|
|
150
169
|
*/
|
|
151
|
-
function
|
|
152
|
-
const {
|
|
170
|
+
function truncateArrayMessage(message, maxBytes) {
|
|
171
|
+
const { key, items } = getArrayItems(message);
|
|
172
|
+
|
|
173
|
+
if (key === null || items.length === 0) {
|
|
174
|
+
return [];
|
|
175
|
+
}
|
|
153
176
|
|
|
154
|
-
// Calculate overhead by creating empty text
|
|
155
|
-
const
|
|
156
|
-
const overhead = jsonBytes({ ...message,
|
|
177
|
+
// Calculate overhead by creating empty text items
|
|
178
|
+
const emptyItems = items.map(item => withItemText(item, ''));
|
|
179
|
+
const overhead = jsonBytes({ ...message, [key]: emptyItems });
|
|
157
180
|
let remainingBytes = maxBytes - overhead;
|
|
158
181
|
|
|
159
182
|
if (remainingBytes <= 0) {
|
|
160
183
|
return [];
|
|
161
184
|
}
|
|
162
185
|
|
|
163
|
-
// Include
|
|
164
|
-
const
|
|
186
|
+
// Include items until we run out of space
|
|
187
|
+
const includedItems = [];
|
|
165
188
|
|
|
166
|
-
for (const
|
|
167
|
-
const text =
|
|
189
|
+
for (const item of items) {
|
|
190
|
+
const text = getItemText(item);
|
|
168
191
|
const textSize = utf8Bytes(text);
|
|
169
192
|
|
|
170
193
|
if (textSize <= remainingBytes) {
|
|
171
|
-
//
|
|
172
|
-
|
|
194
|
+
// Item fits: include it as-is
|
|
195
|
+
includedItems.push(item);
|
|
173
196
|
remainingBytes -= textSize;
|
|
174
|
-
} else if (
|
|
175
|
-
// First
|
|
197
|
+
} else if (includedItems.length === 0) {
|
|
198
|
+
// First item doesn't fit: truncate it
|
|
176
199
|
const truncated = truncateTextByBytes(text, remainingBytes);
|
|
177
200
|
if (truncated) {
|
|
178
|
-
|
|
201
|
+
includedItems.push(withItemText(item, truncated));
|
|
179
202
|
}
|
|
180
203
|
break;
|
|
181
204
|
} else {
|
|
182
|
-
// Subsequent
|
|
205
|
+
// Subsequent item doesn't fit: stop here
|
|
183
206
|
break;
|
|
184
207
|
}
|
|
185
208
|
}
|
|
186
209
|
|
|
187
210
|
/* c8 ignore start
|
|
188
211
|
* for type safety only, algorithm guarantees SOME text included */
|
|
189
|
-
if (
|
|
212
|
+
if (includedItems.length <= 0) {
|
|
190
213
|
return [];
|
|
191
214
|
} else {
|
|
192
215
|
/* c8 ignore stop */
|
|
193
|
-
return [{ ...message,
|
|
216
|
+
return [{ ...message, [key]: includedItems }];
|
|
194
217
|
}
|
|
195
218
|
}
|
|
196
219
|
|
|
@@ -223,13 +246,8 @@ function truncateSingleMessage(message, maxBytes) {
|
|
|
223
246
|
return truncateContentMessage(message, maxBytes);
|
|
224
247
|
}
|
|
225
248
|
|
|
226
|
-
if (isContentArrayMessage(message)) {
|
|
227
|
-
|
|
228
|
-
return [message];
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (isPartsMessage(message)) {
|
|
232
|
-
return truncatePartsMessage(message, maxBytes);
|
|
249
|
+
if (isContentArrayMessage(message) || isPartsMessage(message)) {
|
|
250
|
+
return truncateArrayMessage(message, maxBytes);
|
|
233
251
|
}
|
|
234
252
|
|
|
235
253
|
// Unknown message format: cannot truncate safely
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messageTruncation.js","sources":["../../../../src/tracing/ai/messageTruncation.ts"],"sourcesContent":["import { isContentMedia, stripInlineMediaFromSingleMessage } from './mediaStripping';\n\n/**\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 * 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 (inclusive) 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 (inclusive, UTF-8 encoded)\n * @returns Truncated string whose UTF-8 byte length is at most 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 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 three message formats:\n * - OpenAI/Anthropic: `{ ..., content: string }`\n * - Vercel AI/OpenAI multimodal: `{ ..., content: Array<{type, text?, ...}> }`\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 (isContentArrayMessage(message)) {\n // Content array messages are returned as-is without truncation\n return [message];\n }\n\n if (isPartsMessage(message)) {\n return truncatePartsMessage(message, maxBytes);\n }\n\n // Unknown message format: cannot truncate safely\n return [];\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 // The result is always a single-element array that callers wrap with\n // JSON.stringify([message]), so subtract the 2-byte array wrapper (\"[\" and \"]\")\n // to ensure the final serialized value stays under the limit.\n const effectiveMaxBytes = maxBytes - 2;\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 <= effectiveMaxBytes) {\n return stripped;\n }\n\n // Truncate the single message if needed\n return truncateSingleMessage(strippedMessage, effectiveMaxBytes);\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":["isContentMedia","stripInlineMediaFromSingleMessage"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"messageTruncation.js","sources":["../../../../src/tracing/ai/messageTruncation.ts"],"sourcesContent":["import { isContentMedia, stripInlineMediaFromSingleMessage } from './mediaStripping';\n\n/**\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 * One block inside OpenAI / Anthropic `content: [...]` arrays (text, image_url, etc.).\n */\ntype ContentArrayBlock = {\n [key: string]: unknown;\n type: string;\n};\n\n/**\n * Message format used by OpenAI and Anthropic APIs for media.\n */\ntype ContentArrayMessage = {\n [key: string]: unknown;\n content: ContentArrayBlock[];\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 * One element of an array-based message: OpenAI/Anthropic `content[]` or Google `parts`.\n */\ntype ArrayMessageItem = TextPart | MediaPart | ContentArrayBlock;\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 (inclusive) 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 (inclusive, UTF-8 encoded)\n * @returns Truncated string whose UTF-8 byte length is at most 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 message item.\n * Handles plain strings and objects with a text property.\n *\n * @returns The text content\n */\nfunction getItemText(item: ArrayMessageItem): string {\n if (typeof item === 'string') {\n return item;\n }\n if ('text' in item && typeof item.text === 'string') {\n return item.text;\n }\n return '';\n}\n\n/**\n * Create a new item with updated text content while preserving the original structure.\n *\n * @param item - Original item (string or object)\n * @param text - New text content\n * @returns New item with updated text\n */\nfunction withItemText(item: ArrayMessageItem, text: string): ArrayMessageItem {\n if (typeof item === 'string') {\n return text;\n }\n return { ...item, 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 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 * Extracts the array items and their key from an array-based message.\n * Returns `null` key if neither `parts` nor `content` is a valid array.\n */\nfunction getArrayItems(message: PartsMessage | ContentArrayMessage): {\n key: 'parts' | 'content' | null;\n items: ArrayMessageItem[];\n} {\n if ('parts' in message && Array.isArray(message.parts)) {\n return { key: 'parts', items: message.parts };\n }\n if ('content' in message && Array.isArray(message.content)) {\n return { key: 'content', items: message.content };\n }\n return { key: null, items: [] };\n}\n\n/**\n * Truncate a message with an array-based format.\n * Handles both `parts: [...]` (Google GenAI) and `content: [...]` (OpenAI/Anthropic multimodal).\n * Keeps as many complete items as possible, only truncating the first item if needed.\n *\n * @param message - Message with parts or content array\n * @param maxBytes - Maximum byte limit\n * @returns Array with truncated message, or empty array if it doesn't fit\n */\nfunction truncateArrayMessage(message: PartsMessage | ContentArrayMessage, maxBytes: number): unknown[] {\n const { key, items } = getArrayItems(message);\n\n if (key === null || items.length === 0) {\n return [];\n }\n\n // Calculate overhead by creating empty text items\n const emptyItems = items.map(item => withItemText(item, ''));\n const overhead = jsonBytes({ ...message, [key]: emptyItems });\n let remainingBytes = maxBytes - overhead;\n\n if (remainingBytes <= 0) {\n return [];\n }\n\n // Include items until we run out of space\n const includedItems: ArrayMessageItem[] = [];\n\n for (const item of items) {\n const text = getItemText(item);\n const textSize = utf8Bytes(text);\n\n if (textSize <= remainingBytes) {\n // Item fits: include it as-is\n includedItems.push(item);\n remainingBytes -= textSize;\n } else if (includedItems.length === 0) {\n // First item doesn't fit: truncate it\n const truncated = truncateTextByBytes(text, remainingBytes);\n if (truncated) {\n includedItems.push(withItemText(item, truncated));\n }\n break;\n } else {\n // Subsequent item 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 (includedItems.length <= 0) {\n return [];\n } else {\n /* c8 ignore stop */\n return [{ ...message, [key]: includedItems }];\n }\n}\n\n/**\n * Truncate a single message to fit within maxBytes.\n *\n * Supports three message formats:\n * - OpenAI/Anthropic: `{ ..., content: string }`\n * - Vercel AI/OpenAI multimodal: `{ ..., content: Array<{type, text?, ...}> }`\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 (isContentArrayMessage(message) || isPartsMessage(message)) {\n return truncateArrayMessage(message, maxBytes);\n }\n\n // Unknown message format: cannot truncate safely\n return [];\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 // The result is always a single-element array that callers wrap with\n // JSON.stringify([message]), so subtract the 2-byte array wrapper (\"[\" and \"]\")\n // to ensure the final serialized value stays under the limit.\n const effectiveMaxBytes = maxBytes - 2;\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 <= effectiveMaxBytes) {\n return stripped;\n }\n\n // Truncate the single message if needed\n return truncateSingleMessage(strippedMessage, effectiveMaxBytes);\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":["isContentMedia","stripInlineMediaFromSingleMessage"],"mappings":"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"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
|
|
3
|
+
const _exports = require('../../exports.js');
|
|
3
4
|
const currentScopes = require('../../currentScopes.js');
|
|
5
|
+
const is = require('../../utils/is.js');
|
|
4
6
|
const genAiAttributes = require('./gen-ai-attributes.js');
|
|
5
7
|
const messageTruncation = require('./messageTruncation.js');
|
|
6
8
|
|
|
@@ -165,6 +167,84 @@ function extractSystemInstructions(messages)
|
|
|
165
167
|
return { systemInstructions, filteredMessages };
|
|
166
168
|
}
|
|
167
169
|
|
|
170
|
+
/**
|
|
171
|
+
* Creates a wrapped version of .withResponse() that replaces the data field
|
|
172
|
+
* with the instrumented result while preserving metadata (response, request_id).
|
|
173
|
+
*/
|
|
174
|
+
async function createWithResponseWrapper(
|
|
175
|
+
originalWithResponse,
|
|
176
|
+
instrumentedPromise,
|
|
177
|
+
mechanismType,
|
|
178
|
+
) {
|
|
179
|
+
// Attach catch handler to originalWithResponse immediately to prevent unhandled rejection
|
|
180
|
+
// If instrumentedPromise rejects first, we still need this handled
|
|
181
|
+
const safeOriginalWithResponse = originalWithResponse.catch(error => {
|
|
182
|
+
_exports.captureException(error, {
|
|
183
|
+
mechanism: {
|
|
184
|
+
handled: false,
|
|
185
|
+
type: mechanismType,
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
throw error;
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const instrumentedResult = await instrumentedPromise;
|
|
192
|
+
const originalWrapper = await safeOriginalWithResponse;
|
|
193
|
+
|
|
194
|
+
// Combine instrumented result with original metadata
|
|
195
|
+
if (originalWrapper && typeof originalWrapper === 'object' && 'data' in originalWrapper) {
|
|
196
|
+
return {
|
|
197
|
+
...originalWrapper,
|
|
198
|
+
data: instrumentedResult,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
return instrumentedResult;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Wraps a promise-like object to preserve additional methods (like .withResponse())
|
|
206
|
+
* that AI SDK clients (OpenAI, Anthropic) attach to their APIPromise return values.
|
|
207
|
+
*
|
|
208
|
+
* Standard Promise methods (.then, .catch, .finally) are routed to the instrumented
|
|
209
|
+
* promise to preserve Sentry's span instrumentation, while custom SDK methods are
|
|
210
|
+
* forwarded to the original promise to maintain the SDK's API surface.
|
|
211
|
+
*/
|
|
212
|
+
function wrapPromiseWithMethods(
|
|
213
|
+
originalPromiseLike,
|
|
214
|
+
instrumentedPromise,
|
|
215
|
+
mechanismType,
|
|
216
|
+
) {
|
|
217
|
+
// If the original result is not thenable, return the instrumented promise
|
|
218
|
+
if (!is.isThenable(originalPromiseLike)) {
|
|
219
|
+
return instrumentedPromise;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Create a proxy that forwards Promise methods to instrumentedPromise
|
|
223
|
+
// and preserves additional methods from the original result
|
|
224
|
+
return new Proxy(originalPromiseLike, {
|
|
225
|
+
get(target, prop) {
|
|
226
|
+
// For standard Promise methods (.then, .catch, .finally, Symbol.toStringTag),
|
|
227
|
+
// use instrumentedPromise to preserve Sentry instrumentation.
|
|
228
|
+
// For custom methods (like .withResponse()), use the original target.
|
|
229
|
+
const useInstrumentedPromise = prop in Promise.prototype || prop === Symbol.toStringTag;
|
|
230
|
+
const source = useInstrumentedPromise ? instrumentedPromise : target;
|
|
231
|
+
|
|
232
|
+
const value = Reflect.get(source, prop) ;
|
|
233
|
+
|
|
234
|
+
// Special handling for .withResponse() to preserve instrumentation
|
|
235
|
+
// .withResponse() returns { data: T, response: Response, request_id: string }
|
|
236
|
+
if (prop === 'withResponse' && typeof value === 'function') {
|
|
237
|
+
return function wrappedWithResponse() {
|
|
238
|
+
const originalWithResponse = (value ).call(target);
|
|
239
|
+
return createWithResponseWrapper(originalWithResponse, instrumentedPromise, mechanismType);
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return typeof value === 'function' ? value.bind(source) : value;
|
|
244
|
+
},
|
|
245
|
+
}) ;
|
|
246
|
+
}
|
|
247
|
+
|
|
168
248
|
exports.buildMethodPath = buildMethodPath;
|
|
169
249
|
exports.extractSystemInstructions = extractSystemInstructions;
|
|
170
250
|
exports.getFinalOperationName = getFinalOperationName;
|
|
@@ -172,4 +252,5 @@ exports.getSpanOperation = getSpanOperation;
|
|
|
172
252
|
exports.getTruncatedJsonString = getTruncatedJsonString;
|
|
173
253
|
exports.resolveAIRecordingOptions = resolveAIRecordingOptions;
|
|
174
254
|
exports.setTokenUsageAttributes = setTokenUsageAttributes;
|
|
255
|
+
exports.wrapPromiseWithMethods = wrapPromiseWithMethods;
|
|
175
256
|
//# sourceMappingURL=utils.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sources":["../../../../src/tracing/ai/utils.ts"],"sourcesContent":["/**\n * Shared utils for AI integrations (OpenAI, Anthropic, Verce.AI, etc.)\n */\nimport { getClient } from '../../currentScopes';\nimport type { Span } from '../../types-hoist/span';\nimport {\n GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE,\n GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE,\n GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE,\n} from './gen-ai-attributes';\nimport { truncateGenAiMessages, truncateGenAiStringInput } from './messageTruncation';\n\nexport interface AIRecordingOptions {\n recordInputs?: boolean;\n recordOutputs?: boolean;\n}\n\n/**\n * Resolves AI recording options by falling back to the client's `sendDefaultPii` setting.\n * Precedence: explicit option > sendDefaultPii > false\n */\nexport function resolveAIRecordingOptions<T extends AIRecordingOptions>(options?: T): T & Required<AIRecordingOptions> {\n const sendDefaultPii = Boolean(getClient()?.getOptions().sendDefaultPii);\n return {\n ...options,\n recordInputs: options?.recordInputs ?? sendDefaultPii,\n recordOutputs: options?.recordOutputs ?? sendDefaultPii,\n } as T & Required<AIRecordingOptions>;\n}\n\n/**\n * Maps AI method paths to OpenTelemetry semantic convention operation names\n * @see https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/#llm-request-spans\n */\nexport function getFinalOperationName(methodPath: string): string {\n if (methodPath.includes('messages')) {\n return 'chat';\n }\n if (methodPath.includes('completions')) {\n return 'text_completion';\n }\n // Google GenAI: models.generateContent* -> generate_content (actually generates AI responses)\n if (methodPath.includes('generateContent')) {\n return 'generate_content';\n }\n // Anthropic: models.get/retrieve -> models (metadata retrieval only)\n if (methodPath.includes('models')) {\n return 'models';\n }\n if (methodPath.includes('chat')) {\n return 'chat';\n }\n return methodPath.split('.').pop() || 'unknown';\n}\n\n/**\n * Get the span operation for AI methods\n * Following Sentry's convention: \"gen_ai.{operation_name}\"\n */\nexport function getSpanOperation(methodPath: string): string {\n return `gen_ai.${getFinalOperationName(methodPath)}`;\n}\n\n/**\n * Build method path from current traversal\n */\nexport function buildMethodPath(currentPath: string, prop: string): string {\n return currentPath ? `${currentPath}.${prop}` : prop;\n}\n\n/**\n * Set token usage attributes\n * @param span - The span to add attributes to\n * @param promptTokens - The number of prompt tokens\n * @param completionTokens - The number of completion tokens\n * @param cachedInputTokens - The number of cached input tokens\n * @param cachedOutputTokens - The number of cached output tokens\n */\nexport function setTokenUsageAttributes(\n span: Span,\n promptTokens?: number,\n completionTokens?: number,\n cachedInputTokens?: number,\n cachedOutputTokens?: number,\n): void {\n if (promptTokens !== undefined) {\n span.setAttributes({\n [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: promptTokens,\n });\n }\n if (completionTokens !== undefined) {\n span.setAttributes({\n [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: completionTokens,\n });\n }\n if (\n promptTokens !== undefined ||\n completionTokens !== undefined ||\n cachedInputTokens !== undefined ||\n cachedOutputTokens !== undefined\n ) {\n /**\n * Total input tokens in a request is the summation of `input_tokens`,\n * `cache_creation_input_tokens`, and `cache_read_input_tokens`.\n */\n const totalTokens =\n (promptTokens ?? 0) + (completionTokens ?? 0) + (cachedInputTokens ?? 0) + (cachedOutputTokens ?? 0);\n\n span.setAttributes({\n [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: totalTokens,\n });\n }\n}\n\n/**\n * Get the truncated JSON string for a string or array of strings.\n *\n * @param value - The string or array of strings to truncate\n * @returns The truncated JSON string\n */\nexport function getTruncatedJsonString<T>(value: T | T[]): string {\n if (typeof value === 'string') {\n // Some values are already JSON strings, so we don't need to duplicate the JSON parsing\n return truncateGenAiStringInput(value);\n }\n if (Array.isArray(value)) {\n // truncateGenAiMessages returns an array of strings, so we need to stringify it\n const truncatedMessages = truncateGenAiMessages(value);\n return JSON.stringify(truncatedMessages);\n }\n // value is an object, so we need to stringify it\n return JSON.stringify(value);\n}\n\n/**\n * Extract system instructions from messages array.\n * Finds the first system message and formats it according to OpenTelemetry semantic conventions.\n *\n * @param messages - Array of messages to extract system instructions from\n * @returns systemInstructions (JSON string) and filteredMessages (without system message)\n */\nexport function extractSystemInstructions(messages: unknown[] | unknown): {\n systemInstructions: string | undefined;\n filteredMessages: unknown[] | unknown;\n} {\n if (!Array.isArray(messages)) {\n return { systemInstructions: undefined, filteredMessages: messages };\n }\n\n const systemMessageIndex = messages.findIndex(\n msg => msg && typeof msg === 'object' && 'role' in msg && (msg as { role: string }).role === 'system',\n );\n\n if (systemMessageIndex === -1) {\n return { systemInstructions: undefined, filteredMessages: messages };\n }\n\n const systemMessage = messages[systemMessageIndex] as { role: string; content?: string | unknown };\n const systemContent =\n typeof systemMessage.content === 'string'\n ? systemMessage.content\n : systemMessage.content !== undefined\n ? JSON.stringify(systemMessage.content)\n : undefined;\n\n if (!systemContent) {\n return { systemInstructions: undefined, filteredMessages: messages };\n }\n\n const systemInstructions = JSON.stringify([{ type: 'text', content: systemContent }]);\n const filteredMessages = [...messages.slice(0, systemMessageIndex), ...messages.slice(systemMessageIndex + 1)];\n\n return { systemInstructions, filteredMessages };\n}\n"],"names":["getClient","GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE","GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE","GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE","truncateGenAiStringInput","truncateGenAiMessages"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"utils.js","sources":["../../../../src/tracing/ai/utils.ts"],"sourcesContent":["/**\n * Shared utils for AI integrations (OpenAI, Anthropic, Verce.AI, etc.)\n */\nimport { captureException } from '../../exports';\nimport { getClient } from '../../currentScopes';\nimport type { Span } from '../../types-hoist/span';\nimport { isThenable } from '../../utils/is';\nimport {\n GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE,\n GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE,\n GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE,\n} from './gen-ai-attributes';\nimport { truncateGenAiMessages, truncateGenAiStringInput } from './messageTruncation';\n\nexport interface AIRecordingOptions {\n recordInputs?: boolean;\n recordOutputs?: boolean;\n}\n\n/**\n * Resolves AI recording options by falling back to the client's `sendDefaultPii` setting.\n * Precedence: explicit option > sendDefaultPii > false\n */\nexport function resolveAIRecordingOptions<T extends AIRecordingOptions>(options?: T): T & Required<AIRecordingOptions> {\n const sendDefaultPii = Boolean(getClient()?.getOptions().sendDefaultPii);\n return {\n ...options,\n recordInputs: options?.recordInputs ?? sendDefaultPii,\n recordOutputs: options?.recordOutputs ?? sendDefaultPii,\n } as T & Required<AIRecordingOptions>;\n}\n\n/**\n * Maps AI method paths to OpenTelemetry semantic convention operation names\n * @see https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/#llm-request-spans\n */\nexport function getFinalOperationName(methodPath: string): string {\n if (methodPath.includes('messages')) {\n return 'chat';\n }\n if (methodPath.includes('completions')) {\n return 'text_completion';\n }\n // Google GenAI: models.generateContent* -> generate_content (actually generates AI responses)\n if (methodPath.includes('generateContent')) {\n return 'generate_content';\n }\n // Anthropic: models.get/retrieve -> models (metadata retrieval only)\n if (methodPath.includes('models')) {\n return 'models';\n }\n if (methodPath.includes('chat')) {\n return 'chat';\n }\n return methodPath.split('.').pop() || 'unknown';\n}\n\n/**\n * Get the span operation for AI methods\n * Following Sentry's convention: \"gen_ai.{operation_name}\"\n */\nexport function getSpanOperation(methodPath: string): string {\n return `gen_ai.${getFinalOperationName(methodPath)}`;\n}\n\n/**\n * Build method path from current traversal\n */\nexport function buildMethodPath(currentPath: string, prop: string): string {\n return currentPath ? `${currentPath}.${prop}` : prop;\n}\n\n/**\n * Set token usage attributes\n * @param span - The span to add attributes to\n * @param promptTokens - The number of prompt tokens\n * @param completionTokens - The number of completion tokens\n * @param cachedInputTokens - The number of cached input tokens\n * @param cachedOutputTokens - The number of cached output tokens\n */\nexport function setTokenUsageAttributes(\n span: Span,\n promptTokens?: number,\n completionTokens?: number,\n cachedInputTokens?: number,\n cachedOutputTokens?: number,\n): void {\n if (promptTokens !== undefined) {\n span.setAttributes({\n [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: promptTokens,\n });\n }\n if (completionTokens !== undefined) {\n span.setAttributes({\n [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: completionTokens,\n });\n }\n if (\n promptTokens !== undefined ||\n completionTokens !== undefined ||\n cachedInputTokens !== undefined ||\n cachedOutputTokens !== undefined\n ) {\n /**\n * Total input tokens in a request is the summation of `input_tokens`,\n * `cache_creation_input_tokens`, and `cache_read_input_tokens`.\n */\n const totalTokens =\n (promptTokens ?? 0) + (completionTokens ?? 0) + (cachedInputTokens ?? 0) + (cachedOutputTokens ?? 0);\n\n span.setAttributes({\n [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: totalTokens,\n });\n }\n}\n\n/**\n * Get the truncated JSON string for a string or array of strings.\n *\n * @param value - The string or array of strings to truncate\n * @returns The truncated JSON string\n */\nexport function getTruncatedJsonString<T>(value: T | T[]): string {\n if (typeof value === 'string') {\n // Some values are already JSON strings, so we don't need to duplicate the JSON parsing\n return truncateGenAiStringInput(value);\n }\n if (Array.isArray(value)) {\n // truncateGenAiMessages returns an array of strings, so we need to stringify it\n const truncatedMessages = truncateGenAiMessages(value);\n return JSON.stringify(truncatedMessages);\n }\n // value is an object, so we need to stringify it\n return JSON.stringify(value);\n}\n\n/**\n * Extract system instructions from messages array.\n * Finds the first system message and formats it according to OpenTelemetry semantic conventions.\n *\n * @param messages - Array of messages to extract system instructions from\n * @returns systemInstructions (JSON string) and filteredMessages (without system message)\n */\nexport function extractSystemInstructions(messages: unknown[] | unknown): {\n systemInstructions: string | undefined;\n filteredMessages: unknown[] | unknown;\n} {\n if (!Array.isArray(messages)) {\n return { systemInstructions: undefined, filteredMessages: messages };\n }\n\n const systemMessageIndex = messages.findIndex(\n msg => msg && typeof msg === 'object' && 'role' in msg && (msg as { role: string }).role === 'system',\n );\n\n if (systemMessageIndex === -1) {\n return { systemInstructions: undefined, filteredMessages: messages };\n }\n\n const systemMessage = messages[systemMessageIndex] as { role: string; content?: string | unknown };\n const systemContent =\n typeof systemMessage.content === 'string'\n ? systemMessage.content\n : systemMessage.content !== undefined\n ? JSON.stringify(systemMessage.content)\n : undefined;\n\n if (!systemContent) {\n return { systemInstructions: undefined, filteredMessages: messages };\n }\n\n const systemInstructions = JSON.stringify([{ type: 'text', content: systemContent }]);\n const filteredMessages = [...messages.slice(0, systemMessageIndex), ...messages.slice(systemMessageIndex + 1)];\n\n return { systemInstructions, filteredMessages };\n}\n\n/**\n * Creates a wrapped version of .withResponse() that replaces the data field\n * with the instrumented result while preserving metadata (response, request_id).\n */\nasync function createWithResponseWrapper<T>(\n originalWithResponse: Promise<unknown>,\n instrumentedPromise: Promise<T>,\n mechanismType: string,\n): Promise<unknown> {\n // Attach catch handler to originalWithResponse immediately to prevent unhandled rejection\n // If instrumentedPromise rejects first, we still need this handled\n const safeOriginalWithResponse = originalWithResponse.catch(error => {\n captureException(error, {\n mechanism: {\n handled: false,\n type: mechanismType,\n },\n });\n throw error;\n });\n\n const instrumentedResult = await instrumentedPromise;\n const originalWrapper = await safeOriginalWithResponse;\n\n // Combine instrumented result with original metadata\n if (originalWrapper && typeof originalWrapper === 'object' && 'data' in originalWrapper) {\n return {\n ...originalWrapper,\n data: instrumentedResult,\n };\n }\n return instrumentedResult;\n}\n\n/**\n * Wraps a promise-like object to preserve additional methods (like .withResponse())\n * that AI SDK clients (OpenAI, Anthropic) attach to their APIPromise return values.\n *\n * Standard Promise methods (.then, .catch, .finally) are routed to the instrumented\n * promise to preserve Sentry's span instrumentation, while custom SDK methods are\n * forwarded to the original promise to maintain the SDK's API surface.\n */\nexport function wrapPromiseWithMethods<R>(\n originalPromiseLike: Promise<R>,\n instrumentedPromise: Promise<R>,\n mechanismType: string,\n): Promise<R> {\n // If the original result is not thenable, return the instrumented promise\n if (!isThenable(originalPromiseLike)) {\n return instrumentedPromise;\n }\n\n // Create a proxy that forwards Promise methods to instrumentedPromise\n // and preserves additional methods from the original result\n return new Proxy(originalPromiseLike, {\n get(target: object, prop: string | symbol): unknown {\n // For standard Promise methods (.then, .catch, .finally, Symbol.toStringTag),\n // use instrumentedPromise to preserve Sentry instrumentation.\n // For custom methods (like .withResponse()), use the original target.\n const useInstrumentedPromise = prop in Promise.prototype || prop === Symbol.toStringTag;\n const source = useInstrumentedPromise ? instrumentedPromise : target;\n\n const value = Reflect.get(source, prop) as unknown;\n\n // Special handling for .withResponse() to preserve instrumentation\n // .withResponse() returns { data: T, response: Response, request_id: string }\n if (prop === 'withResponse' && typeof value === 'function') {\n return function wrappedWithResponse(this: unknown): unknown {\n const originalWithResponse = (value as (...args: unknown[]) => unknown).call(target);\n return createWithResponseWrapper(originalWithResponse, instrumentedPromise, mechanismType);\n };\n }\n\n return typeof value === 'function' ? value.bind(source) : value;\n },\n }) as Promise<R>;\n}\n"],"names":["getClient","GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE","GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE","GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE","truncateGenAiStringInput","truncateGenAiMessages","captureException","isThenable"],"mappings":"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"}
|
|
@@ -4,7 +4,6 @@ const _exports = require('../../exports.js');
|
|
|
4
4
|
const semanticAttributes = require('../../semanticAttributes.js');
|
|
5
5
|
const spanstatus = require('../spanstatus.js');
|
|
6
6
|
const trace = require('../trace.js');
|
|
7
|
-
const handleCallbackErrors = require('../../utils/handleCallbackErrors.js');
|
|
8
7
|
const genAiAttributes = require('../ai/gen-ai-attributes.js');
|
|
9
8
|
const utils = require('../ai/utils.js');
|
|
10
9
|
const streaming = require('./streaming.js');
|
|
@@ -189,21 +188,30 @@ function handleStreamingRequest(
|
|
|
189
188
|
|
|
190
189
|
// messages.stream() always returns a sync MessageStream, even with stream: true param
|
|
191
190
|
if (isStreamRequested && !isStreamingMethod) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
result ,
|
|
200
|
-
span,
|
|
201
|
-
options.recordOutputs ?? false,
|
|
202
|
-
) ;
|
|
203
|
-
} catch (error) {
|
|
204
|
-
return handleStreamingError(error, span, methodPath);
|
|
191
|
+
let originalResult;
|
|
192
|
+
|
|
193
|
+
const instrumentedPromise = trace.startSpanManual(spanConfig, (span) => {
|
|
194
|
+
originalResult = originalMethod.apply(context, args) ;
|
|
195
|
+
|
|
196
|
+
if (options.recordInputs && params) {
|
|
197
|
+
addPrivateRequestAttributes(span, params);
|
|
205
198
|
}
|
|
199
|
+
|
|
200
|
+
return (async () => {
|
|
201
|
+
try {
|
|
202
|
+
const result = await originalResult;
|
|
203
|
+
return streaming.instrumentAsyncIterableStream(
|
|
204
|
+
result ,
|
|
205
|
+
span,
|
|
206
|
+
options.recordOutputs ?? false,
|
|
207
|
+
) ;
|
|
208
|
+
} catch (error) {
|
|
209
|
+
return handleStreamingError(error, span, methodPath);
|
|
210
|
+
}
|
|
211
|
+
})();
|
|
206
212
|
});
|
|
213
|
+
|
|
214
|
+
return utils.wrapPromiseWithMethods(originalResult, instrumentedPromise, 'auto.ai.anthropic');
|
|
207
215
|
} else {
|
|
208
216
|
return trace.startSpanManual(spanConfig, span => {
|
|
209
217
|
try {
|
|
@@ -256,19 +264,26 @@ function instrumentMethod(
|
|
|
256
264
|
);
|
|
257
265
|
}
|
|
258
266
|
|
|
259
|
-
|
|
267
|
+
let originalResult;
|
|
268
|
+
|
|
269
|
+
const instrumentedPromise = trace.startSpan(
|
|
260
270
|
{
|
|
261
271
|
name: `${operationName} ${model}`,
|
|
262
272
|
op: utils.getSpanOperation(methodPath),
|
|
263
273
|
attributes: requestAttributes ,
|
|
264
274
|
},
|
|
265
275
|
span => {
|
|
276
|
+
originalResult = target.apply(context, args) ;
|
|
277
|
+
|
|
266
278
|
if (options.recordInputs && params) {
|
|
267
279
|
addPrivateRequestAttributes(span, params);
|
|
268
280
|
}
|
|
269
281
|
|
|
270
|
-
return
|
|
271
|
-
|
|
282
|
+
return originalResult.then(
|
|
283
|
+
result => {
|
|
284
|
+
addResponseAttributes(span, result , options.recordOutputs);
|
|
285
|
+
return result;
|
|
286
|
+
},
|
|
272
287
|
error => {
|
|
273
288
|
_exports.captureException(error, {
|
|
274
289
|
mechanism: {
|
|
@@ -279,12 +294,13 @@ function instrumentMethod(
|
|
|
279
294
|
},
|
|
280
295
|
},
|
|
281
296
|
});
|
|
297
|
+
throw error;
|
|
282
298
|
},
|
|
283
|
-
() => {},
|
|
284
|
-
result => addResponseAttributes(span, result , options.recordOutputs),
|
|
285
299
|
);
|
|
286
300
|
},
|
|
287
301
|
);
|
|
302
|
+
|
|
303
|
+
return utils.wrapPromiseWithMethods(originalResult, instrumentedPromise, 'auto.ai.anthropic');
|
|
288
304
|
},
|
|
289
305
|
}) ;
|
|
290
306
|
}
|