Skip to content

Commit 0ef32bf

Browse files
authored
Merge branch 'develop' into genai-msg-limit
2 parents ea474d5 + 40bcc3d commit 0ef32bf

File tree

6 files changed

+82
-70
lines changed

6 files changed

+82
-70
lines changed

dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTraceSampling/init.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ window.Sentry = Sentry;
55
// Force this so that the initial sampleRand is consistent
66
Math.random = () => 0.45;
77

8+
// Polyfill crypto.randomUUID
9+
crypto.randomUUID = function randomUUID() {
10+
return ([1e7] + 1e3 + 4e3 + 8e3 + 1e11).replace(
11+
/[018]/g,
12+
// eslint-disable-next-line no-bitwise
13+
c => (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16),
14+
);
15+
};
16+
817
Sentry.init({
918
dsn: 'https://[email protected]/1337',
1019
integrations: [Sentry.browserTracingIntegration()],

packages/core/src/utils/ai/gen-ai-attributes.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,16 @@ export const GEN_AI_RESPONSE_STREAMING_ATTRIBUTE = 'gen_ai.response.streaming';
129129
*/
130130
export const GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE = 'gen_ai.response.tool_calls';
131131

132+
/**
133+
* The number of cache write input tokens used
134+
*/
135+
export const GEN_AI_USAGE_INPUT_TOKENS_CACHE_WRITE_ATTRIBUTE = 'gen_ai.usage.input_tokens.cache_write';
136+
137+
/**
138+
* The number of cached input tokens that were used
139+
*/
140+
export const GEN_AI_USAGE_INPUT_TOKENS_CACHED_ATTRIBUTE = 'gen_ai.usage.input_tokens.cached';
141+
132142
// =============================================================================
133143
// OPENAI-SPECIFIC ATTRIBUTES
134144
// =============================================================================

packages/core/src/utils/misc.ts

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { snipLine } from './string';
77
import { GLOBAL_OBJ } from './worldwide';
88

99
interface CryptoInternal {
10-
getRandomValues(array: Uint8Array): Uint8Array;
1110
randomUUID?(): string;
1211
}
1312

@@ -22,37 +21,34 @@ function getCrypto(): CryptoInternal | undefined {
2221
return gbl.crypto || gbl.msCrypto;
2322
}
2423

24+
let emptyUuid: string | undefined;
25+
26+
function getRandomByte(): number {
27+
return Math.random() * 16;
28+
}
29+
2530
/**
2631
* UUID4 generator
2732
* @param crypto Object that provides the crypto API.
2833
* @returns string Generated UUID4.
2934
*/
3035
export function uuid4(crypto = getCrypto()): string {
31-
let getRandomByte = (): number => Math.random() * 16;
3236
try {
3337
if (crypto?.randomUUID) {
3438
return crypto.randomUUID().replace(/-/g, '');
3539
}
36-
if (crypto?.getRandomValues) {
37-
getRandomByte = () => {
38-
// crypto.getRandomValues might return undefined instead of the typed array
39-
// in old Chromium versions (e.g. 23.0.1235.0 (151422))
40-
// However, `typedArray` is still filled in-place.
41-
// @see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues#typedarray
42-
const typedArray = new Uint8Array(1);
43-
crypto.getRandomValues(typedArray);
44-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
45-
return typedArray[0]!;
46-
};
47-
}
4840
} catch {
4941
// some runtimes can crash invoking crypto
5042
// https://github.com/getsentry/sentry-javascript/issues/8935
5143
}
5244

53-
// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523
54-
// Concatenating the following numbers as strings results in '10000000100040008000100000000000'
55-
return (([1e7] as unknown as string) + 1e3 + 4e3 + 8e3 + 1e11).replace(/[018]/g, c =>
45+
if (!emptyUuid) {
46+
// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523
47+
// Concatenating the following numbers as strings results in '10000000100040008000100000000000'
48+
emptyUuid = ([1e7] as unknown as string) + 1e3 + 4e3 + 8e3 + 1e11;
49+
}
50+
51+
return emptyUuid.replace(/[018]/g, c =>
5652
// eslint-disable-next-line no-bitwise
5753
((c as unknown as number) ^ ((getRandomByte() & 15) >> ((c as unknown as number) / 4))).toString(16),
5854
);

packages/core/src/utils/vercel-ai/index.ts

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import type { Client } from '../../client';
22
import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes';
33
import type { Event } from '../../types-hoist/event';
44
import type { Span, SpanAttributes, SpanAttributeValue, SpanJSON, SpanOrigin } from '../../types-hoist/span';
5+
import {
6+
GEN_AI_USAGE_INPUT_TOKENS_CACHE_WRITE_ATTRIBUTE,
7+
GEN_AI_USAGE_INPUT_TOKENS_CACHED_ATTRIBUTE,
8+
} from '../ai/gen-ai-attributes';
59
import { truncateGenAiMessages } from '../ai/messageTruncation';
610
import { spanToJSON } from '../spanUtils';
711
import { toolCallSpanMap } from './constants';
@@ -24,6 +28,7 @@ import {
2428
AI_TOOL_CALL_ID_ATTRIBUTE,
2529
AI_TOOL_CALL_NAME_ATTRIBUTE,
2630
AI_TOOL_CALL_RESULT_ATTRIBUTE,
31+
AI_USAGE_CACHED_INPUT_TOKENS_ATTRIBUTE,
2732
AI_USAGE_COMPLETION_TOKENS_ATTRIBUTE,
2833
AI_USAGE_PROMPT_TOKENS_ATTRIBUTE,
2934
GEN_AI_RESPONSE_MODEL_ATTRIBUTE,
@@ -108,6 +113,7 @@ function processEndedVercelAiSpan(span: SpanJSON): void {
108113

109114
renameAttributeKey(attributes, AI_USAGE_COMPLETION_TOKENS_ATTRIBUTE, GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE);
110115
renameAttributeKey(attributes, AI_USAGE_PROMPT_TOKENS_ATTRIBUTE, GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE);
116+
renameAttributeKey(attributes, AI_USAGE_CACHED_INPUT_TOKENS_ATTRIBUTE, GEN_AI_USAGE_INPUT_TOKENS_CACHED_ATTRIBUTE);
111117

112118
if (
113119
typeof attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE] === 'number' &&
@@ -294,7 +300,7 @@ function addProviderMetadataToAttributes(attributes: SpanAttributes): void {
294300
if (providerMetadataObject.openai) {
295301
setAttributeIfDefined(
296302
attributes,
297-
'gen_ai.usage.input_tokens.cached',
303+
GEN_AI_USAGE_INPUT_TOKENS_CACHED_ATTRIBUTE,
298304
providerMetadataObject.openai.cachedPromptTokens,
299305
);
300306
setAttributeIfDefined(
@@ -316,35 +322,34 @@ function addProviderMetadataToAttributes(attributes: SpanAttributes): void {
316322
}
317323

318324
if (providerMetadataObject.anthropic) {
319-
setAttributeIfDefined(
320-
attributes,
321-
'gen_ai.usage.input_tokens.cached',
322-
providerMetadataObject.anthropic.cacheReadInputTokens,
323-
);
324-
setAttributeIfDefined(
325-
attributes,
326-
'gen_ai.usage.input_tokens.cache_write',
327-
providerMetadataObject.anthropic.cacheCreationInputTokens,
328-
);
325+
const cachedInputTokens =
326+
providerMetadataObject.anthropic.usage?.cache_read_input_tokens ??
327+
providerMetadataObject.anthropic.cacheReadInputTokens;
328+
setAttributeIfDefined(attributes, GEN_AI_USAGE_INPUT_TOKENS_CACHED_ATTRIBUTE, cachedInputTokens);
329+
330+
const cacheWriteInputTokens =
331+
providerMetadataObject.anthropic.usage?.cache_creation_input_tokens ??
332+
providerMetadataObject.anthropic.cacheCreationInputTokens;
333+
setAttributeIfDefined(attributes, GEN_AI_USAGE_INPUT_TOKENS_CACHE_WRITE_ATTRIBUTE, cacheWriteInputTokens);
329334
}
330335

331336
if (providerMetadataObject.bedrock?.usage) {
332337
setAttributeIfDefined(
333338
attributes,
334-
'gen_ai.usage.input_tokens.cached',
339+
GEN_AI_USAGE_INPUT_TOKENS_CACHED_ATTRIBUTE,
335340
providerMetadataObject.bedrock.usage.cacheReadInputTokens,
336341
);
337342
setAttributeIfDefined(
338343
attributes,
339-
'gen_ai.usage.input_tokens.cache_write',
344+
GEN_AI_USAGE_INPUT_TOKENS_CACHE_WRITE_ATTRIBUTE,
340345
providerMetadataObject.bedrock.usage.cacheWriteInputTokens,
341346
);
342347
}
343348

344349
if (providerMetadataObject.deepseek) {
345350
setAttributeIfDefined(
346351
attributes,
347-
'gen_ai.usage.input_tokens.cached',
352+
GEN_AI_USAGE_INPUT_TOKENS_CACHED_ATTRIBUTE,
348353
providerMetadataObject.deepseek.promptCacheHitTokens,
349354
);
350355
setAttributeIfDefined(

packages/core/src/utils/vercel-ai/vercel-ai-attributes.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,14 @@ export const AI_RESPONSE_PROVIDER_METADATA_ATTRIBUTE = 'ai.response.providerMeta
288288
*/
289289
export const AI_SETTINGS_MAX_RETRIES_ATTRIBUTE = 'ai.settings.maxRetries';
290290

291+
/**
292+
* Basic LLM span information
293+
* Multiple spans
294+
*
295+
* The number of cached input tokens that were used
296+
* @see https://ai-sdk.dev/docs/ai-sdk-core/telemetry#basic-llm-span-information
297+
*/
298+
export const AI_USAGE_CACHED_INPUT_TOKENS_ATTRIBUTE = 'ai.usage.cachedInputTokens';
291299
/**
292300
* Basic LLM span information
293301
* Multiple spans
@@ -863,6 +871,21 @@ interface AnthropicProviderMetadata {
863871
* @see https://ai-sdk.dev/providers/ai-sdk-providers/anthropic#cache-control
864872
*/
865873
cacheReadInputTokens?: number;
874+
875+
/**
876+
* Usage metrics for the Anthropic model.
877+
*/
878+
usage?: {
879+
input_tokens: number;
880+
cache_creation_input_tokens?: number;
881+
cache_read_input_tokens?: number;
882+
cache_creation?: {
883+
ephemeral_5m_input_tokens?: number;
884+
ephemeral_1h_input_tokens?: number;
885+
};
886+
output_tokens?: number;
887+
service_tier?: string;
888+
};
866889
}
867890

868891
/**

packages/core/test/lib/utils/misc.test.ts

Lines changed: 8 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -292,36 +292,29 @@ describe('checkOrSetAlreadyCaught()', () => {
292292

293293
describe('uuid4 generation', () => {
294294
const uuid4Regex = /^[0-9A-F]{12}[4][0-9A-F]{3}[89AB][0-9A-F]{15}$/i;
295-
it('returns valid uuid v4 ids via Math.random', () => {
295+
it('returns valid and unique uuid v4 ids via Math.random', () => {
296+
const uuids = new Set<string>();
296297
for (let index = 0; index < 1_000; index++) {
297-
expect(uuid4()).toMatch(uuid4Regex);
298-
}
299-
});
300-
301-
it('returns valid uuid v4 ids via crypto.getRandomValues', () => {
302-
// eslint-disable-next-line @typescript-eslint/no-var-requires
303-
const cryptoMod = require('crypto');
304-
305-
const crypto = { getRandomValues: cryptoMod.getRandomValues };
306-
307-
for (let index = 0; index < 1_000; index++) {
308-
expect(uuid4(crypto)).toMatch(uuid4Regex);
298+
const id = uuid4();
299+
expect(id).toMatch(uuid4Regex);
300+
uuids.add(id);
309301
}
302+
expect(uuids.size).toBe(1_000);
310303
});
311304

312305
it('returns valid uuid v4 ids via crypto.randomUUID', () => {
313306
// eslint-disable-next-line @typescript-eslint/no-var-requires
314307
const cryptoMod = require('crypto');
315308

316-
const crypto = { getRandomValues: cryptoMod.getRandomValues, randomUUID: cryptoMod.randomUUID };
309+
const crypto = { randomUUID: cryptoMod.randomUUID };
317310

318311
for (let index = 0; index < 1_000; index++) {
319312
expect(uuid4(crypto)).toMatch(uuid4Regex);
320313
}
321314
});
322315

323316
it("return valid uuid v4 even if crypto doesn't exists", () => {
324-
const crypto = { getRandomValues: undefined, randomUUID: undefined };
317+
const crypto = { randomUUID: undefined };
325318

326319
for (let index = 0; index < 1_000; index++) {
327320
expect(uuid4(crypto)).toMatch(uuid4Regex);
@@ -330,9 +323,6 @@ describe('uuid4 generation', () => {
330323

331324
it('return valid uuid v4 even if crypto invoked causes an error', () => {
332325
const crypto = {
333-
getRandomValues: () => {
334-
throw new Error('yo');
335-
},
336326
randomUUID: () => {
337327
throw new Error('yo');
338328
},
@@ -342,25 +332,4 @@ describe('uuid4 generation', () => {
342332
expect(uuid4(crypto)).toMatch(uuid4Regex);
343333
}
344334
});
345-
346-
// Corner case related to crypto.getRandomValues being only
347-
// semi-implemented (e.g. Chromium 23.0.1235.0 (151422))
348-
it('returns valid uuid v4 even if crypto.getRandomValues does not return a typed array', () => {
349-
// eslint-disable-next-line @typescript-eslint/no-var-requires
350-
const cryptoMod = require('crypto');
351-
352-
const getRandomValues = (typedArray: Uint8Array) => {
353-
if (cryptoMod.getRandomValues) {
354-
cryptoMod.getRandomValues(typedArray);
355-
}
356-
};
357-
358-
const crypto = { getRandomValues };
359-
360-
for (let index = 0; index < 1_000; index++) {
361-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
362-
// @ts-ignore - we are testing a corner case
363-
expect(uuid4(crypto)).toMatch(uuid4Regex);
364-
}
365-
});
366335
});

0 commit comments

Comments
 (0)