Skip to content

Commit 9ba6430

Browse files
committed
move stuff to core
1 parent 301e032 commit 9ba6430

File tree

5 files changed

+200
-388
lines changed

5 files changed

+200
-388
lines changed

packages/cloudflare/src/integrations/tracing/vercelai/index.ts

Lines changed: 6 additions & 198 deletions
Original file line numberDiff line numberDiff line change
@@ -8,37 +8,17 @@
88
* and users have to manually these to get spans.
99
*/
1010

11-
/* eslint-disable @typescript-eslint/no-dynamic-delete */
12-
/* eslint-disable complexity */
13-
import type { Client, IntegrationFn } from '@sentry/core';
14-
import { defineIntegration, SEMANTIC_ATTRIBUTE_SENTRY_OP, spanToJSON } from '@sentry/core';
15-
import { addOriginToSpan } from '../../../utils/addOriginToSpan';
11+
import type { Client, IntegrationFn} from '@sentry/core';
12+
import { defineIntegration,processVercelAiSpan } from '@sentry/core';
1613
import type { modulesIntegration } from '../../modules';
17-
import {
18-
AI_MODEL_ID_ATTRIBUTE,
19-
AI_MODEL_PROVIDER_ATTRIBUTE,
20-
AI_PROMPT_ATTRIBUTE,
21-
AI_PROMPT_MESSAGES_ATTRIBUTE,
22-
AI_PROMPT_TOOLS_ATTRIBUTE,
23-
AI_RESPONSE_TEXT_ATTRIBUTE,
24-
AI_RESPONSE_TOOL_CALLS_ATTRIBUTE,
25-
AI_TELEMETRY_FUNCTION_ID_ATTRIBUTE,
26-
AI_TOOL_CALL_ID_ATTRIBUTE,
27-
AI_TOOL_CALL_NAME_ATTRIBUTE,
28-
AI_USAGE_COMPLETION_TOKENS_ATTRIBUTE,
29-
AI_USAGE_PROMPT_TOKENS_ATTRIBUTE,
30-
GEN_AI_RESPONSE_MODEL_ATTRIBUTE,
31-
GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE,
32-
GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE,
33-
} from './ai_sdk_attributes';
3414
import { INTEGRATION_NAME } from './constants';
3515
import type { VercelAiOptions } from './types';
3616

3717
/**
3818
* Determines if the integration should be forced based on environment and package availability.
3919
* Returns true if the 'ai' package is available.
4020
*/
41-
function shouldForceIntegration(client: Client): boolean {
21+
function shouldRunIntegration(client: Client): boolean {
4222
const modules = client.getIntegrationByName<ReturnType<typeof modulesIntegration>>('Modules');
4323
return !!modules?.getModules?.()?.ai;
4424
}
@@ -47,173 +27,12 @@ const _vercelAIIntegration = ((options: VercelAiOptions = {}) => {
4727
return {
4828
name: INTEGRATION_NAME,
4929
options,
50-
afterAllSetup(client) {
30+
setup(client) {
5131
function registerProcessors(): void {
52-
client.on('spanStart', span => {
53-
const { data: attributes, description: name } = spanToJSON(span);
54-
55-
if (!name) {
56-
return;
57-
}
58-
59-
// Tool call spans
60-
// https://ai-sdk.dev/docs/ai-sdk-core/telemetry#tool-call-spans
61-
if (
62-
attributes[AI_TOOL_CALL_NAME_ATTRIBUTE] &&
63-
attributes[AI_TOOL_CALL_ID_ATTRIBUTE] &&
64-
name === 'ai.toolCall'
65-
) {
66-
addOriginToSpan(span, 'auto.vercelai.otel');
67-
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'gen_ai.execute_tool');
68-
span.setAttribute('gen_ai.tool.call.id', attributes[AI_TOOL_CALL_ID_ATTRIBUTE]);
69-
span.setAttribute('gen_ai.tool.name', attributes[AI_TOOL_CALL_NAME_ATTRIBUTE]);
70-
span.updateName(`execute_tool ${attributes[AI_TOOL_CALL_NAME_ATTRIBUTE]}`);
71-
return;
72-
}
73-
74-
// The AI and Provider must be defined for generate, stream, and embed spans.
75-
// The id of the model
76-
const aiModelId = attributes[AI_MODEL_ID_ATTRIBUTE];
77-
// the provider of the model
78-
const aiModelProvider = attributes[AI_MODEL_PROVIDER_ATTRIBUTE];
79-
if (typeof aiModelId !== 'string' || typeof aiModelProvider !== 'string' || !aiModelId || !aiModelProvider) {
80-
return;
81-
}
82-
83-
addOriginToSpan(span, 'auto.vercelai.otel');
84-
85-
const nameWthoutAi = name.replace('ai.', '');
86-
span.setAttribute('ai.pipeline.name', nameWthoutAi);
87-
span.updateName(nameWthoutAi);
88-
89-
// If a Telemetry name is set and it is a pipeline span, use that as the operation name
90-
const functionId = attributes[AI_TELEMETRY_FUNCTION_ID_ATTRIBUTE];
91-
if (functionId && typeof functionId === 'string' && name.split('.').length - 1 === 1) {
92-
span.updateName(`${nameWthoutAi} ${functionId}`);
93-
span.setAttribute('ai.pipeline.name', functionId);
94-
}
95-
96-
if (attributes[AI_PROMPT_ATTRIBUTE]) {
97-
span.setAttribute('gen_ai.prompt', attributes[AI_PROMPT_ATTRIBUTE]);
98-
}
99-
if (attributes[AI_MODEL_ID_ATTRIBUTE] && !attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]) {
100-
span.setAttribute(GEN_AI_RESPONSE_MODEL_ATTRIBUTE, attributes[AI_MODEL_ID_ATTRIBUTE]);
101-
}
102-
span.setAttribute('ai.streaming', name.includes('stream'));
103-
104-
// Generate Spans
105-
if (name === 'ai.generateText') {
106-
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'gen_ai.invoke_agent');
107-
return;
108-
}
109-
110-
if (name === 'ai.generateText.doGenerate') {
111-
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'gen_ai.generate_text');
112-
span.updateName(`generate_text ${attributes[AI_MODEL_ID_ATTRIBUTE]}`);
113-
return;
114-
}
115-
116-
if (name === 'ai.streamText') {
117-
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'gen_ai.invoke_agent');
118-
return;
119-
}
120-
121-
if (name === 'ai.streamText.doStream') {
122-
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'gen_ai.stream_text');
123-
span.updateName(`stream_text ${attributes[AI_MODEL_ID_ATTRIBUTE]}`);
124-
return;
125-
}
126-
127-
if (name === 'ai.generateObject') {
128-
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'gen_ai.invoke_agent');
129-
return;
130-
}
131-
132-
if (name === 'ai.generateObject.doGenerate') {
133-
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'gen_ai.generate_object');
134-
span.updateName(`generate_object ${attributes[AI_MODEL_ID_ATTRIBUTE]}`);
135-
return;
136-
}
137-
138-
if (name === 'ai.streamObject') {
139-
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'gen_ai.invoke_agent');
140-
return;
141-
}
142-
143-
if (name === 'ai.streamObject.doStream') {
144-
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'gen_ai.stream_object');
145-
span.updateName(`stream_object ${attributes[AI_MODEL_ID_ATTRIBUTE]}`);
146-
return;
147-
}
148-
149-
if (name === 'ai.embed') {
150-
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'gen_ai.invoke_agent');
151-
return;
152-
}
153-
154-
if (name === 'ai.embed.doEmbed') {
155-
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'gen_ai.embed');
156-
span.updateName(`embed ${attributes[AI_MODEL_ID_ATTRIBUTE]}`);
157-
return;
158-
}
159-
160-
if (name === 'ai.embedMany') {
161-
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'gen_ai.invoke_agent');
162-
return;
163-
}
164-
165-
if (name === 'ai.embedMany.doEmbed') {
166-
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'gen_ai.embed_many');
167-
span.updateName(`embed_many ${attributes[AI_MODEL_ID_ATTRIBUTE]}`);
168-
return;
169-
}
170-
171-
if (name.startsWith('ai.stream')) {
172-
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'ai.run');
173-
return;
174-
}
175-
});
176-
177-
client.addEventProcessor(event => {
178-
if (event.type === 'transaction' && event.spans?.length) {
179-
for (const span of event.spans) {
180-
const { data: attributes, description: name } = span;
181-
182-
if (!name || span.origin !== 'auto.vercelai.otel') {
183-
continue;
184-
}
185-
186-
renameAttributeKey(
187-
attributes,
188-
AI_USAGE_COMPLETION_TOKENS_ATTRIBUTE,
189-
GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE,
190-
);
191-
renameAttributeKey(attributes, AI_USAGE_PROMPT_TOKENS_ATTRIBUTE, GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE);
192-
if (
193-
typeof attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE] === 'number' &&
194-
typeof attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE] === 'number'
195-
) {
196-
attributes['gen_ai.usage.total_tokens'] =
197-
attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE] + attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE];
198-
}
199-
200-
// Rename AI SDK attributes to standardized gen_ai attributes
201-
renameAttributeKey(attributes, AI_PROMPT_MESSAGES_ATTRIBUTE, 'gen_ai.request.messages');
202-
renameAttributeKey(attributes, AI_RESPONSE_TEXT_ATTRIBUTE, 'gen_ai.response.text');
203-
renameAttributeKey(attributes, AI_RESPONSE_TOOL_CALLS_ATTRIBUTE, 'gen_ai.response.tool_calls');
204-
renameAttributeKey(attributes, AI_PROMPT_TOOLS_ATTRIBUTE, 'gen_ai.request.available_tools');
205-
}
206-
}
207-
208-
return event;
209-
});
32+
client.on('spanEnd', processVercelAiSpan);
21033
}
21134

212-
// Auto-detect if we should force the integration when running with 'ai' package available
213-
// Note that this can only be detected if the 'Modules' integration is available, and running in CJS mode
214-
const shouldForce = options.force ?? shouldForceIntegration(client);
215-
216-
if (shouldForce) {
35+
if (options.force || shouldRunIntegration(client)) {
21736
registerProcessors();
21837
}
21938
},
@@ -259,14 +78,3 @@ const _vercelAIIntegration = ((options: VercelAiOptions = {}) => {
25978
* });
26079
*/
26180
export const vercelAIIntegration = defineIntegration(_vercelAIIntegration);
262-
263-
/**
264-
* Renames an attribute key in the provided attributes object if the old key exists.
265-
* This function safely handles null and undefined values.
266-
*/
267-
function renameAttributeKey(attributes: Record<string, unknown>, oldKey: string, newKey: string): void {
268-
if (attributes[oldKey] != null) {
269-
attributes[newKey] = attributes[oldKey];
270-
delete attributes[oldKey];
271-
}
272-
}

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ export { captureFeedback } from './feedback';
123123
export type { ReportDialogOptions } from './report-dialog';
124124
export { _INTERNAL_captureLog, _INTERNAL_flushLogsBuffer, _INTERNAL_captureSerializedLog } from './logs/exports';
125125
export { consoleLoggingIntegration } from './logs/console-integration';
126+
export { processVercelAiSpan } from './utils/vercel-ai';
126127

127128
export type { FeatureFlag } from './utils/featureFlags';
128129
export {

0 commit comments

Comments
 (0)