From 53aee53cc8c80b429b00a9ea8ac2c42c442e72f7 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Mon, 3 Aug 2026 16:40:01 +0200 Subject: [PATCH] ref(vercel-ai)!: Move Vercel AI integration to `@sentry/server-utils` Move the Vercel AI gen-ai instrumentation from `@sentry/core` to `@sentry/server-utils/src/ai/vercel-ai`, consuming the shared `ai/core` base. This is the last provider move. The public helpers (`addVercelAiProcessors`, `getProviderMetadataAttributes`) and the `_INTERNAL_*` tool-call-context symbols are re-exported from `@sentry/server-utils`; cloudflare/deno/vercel-edge consume them from there, so the platform SDK public API is unchanged. The existing `src/vercel-ai/` diagnostics-channel subscriber layer now imports the moved provider code from `../ai/vercel-ai` and `../ai/core/utils` instead of `@sentry/core`. With both vercel-ai and workers-ai now in server-utils, vercel-ai re-couples to `../workers-ai/constants` directly (the temporary inline in core's copy is gone with the deleted file). As with the other providers, `vercel-edge` re-exports from `@sentry/server-utils/no-diagnostic-channels` to keep the edge build free of `node:diagnostics_channel`. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../vercelai/span-streaming-v4/test.ts | 2 +- .../vercelai/span-streaming-v6/test.ts | 2 +- .../suites/tracing/vercelai/test.ts | 2 +- .../suites/tracing/vercelai/v5/test.ts | 2 +- .../suites/tracing/vercelai/v6_v7/test.ts | 2 +- .../src/integrations/tracing/vercelai.ts | 4 +- packages/core/src/server-exports.ts | 3 - .../deno/src/integrations/tracing/vercelai.ts | 8 +- packages/server-utils/src/ai/index.ts | 1 + .../src/ai}/vercel-ai/constants.ts | 0 .../src/ai}/vercel-ai/index.ts | 24 ++--- .../src/ai}/vercel-ai/types.ts | 0 .../src/ai}/vercel-ai/utils.ts | 7 +- .../src/ai}/vercel-ai/vercel-ai-attributes.ts | 0 .../src/vercel-ai/vercel-ai-dc-subscriber.ts | 8 +- .../tracing/vercel-ai-cached-tokens.test.ts | 6 +- .../tracing/vercel-ai-parent-tokens.test.ts | 6 +- .../vercel-ai-request-messages.test.ts | 10 +- .../lib/tracing/vercel-ai-span-status.test.ts | 6 +- .../test/ai/lib/tracing/workers-ai.test.ts | 27 +---- .../test/ai}/lib/utils/vercelai-utils.test.ts | 2 +- packages/server-utils/test/mocks/client.ts | 98 +++++++++++++++++++ .../test/vercel-ai/skip-workers-ai.test.ts | 4 +- .../src/integrations/tracing/vercelai.ts | 3 +- 24 files changed, 151 insertions(+), 76 deletions(-) rename packages/{core/src/tracing => server-utils/src/ai}/vercel-ai/constants.ts (100%) rename packages/{core/src/tracing => server-utils/src/ai}/vercel-ai/index.ts (96%) rename packages/{core/src/tracing => server-utils/src/ai}/vercel-ai/types.ts (100%) rename packages/{core/src/tracing => server-utils/src/ai}/vercel-ai/utils.ts (98%) rename packages/{core/src/tracing => server-utils/src/ai}/vercel-ai/vercel-ai-attributes.ts (100%) rename packages/{core/test => server-utils/test/ai}/lib/tracing/vercel-ai-cached-tokens.test.ts (91%) rename packages/{core/test => server-utils/test/ai}/lib/tracing/vercel-ai-parent-tokens.test.ts (92%) rename packages/{core/test => server-utils/test/ai}/lib/tracing/vercel-ai-request-messages.test.ts (90%) rename packages/{core/test => server-utils/test/ai}/lib/tracing/vercel-ai-span-status.test.ts (84%) rename packages/{core/test => server-utils/test/ai}/lib/utils/vercelai-utils.test.ts (98%) create mode 100644 packages/server-utils/test/mocks/client.ts diff --git a/dev-packages/node-integration-tests/suites/tracing/vercelai/span-streaming-v4/test.ts b/dev-packages/node-integration-tests/suites/tracing/vercelai/span-streaming-v4/test.ts index e28aa7ddbe41..2f61639c50b6 100644 --- a/dev-packages/node-integration-tests/suites/tracing/vercelai/span-streaming-v4/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/vercelai/span-streaming-v4/test.ts @@ -16,7 +16,7 @@ import { GEN_AI_USAGE_OUTPUT_TOKENS, GEN_AI_USAGE_TOTAL_TOKENS, } from '@sentry/conventions/attributes'; -import { GEN_AI_TOOL_CALL_ID_ATTRIBUTE } from '../../../../../../packages/core/src/tracing/ai/gen-ai-attributes'; +import { GEN_AI_TOOL_CALL_ID_ATTRIBUTE } from '../../../../../../packages/server-utils/src/ai/core/gen-ai-attributes'; import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../../utils/runner'; import { getStringAttributeValue, isOrchestrionEnabled } from '../../../../utils'; diff --git a/dev-packages/node-integration-tests/suites/tracing/vercelai/span-streaming-v6/test.ts b/dev-packages/node-integration-tests/suites/tracing/vercelai/span-streaming-v6/test.ts index 220a0314a77b..4136c7e6bb70 100644 --- a/dev-packages/node-integration-tests/suites/tracing/vercelai/span-streaming-v6/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/vercelai/span-streaming-v6/test.ts @@ -16,7 +16,7 @@ import { GEN_AI_USAGE_OUTPUT_TOKENS, GEN_AI_USAGE_TOTAL_TOKENS, } from '@sentry/conventions/attributes'; -import { GEN_AI_TOOL_CALL_ID_ATTRIBUTE } from '../../../../../../packages/core/src/tracing/ai/gen-ai-attributes'; +import { GEN_AI_TOOL_CALL_ID_ATTRIBUTE } from '../../../../../../packages/server-utils/src/ai/core/gen-ai-attributes'; import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../../utils/runner'; import { isOrchestrionEnabled } from '../../../../utils'; diff --git a/dev-packages/node-integration-tests/suites/tracing/vercelai/test.ts b/dev-packages/node-integration-tests/suites/tracing/vercelai/test.ts index ca28f11c3305..d68a3eae4f5e 100644 --- a/dev-packages/node-integration-tests/suites/tracing/vercelai/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/vercelai/test.ts @@ -19,7 +19,7 @@ import { GEN_AI_USAGE_OUTPUT_TOKENS, GEN_AI_USAGE_TOTAL_TOKENS, } from '@sentry/conventions/attributes'; -import { GEN_AI_TOOL_CALL_ID_ATTRIBUTE } from '../../../../../packages/core/src/tracing/ai/gen-ai-attributes'; +import { GEN_AI_TOOL_CALL_ID_ATTRIBUTE } from '../../../../../packages/server-utils/src/ai/core/gen-ai-attributes'; import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner'; import { getStringAttributeValue, isOrchestrionEnabled } from '../../../utils'; diff --git a/dev-packages/node-integration-tests/suites/tracing/vercelai/v5/test.ts b/dev-packages/node-integration-tests/suites/tracing/vercelai/v5/test.ts index 37809bc4eeef..c11b2862da56 100644 --- a/dev-packages/node-integration-tests/suites/tracing/vercelai/v5/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/vercelai/v5/test.ts @@ -16,7 +16,7 @@ import { GEN_AI_USAGE_OUTPUT_TOKENS, GEN_AI_USAGE_TOTAL_TOKENS, } from '@sentry/conventions/attributes'; -import { GEN_AI_TOOL_CALL_ID_ATTRIBUTE } from '../../../../../../packages/core/src/tracing/ai/gen-ai-attributes'; +import { GEN_AI_TOOL_CALL_ID_ATTRIBUTE } from '../../../../../../packages/server-utils/src/ai/core/gen-ai-attributes'; import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../../utils/runner'; import { getStringAttributeValue, isOrchestrionEnabled } from '../../../../utils'; diff --git a/dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts b/dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts index 4803ce795370..f54be5f73995 100644 --- a/dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts @@ -21,7 +21,7 @@ import { GEN_AI_USAGE_OUTPUT_TOKENS, GEN_AI_USAGE_TOTAL_TOKENS, } from '@sentry/conventions/attributes'; -import { GEN_AI_TOOL_CALL_ID_ATTRIBUTE } from '../../../../../../packages/core/src/tracing/ai/gen-ai-attributes'; +import { GEN_AI_TOOL_CALL_ID_ATTRIBUTE } from '../../../../../../packages/server-utils/src/ai/core/gen-ai-attributes'; import { cleanupChildProcesses, createEsmAndCjsTests, createEsmTests } from '../../../../utils/runner'; import { isOrchestrionEnabled } from '../../../../utils'; diff --git a/packages/cloudflare/src/integrations/tracing/vercelai.ts b/packages/cloudflare/src/integrations/tracing/vercelai.ts index 242d476c342f..14658f1907a9 100644 --- a/packages/cloudflare/src/integrations/tracing/vercelai.ts +++ b/packages/cloudflare/src/integrations/tracing/vercelai.ts @@ -9,8 +9,8 @@ */ import type { IntegrationFn } from '@sentry/core'; -import { addVercelAiProcessors, defineIntegration, extendIntegration } from '@sentry/core'; -import { vercelAiIntegration, type VercelAiOptions } from '@sentry/server-utils'; +import { defineIntegration, extendIntegration } from '@sentry/core'; +import { addVercelAiProcessors, vercelAiIntegration, type VercelAiOptions } from '@sentry/server-utils'; const _vercelAIIntegration = ((options: VercelAiOptions = {}) => { const inner = vercelAiIntegration(options); diff --git a/packages/core/src/server-exports.ts b/packages/core/src/server-exports.ts index fc6b53710cf9..a664886eafa3 100644 --- a/packages/core/src/server-exports.ts +++ b/packages/core/src/server-exports.ts @@ -56,7 +56,4 @@ export type { // AI instrumentation is only supported in server runtimes, so these exports are kept out of the browser entry to // avoid shipping the AI tracing code in browser bundles. -export { addVercelAiProcessors, getProviderMetadataAttributes } from './tracing/vercel-ai'; export { getTruncatedJsonString, shouldEnableTruncation, resolveAIRecordingOptions } from './tracing/ai/utils'; -export { _INTERNAL_getSpanContextForToolCallId, _INTERNAL_cleanupToolCallSpanContext } from './tracing/vercel-ai/utils'; -export { toolCallSpanContextMap as _INTERNAL_toolCallSpanContextMap } from './tracing/vercel-ai/constants'; diff --git a/packages/deno/src/integrations/tracing/vercelai.ts b/packages/deno/src/integrations/tracing/vercelai.ts index a46ff82ae1e5..0a9dc56961d8 100644 --- a/packages/deno/src/integrations/tracing/vercelai.ts +++ b/packages/deno/src/integrations/tracing/vercelai.ts @@ -3,8 +3,12 @@ */ import type { IntegrationFn } from '@sentry/core'; -import { addVercelAiProcessors, defineIntegration, extendIntegration } from '@sentry/core'; -import { vercelAiIntegration as serverUtilsVercelAiIntegration, type VercelAiOptions } from '@sentry/server-utils'; +import { defineIntegration, extendIntegration } from '@sentry/core'; +import { + addVercelAiProcessors, + vercelAiIntegration as serverUtilsVercelAiIntegration, + type VercelAiOptions, +} from '@sentry/server-utils'; const _vercelAIIntegration = ((options: VercelAiOptions = {}) => { const inner = serverUtilsVercelAiIntegration(options); diff --git a/packages/server-utils/src/ai/index.ts b/packages/server-utils/src/ai/index.ts index 769ab826e01d..dffc8e21d027 100644 --- a/packages/server-utils/src/ai/index.ts +++ b/packages/server-utils/src/ai/index.ts @@ -10,3 +10,4 @@ export { instrumentGoogleGenAIClient } from './google-genai'; export { instrumentWorkersAiClient } from './workers-ai'; export { createLangChainCallbackHandler, instrumentLangChainEmbeddings } from './langchain'; export { instrumentStateGraph, instrumentStateGraphCompile, instrumentCreateReactAgent } from './langgraph'; +export { addVercelAiProcessors } from './vercel-ai'; diff --git a/packages/core/src/tracing/vercel-ai/constants.ts b/packages/server-utils/src/ai/vercel-ai/constants.ts similarity index 100% rename from packages/core/src/tracing/vercel-ai/constants.ts rename to packages/server-utils/src/ai/vercel-ai/constants.ts diff --git a/packages/core/src/tracing/vercel-ai/index.ts b/packages/server-utils/src/ai/vercel-ai/index.ts similarity index 96% rename from packages/core/src/tracing/vercel-ai/index.ts rename to packages/server-utils/src/ai/vercel-ai/index.ts index 3ae0a7386f06..d45104eac744 100644 --- a/packages/core/src/tracing/vercel-ai/index.ts +++ b/packages/server-utils/src/ai/vercel-ai/index.ts @@ -1,13 +1,16 @@ /* eslint-disable typescript-eslint/no-deprecated */ /* eslint-disable max-lines */ -import type { Client } from '../../client'; -import { getClient } from '../../currentScopes'; -import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes'; -import { shouldEnableTruncation } from '../ai/utils'; -import type { Event } from '../../types/event'; -import type { Span, SpanAttributes, SpanAttributeValue, SpanJSON, StreamedSpanJSON } from '../../types/span'; -import { _INTERNAL_skipAiProviderWrapping } from '../../utils/ai/providerSkip'; -import { spanToJSON } from '../../utils/spanUtils'; +import { + _INTERNAL_skipAiProviderWrapping, + getClient, + hasSpanStreamingEnabled, + SEMANTIC_ATTRIBUTE_SENTRY_OP, + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + spanToJSON, +} from '@sentry/core'; +import type { Client, Event, Span, SpanAttributes, SpanAttributeValue, SpanJSON, StreamedSpanJSON } from '@sentry/core'; +import { shouldEnableTruncation } from '../core/utils'; +import { WORKERS_AI_INTEGRATION_NAME } from '../workers-ai/constants'; import { GEN_AI_CONVERSATION_ID, GEN_AI_EMBEDDINGS_INPUT, @@ -27,10 +30,9 @@ import { GEN_AI_USAGE_OUTPUT_TOKENS, GEN_AI_USAGE_TOTAL_TOKENS, } from '@sentry/conventions/attributes'; -import { GEN_AI_TOOL_CALL_ID_ATTRIBUTE } from '../ai/gen-ai-attributes'; +import { GEN_AI_TOOL_CALL_ID_ATTRIBUTE } from '../core/gen-ai-attributes'; import { SPAN_TO_OPERATION_NAME, toolCallSpanContextMap, toolDescriptionMap } from './constants'; import type { TokenSummary } from './types'; -import { hasSpanStreamingEnabled } from '../spans/hasSpanStreamingEnabled'; import { accumulateTokensForParent, applyAccumulatedTokens, @@ -64,8 +66,6 @@ import { OPERATION_NAME_ATTRIBUTE, } from './vercel-ai-attributes'; -const WORKERS_AI_INTEGRATION_NAME = 'WorkersAI'; - /** * Post-process spans emitted by the Vercel AI SDK. * This is supposed to be used in `client.on('spanStart', ...) diff --git a/packages/core/src/tracing/vercel-ai/types.ts b/packages/server-utils/src/ai/vercel-ai/types.ts similarity index 100% rename from packages/core/src/tracing/vercel-ai/types.ts rename to packages/server-utils/src/ai/vercel-ai/types.ts diff --git a/packages/core/src/tracing/vercel-ai/utils.ts b/packages/server-utils/src/ai/vercel-ai/utils.ts similarity index 98% rename from packages/core/src/tracing/vercel-ai/utils.ts rename to packages/server-utils/src/ai/vercel-ai/utils.ts index 19f593808e64..89672aa1d4a9 100644 --- a/packages/core/src/tracing/vercel-ai/utils.ts +++ b/packages/server-utils/src/ai/vercel-ai/utils.ts @@ -1,6 +1,6 @@ /* eslint-disable typescript-eslint/no-deprecated */ -import type { TraceContext } from '../../types/context'; -import type { Span, SpanAttributes, SpanJSON } from '../../types/span'; +import { stringify } from '@sentry/core'; +import type { Span, SpanAttributes, SpanJSON, TraceContext } from '@sentry/core'; import { GEN_AI_INPUT_MESSAGES, GEN_AI_REQUEST_AVAILABLE_TOOLS, @@ -10,8 +10,7 @@ import { GEN_AI_USAGE_INPUT_TOKENS, GEN_AI_USAGE_OUTPUT_TOKENS, } from '@sentry/conventions/attributes'; -import { extractSystemInstructions, getTruncatedJsonString } from '../ai/utils'; -import { stringify } from '../../utils/string'; +import { extractSystemInstructions, getTruncatedJsonString } from '../core/utils'; import { toolCallSpanContextMap } from './constants'; import type { TokenSummary, ToolCallSpanContext } from './types'; import { AI_PROMPT_ATTRIBUTE, AI_PROMPT_MESSAGES_ATTRIBUTE } from './vercel-ai-attributes'; diff --git a/packages/core/src/tracing/vercel-ai/vercel-ai-attributes.ts b/packages/server-utils/src/ai/vercel-ai/vercel-ai-attributes.ts similarity index 100% rename from packages/core/src/tracing/vercel-ai/vercel-ai-attributes.ts rename to packages/server-utils/src/ai/vercel-ai/vercel-ai-attributes.ts diff --git a/packages/server-utils/src/vercel-ai/vercel-ai-dc-subscriber.ts b/packages/server-utils/src/vercel-ai/vercel-ai-dc-subscriber.ts index 79ded795c817..37ceb81059ee 100644 --- a/packages/server-utils/src/vercel-ai/vercel-ai-dc-subscriber.ts +++ b/packages/server-utils/src/vercel-ai/vercel-ai-dc-subscriber.ts @@ -33,11 +33,8 @@ import { captureException, GEN_AI_CONVERSATION_ID_ATTRIBUTE, getClient, - getProviderMetadataAttributes, - getTruncatedJsonString, isObjectLike, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, - shouldEnableTruncation, SPAN_STATUS_ERROR, spanToJSON, spanToTraceContext, @@ -46,6 +43,9 @@ import { withScope, } from '@sentry/core'; import type { TracingChannel } from 'node:diagnostics_channel'; +import { getProviderMetadataAttributes } from '../ai/vercel-ai'; +import { getTruncatedJsonString, shouldEnableTruncation } from '../ai/core/utils'; +import { WORKERS_AI_INTEGRATION_NAME } from '../ai/workers-ai/constants'; import { bindTracingChannelToSpan } from '../tracing-channel'; import { asNumber, asString, isReadableStream, type StreamedModelCallResult, sum, tapModelCallStream } from './util'; @@ -66,8 +66,6 @@ const GEN_AI_RERANK_OPERATION = 'rerank'; // The model-call op matches the Vercel AI OTel integration (`gen_ai.generate_content`) rather than // the generic `gen_ai.chat`, so v6 (OTel) and v7 (channel) produce the same spans. const GEN_AI_GENERATE_CONTENT_OPERATION = 'generate_content'; -// TODO(v11): export the constant from server-utils and import it here instead. -const WORKERS_AI_INTEGRATION_NAME = 'WorkersAI'; // Subset of the `vercel.ai.*` passthrough attributes the OTel integration emits that we reproduce. const VERCEL_AI_OPERATION_ID_ATTRIBUTE = 'vercel.ai.operationId'; diff --git a/packages/core/test/lib/tracing/vercel-ai-cached-tokens.test.ts b/packages/server-utils/test/ai/lib/tracing/vercel-ai-cached-tokens.test.ts similarity index 91% rename from packages/core/test/lib/tracing/vercel-ai-cached-tokens.test.ts rename to packages/server-utils/test/ai/lib/tracing/vercel-ai-cached-tokens.test.ts index 4ab68c6f8d1b..4ca75d09041f 100644 --- a/packages/core/test/lib/tracing/vercel-ai-cached-tokens.test.ts +++ b/packages/server-utils/test/ai/lib/tracing/vercel-ai-cached-tokens.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest'; -import { addVercelAiProcessors } from '../../../src/tracing/vercel-ai'; -import type { SpanJSON } from '../../../src/types/span'; -import { getDefaultTestClientOptions, TestClient } from '../../mocks/client'; +import { addVercelAiProcessors } from '../../../../src/ai/vercel-ai'; +import type { SpanJSON } from '@sentry/core'; +import { getDefaultTestClientOptions, TestClient } from '../../../mocks/client'; function processSpan(data: SpanJSON['data']): SpanJSON { const options = getDefaultTestClientOptions({ tracesSampleRate: 1.0 }); diff --git a/packages/core/test/lib/tracing/vercel-ai-parent-tokens.test.ts b/packages/server-utils/test/ai/lib/tracing/vercel-ai-parent-tokens.test.ts similarity index 92% rename from packages/core/test/lib/tracing/vercel-ai-parent-tokens.test.ts rename to packages/server-utils/test/ai/lib/tracing/vercel-ai-parent-tokens.test.ts index b3541a20787a..649a74a5fbb7 100644 --- a/packages/core/test/lib/tracing/vercel-ai-parent-tokens.test.ts +++ b/packages/server-utils/test/ai/lib/tracing/vercel-ai-parent-tokens.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest'; -import { addVercelAiProcessors } from '../../../src/tracing/vercel-ai'; -import type { SpanJSON } from '../../../src/types/span'; -import { getDefaultTestClientOptions, TestClient } from '../../mocks/client'; +import { addVercelAiProcessors } from '../../../../src/ai/vercel-ai'; +import type { SpanJSON } from '@sentry/core'; +import { getDefaultTestClientOptions, TestClient } from '../../../mocks/client'; describe('vercel-ai parent span token attributes', () => { it('should map ai.usage.inputTokens to gen_ai.usage.input_tokens', () => { diff --git a/packages/core/test/lib/tracing/vercel-ai-request-messages.test.ts b/packages/server-utils/test/ai/lib/tracing/vercel-ai-request-messages.test.ts similarity index 90% rename from packages/core/test/lib/tracing/vercel-ai-request-messages.test.ts rename to packages/server-utils/test/ai/lib/tracing/vercel-ai-request-messages.test.ts index be9f4e9f8dff..3d58a88f58b4 100644 --- a/packages/core/test/lib/tracing/vercel-ai-request-messages.test.ts +++ b/packages/server-utils/test/ai/lib/tracing/vercel-ai-request-messages.test.ts @@ -1,10 +1,10 @@ import { describe, expect, it } from 'vitest'; -import { getTruncatedJsonString } from '../../../src/tracing/ai/utils'; -import { stringify } from '../../../src/utils/string'; +import { getTruncatedJsonString } from '../../../../src/ai/core/utils'; +import { stringify } from '@sentry/core'; import { GEN_AI_INPUT_MESSAGES, GEN_AI_SYSTEM_INSTRUCTIONS } from '@sentry/conventions/attributes'; -import { requestMessagesFromPrompt } from '../../../src/tracing/vercel-ai/utils'; -import { AI_PROMPT_MESSAGES_ATTRIBUTE } from '../../../src/tracing/vercel-ai/vercel-ai-attributes'; -import type { Span, SpanAttributes } from '../../../src/types/span'; +import { requestMessagesFromPrompt } from '../../../../src/ai/vercel-ai/utils'; +import { AI_PROMPT_MESSAGES_ATTRIBUTE } from '../../../../src/ai/vercel-ai/vercel-ai-attributes'; +import type { Span, SpanAttributes } from '@sentry/core'; /** * Minimal span that records the attributes set on it, so we can assert on the diff --git a/packages/core/test/lib/tracing/vercel-ai-span-status.test.ts b/packages/server-utils/test/ai/lib/tracing/vercel-ai-span-status.test.ts similarity index 84% rename from packages/core/test/lib/tracing/vercel-ai-span-status.test.ts rename to packages/server-utils/test/ai/lib/tracing/vercel-ai-span-status.test.ts index 8a7b4302315b..de86a0fecec4 100644 --- a/packages/core/test/lib/tracing/vercel-ai-span-status.test.ts +++ b/packages/server-utils/test/ai/lib/tracing/vercel-ai-span-status.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest'; -import { addVercelAiProcessors } from '../../../src/tracing/vercel-ai'; -import type { SpanJSON } from '../../../src/types/span'; -import { getDefaultTestClientOptions, TestClient } from '../../mocks/client'; +import { addVercelAiProcessors } from '../../../../src/ai/vercel-ai'; +import type { SpanJSON } from '@sentry/core'; +import { getDefaultTestClientOptions, TestClient } from '../../../mocks/client'; describe('vercel-ai span status normalization', () => { function processSpan(status: string): string | undefined { diff --git a/packages/server-utils/test/ai/lib/tracing/workers-ai.test.ts b/packages/server-utils/test/ai/lib/tracing/workers-ai.test.ts index 5dfef69d8bdf..36340768b04d 100644 --- a/packages/server-utils/test/ai/lib/tracing/workers-ai.test.ts +++ b/packages/server-utils/test/ai/lib/tracing/workers-ai.test.ts @@ -10,13 +10,9 @@ import { import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { _INTERNAL_clearAiProviderSkips, - addVercelAiProcessors, - Client, - createTransport, getCurrentScope, getGlobalScope, getIsolationScope, - resolvedSyncPromise, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, @@ -25,30 +21,13 @@ import { spanToJSON, startSpan, } from '@sentry/core'; -import type { ClientOptions, DataCollection, Span } from '@sentry/core'; +import type { DataCollection, Span } from '@sentry/core'; +import { addVercelAiProcessors } from '../../../../src/ai/vercel-ai'; import { instrumentWorkersAiClient } from '../../../../src/ai/workers-ai'; +import { getDefaultTestClientOptions, TestClient } from '../../../mocks/client'; const AI_OPERATION_ID_ATTRIBUTE = 'ai.operationId'; -function getDefaultTestClientOptions(options: Partial = {}): ClientOptions { - return { - integrations: [], - transport: () => createTransport({ recordDroppedEvent: () => undefined }, () => resolvedSyncPromise({})), - stackParser: () => [], - ...options, - } as ClientOptions; -} - -class TestClient extends Client { - public eventFromException(): PromiseLike { - return resolvedSyncPromise({}); - } - - public eventFromMessage(): PromiseLike { - return resolvedSyncPromise({}); - } -} - const MODEL = '@cf/meta/llama-3.1-8b-instruct'; describe('instrumentWorkersAiClient', () => { diff --git a/packages/core/test/lib/utils/vercelai-utils.test.ts b/packages/server-utils/test/ai/lib/utils/vercelai-utils.test.ts similarity index 98% rename from packages/core/test/lib/utils/vercelai-utils.test.ts rename to packages/server-utils/test/ai/lib/utils/vercelai-utils.test.ts index d30c3602ab1c..a6ac57b2a7d8 100644 --- a/packages/core/test/lib/utils/vercelai-utils.test.ts +++ b/packages/server-utils/test/ai/lib/utils/vercelai-utils.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { convertUserInputToMessagesFormat } from '../../../src/tracing/vercel-ai/utils'; +import { convertUserInputToMessagesFormat } from '../../../../src/ai/vercel-ai/utils'; describe('vercel-ai-utils', () => { describe('convertUserInputToMessagesFormat', () => { diff --git a/packages/server-utils/test/mocks/client.ts b/packages/server-utils/test/mocks/client.ts new file mode 100644 index 000000000000..2e1e4353d474 --- /dev/null +++ b/packages/server-utils/test/mocks/client.ts @@ -0,0 +1,98 @@ +import type { + ClientOptions, + Event, + EventHint, + Integration, + ParameterizedString, + Session, + SeverityLevel, +} from '@sentry/core'; +import { Client, createTransport, initAndBind, resolvedSyncPromise } from '@sentry/core'; + +export function getDefaultTestClientOptions(options: Partial = {}): TestClientOptions { + return { + integrations: [], + sendClientReports: true, + traceLifecycle: 'static', + transport: () => + createTransport( + { + recordDroppedEvent: () => undefined, + }, // noop + _ => resolvedSyncPromise({}), + ), + stackParser: () => [], + ...options, + }; +} + +export interface TestClientOptions extends ClientOptions { + test?: boolean; + mockInstallFailure?: boolean; + enableSend?: boolean; + defaultIntegrations?: Integration[] | false; +} + +export class TestClient extends Client { + public static instance?: TestClient; + public static sendEventCalled?: (event: Event) => void; + + public event?: Event; + public session?: Session; + + public constructor(options: TestClientOptions) { + super(options); + TestClient.instance = this; + } + + public eventFromException(exception: any): PromiseLike { + const event: Event = { + exception: { + values: [ + { + type: exception.name, + value: exception.message, + }, + ], + }, + }; + + const frames = this._options.stackParser(exception.stack || '', 1); + if (frames.length && event.exception?.values?.[0]) { + event.exception.values[0] = { ...event.exception.values[0], stacktrace: { frames } }; + } + + return resolvedSyncPromise(event); + } + + public eventFromMessage(message: ParameterizedString, level: SeverityLevel = 'info'): PromiseLike { + return resolvedSyncPromise({ message, level }); + } + + public sendEvent(event: Event, hint?: EventHint): void { + this.event = event; + + if (this._options.enableSend) { + super.sendEvent(event, hint); + return; + } + + // In real life, this will get deleted as part of envelope creation. + delete event.sdkProcessingMetadata; + + TestClient.sendEventCalled?.(event); + } + + public sendSession(session: Session): void { + this.session = session; + } + + // Public proxy for protected method + public _clearOutcomes(): ReturnType { + return super._clearOutcomes(); + } +} + +export function init(options: TestClientOptions): void { + initAndBind(TestClient, options); +} diff --git a/packages/server-utils/test/vercel-ai/skip-workers-ai.test.ts b/packages/server-utils/test/vercel-ai/skip-workers-ai.test.ts index b42565d595ee..8646446b30f1 100644 --- a/packages/server-utils/test/vercel-ai/skip-workers-ai.test.ts +++ b/packages/server-utils/test/vercel-ai/skip-workers-ai.test.ts @@ -10,11 +10,9 @@ import { resolvedSyncPromise, } from '@sentry/core'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { WORKERS_AI_INTEGRATION_NAME } from '../../src/ai/workers-ai/constants'; import { createSpanFromMessage } from '../../src/vercel-ai/vercel-ai-dc-subscriber'; -// Must match `WORKERS_AI_INTEGRATION_NAME` in core's `tracing/workers-ai/constants`. -const WORKERS_AI_INTEGRATION_NAME = 'WorkersAI'; - class TestClient extends Client { public eventFromException(): PromiseLike { return resolvedSyncPromise({}); diff --git a/packages/vercel-edge/src/integrations/tracing/vercelai.ts b/packages/vercel-edge/src/integrations/tracing/vercelai.ts index 7f5fbddf09fa..69932855b4a6 100644 --- a/packages/vercel-edge/src/integrations/tracing/vercelai.ts +++ b/packages/vercel-edge/src/integrations/tracing/vercelai.ts @@ -9,7 +9,8 @@ */ import type { IntegrationFn } from '@sentry/core'; -import { addVercelAiProcessors, defineIntegration } from '@sentry/core'; +import { defineIntegration } from '@sentry/core'; +import { addVercelAiProcessors } from '@sentry/server-utils/no-diagnostic-channels'; const INTEGRATION_NAME = 'VercelAI' as const;