Skip to content

Commit ae2692e

Browse files
authored
Merge branch 'develop' into cg-metrics-node-exports
2 parents ae496b2 + c2a53e7 commit ae2692e

File tree

8 files changed

+147
-18
lines changed

8 files changed

+147
-18
lines changed

dev-packages/bundler-tests/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@sentry-internal/bundler-tests",
3-
"version": "10.23.0",
3+
"version": "10.24.0",
44
"description": "Bundler tests for Sentry Browser SDK",
55
"repository": "git://github.com/getsentry/sentry-javascript.git",
66
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/bundler-tests",
@@ -12,7 +12,7 @@
1212
"test": "vitest run"
1313
},
1414
"dependencies": {
15-
"@sentry/browser": "10.23.0",
15+
"@sentry/browser": "10.24.0",
1616
"webpack": "^5.0.0",
1717
"rollup": "^4.0.0",
1818
"vite": "^5.0.0",

dev-packages/bundler-tests/tests/bundling.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,12 +133,12 @@ describe('spotlight', () => {
133133
for (const [name, bundler] of cases) {
134134
test(`${name} development bundle contains spotlight`, async () => {
135135
const code = await bundler('development');
136-
expect(code).includes(SPOTLIGHT_URL);
136+
expect(code).toContain(SPOTLIGHT_URL);
137137
});
138138

139139
test(`${name} production bundle does not contain spotlight`, async () => {
140140
const code = await bundler('production');
141-
expect(code).not.includes(SPOTLIGHT_URL);
141+
expect(code).not.toContain(SPOTLIGHT_URL);
142142
});
143143
}
144144
});

dev-packages/bundler-tests/webpack.config.js

Lines changed: 0 additions & 10 deletions
This file was deleted.

dev-packages/node-integration-tests/suites/tracing/openai/scenario-message-truncation.mjs renamed to dev-packages/node-integration-tests/suites/tracing/openai/scenario-message-truncation-completions.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class MockOpenAI {
1212
await new Promise(resolve => setTimeout(resolve, 10));
1313

1414
return {
15-
id: 'chatcmpl-truncation-test',
15+
id: 'chatcmpl-completions-truncation-test',
1616
object: 'chat.completion',
1717
created: 1677652288,
1818
model: params.model,
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { instrumentOpenAiClient } from '@sentry/core';
2+
import * as Sentry from '@sentry/node';
3+
4+
class MockOpenAI {
5+
constructor(config) {
6+
this.apiKey = config.apiKey;
7+
8+
this.responses = {
9+
create: async params => {
10+
// Simulate processing time
11+
await new Promise(resolve => setTimeout(resolve, 10));
12+
13+
return {
14+
id: 'chatcmpl-responses-truncation-test',
15+
object: 'response',
16+
created_at: 1677652288,
17+
status: 'completed',
18+
error: null,
19+
incomplete_details: null,
20+
instructions: null,
21+
max_output_tokens: null,
22+
model: params.model,
23+
output: [
24+
{
25+
type: 'message',
26+
id: 'message-123',
27+
status: 'completed',
28+
role: 'assistant',
29+
content: [
30+
{
31+
type: 'output_text',
32+
text: 'Response to truncated messages',
33+
annotations: [],
34+
},
35+
],
36+
},
37+
],
38+
parallel_tool_calls: true,
39+
previous_response_id: null,
40+
reasoning: {
41+
effort: null,
42+
summary: null,
43+
},
44+
store: true,
45+
temperature: params.temperature,
46+
text: {
47+
format: {
48+
type: 'text',
49+
},
50+
},
51+
tool_choice: 'auto',
52+
tools: [],
53+
top_p: 1.0,
54+
truncation: 'disabled',
55+
usage: {
56+
input_tokens: 10,
57+
input_tokens_details: {
58+
cached_tokens: 0,
59+
},
60+
output_tokens: 15,
61+
output_tokens_details: {
62+
reasoning_tokens: 0,
63+
},
64+
total_tokens: 25,
65+
},
66+
user: null,
67+
metadata: {},
68+
};
69+
},
70+
};
71+
}
72+
}
73+
74+
async function run() {
75+
await Sentry.startSpan({ op: 'function', name: 'main' }, async () => {
76+
const mockClient = new MockOpenAI({
77+
apiKey: 'mock-api-key',
78+
});
79+
80+
const client = instrumentOpenAiClient(mockClient);
81+
82+
// Create 1 large message that gets truncated to fit within the 20KB limit
83+
const largeContent = 'A'.repeat(25000) + 'B'.repeat(25000); // ~50KB gets truncated to include only As
84+
85+
await client.responses.create({
86+
model: 'gpt-3.5-turbo',
87+
input: largeContent,
88+
temperature: 0.7,
89+
});
90+
});
91+
}
92+
93+
run();

dev-packages/node-integration-tests/suites/tracing/openai/test.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ describe('OpenAI integration', () => {
400400

401401
createEsmAndCjsTests(
402402
__dirname,
403-
'scenario-message-truncation.mjs',
403+
'scenario-message-truncation-completions.mjs',
404404
'instrument-with-pii.mjs',
405405
(createRunner, test) => {
406406
test('truncates messages when they exceed byte limit - keeps only last message and crops it', async () => {
@@ -433,4 +433,40 @@ describe('OpenAI integration', () => {
433433
});
434434
},
435435
);
436+
437+
createEsmAndCjsTests(
438+
__dirname,
439+
'scenario-message-truncation-responses.mjs',
440+
'instrument-with-pii.mjs',
441+
(createRunner, test) => {
442+
test('truncates string inputs when they exceed byte limit', async () => {
443+
await createRunner()
444+
.ignore('event')
445+
.expect({
446+
transaction: {
447+
transaction: 'main',
448+
spans: expect.arrayContaining([
449+
expect.objectContaining({
450+
data: expect.objectContaining({
451+
'gen_ai.operation.name': 'responses',
452+
'sentry.op': 'gen_ai.responses',
453+
'sentry.origin': 'auto.ai.openai',
454+
'gen_ai.system': 'openai',
455+
'gen_ai.request.model': 'gpt-3.5-turbo',
456+
// Messages should be present and should include truncated string input (contains only As)
457+
'gen_ai.request.messages': expect.stringMatching(/^A+$/),
458+
}),
459+
description: 'responses gpt-3.5-turbo',
460+
op: 'gen_ai.responses',
461+
origin: 'auto.ai.openai',
462+
status: 'ok',
463+
}),
464+
]),
465+
},
466+
})
467+
.start()
468+
.completed();
469+
});
470+
},
471+
);
436472
});

packages/core/src/utils/ai/messageTruncation.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,3 +294,13 @@ export function truncateMessagesByBytes(messages: unknown[], maxBytes: number):
294294
export function truncateGenAiMessages(messages: unknown[]): unknown[] {
295295
return truncateMessagesByBytes(messages, DEFAULT_GEN_AI_MESSAGES_BYTE_LIMIT);
296296
}
297+
298+
/**
299+
* Truncate GenAI string input using the default byte limit.
300+
*
301+
* @param input - The string to truncate
302+
* @returns Truncated string
303+
*/
304+
export function truncateGenAiStringInput(input: string): string {
305+
return truncateTextByBytes(input, DEFAULT_GEN_AI_MESSAGES_BYTE_LIMIT);
306+
}

packages/core/src/utils/ai/utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE,
88
GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE,
99
} from './gen-ai-attributes';
10-
import { truncateGenAiMessages } from './messageTruncation';
10+
import { truncateGenAiMessages, truncateGenAiStringInput } from './messageTruncation';
1111
/**
1212
* Maps AI method paths to Sentry operation name
1313
*/
@@ -95,7 +95,7 @@ export function setTokenUsageAttributes(
9595
export function getTruncatedJsonString<T>(value: T | T[]): string {
9696
if (typeof value === 'string') {
9797
// Some values are already JSON strings, so we don't need to duplicate the JSON parsing
98-
return value;
98+
return truncateGenAiStringInput(value);
9999
}
100100
if (Array.isArray(value)) {
101101
// truncateGenAiMessages returns an array of strings, so we need to stringify it

0 commit comments

Comments
 (0)