From 428912caf2768a4d06fcb13d30bcb6884378e6a9 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Wed, 22 Jul 2026 15:19:31 +0200 Subject: [PATCH 1/2] feat(core)!: Remove `streamGenAiSpans` flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removes the `streamGenAiSpans` client option. The behavior it gated (extracting gen_ai spans from transactions into a v2 span envelope container) was already the default and is now unconditional — there is no longer a way to opt out. For static transactions, `extractGenAiSpansFromEvent` always pulls gen_ai spans into a v2 span container. For full span streaming (`traceLifecycle: 'stream'`), the existing `hasSpanStreamingEnabled` guard still short-circuits so spans are not extracted twice. Because gen_ai spans now always take the v2 path (not subject to the transaction payload-size limits that truncation works around), `shouldEnableTruncation` simplifies to `!getClient()`. The `enableTruncation` escape hatch is intentionally left in place for a follow-up. Test suites that set `streamGenAiSpans: false` to assert on `transaction.spans` are migrated to assert on the v2 span container; redundant `streamGenAiSpans: true` config lines are removed. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../suites/tracing/anthropic-ai/index.ts | 1 - .../suites/tracing/google-genai/index.ts | 1 - .../suites/tracing/langchain/index.ts | 1 - .../suites/tracing/langgraph/index.ts | 1 - .../suites/tracing/openai/index.ts | 1 - .../suites/tracing/workers-ai/index.ts | 3 - .../suites/tracing/workers-ai/test.ts | 104 +++++++----------- .../cloudflare-vercelai-v7-als/src/index.ts | 1 - .../tests/index.test.ts | 26 +++-- .../src/index.ts | 1 - .../tests/index.test.ts | 63 ++++------- .../nextjs-15/sentry.server.config.ts | 1 - .../nextjs-16/sentry.server.config.ts | 1 - .../anthropic/instrument-no-truncation.mjs | 1 - .../instrument-streaming-with-truncation.mjs | 1 - .../anthropic/instrument-streaming.mjs | 1 - .../anthropic/instrument-with-options.mjs | 1 - .../tracing/anthropic/instrument-with-pii.mjs | 1 - .../anthropic/instrument-with-truncation.mjs | 1 - .../suites/tracing/anthropic/instrument.mjs | 1 - .../google-genai/instrument-no-truncation.mjs | 1 - .../instrument-streaming-with-truncation.mjs | 1 - .../google-genai/instrument-streaming.mjs | 1 - .../google-genai/instrument-with-options.mjs | 1 - .../google-genai/instrument-with-pii.mjs | 1 - .../instrument-with-truncation.mjs | 1 - .../tracing/google-genai/instrument.mjs | 1 - .../langchain/instrument-no-truncation.mjs | 1 - .../instrument-streaming-with-truncation.mjs | 1 - .../langchain/instrument-streaming.mjs | 1 - .../tracing/langchain/instrument-with-pii.mjs | 1 - .../langchain/instrument-with-truncation.mjs | 1 - .../suites/tracing/langchain/instrument.mjs | 1 - .../langchain/v1/instrument-with-pii.mjs | 1 - .../v1/instrument-with-truncation.mjs | 1 - .../tracing/langchain/v1/instrument.mjs | 1 - .../tracing/langgraph/instrument-agent.mjs | 2 - .../langgraph/instrument-no-truncation.mjs | 1 - .../instrument-streaming-with-truncation.mjs | 1 - .../langgraph/instrument-streaming.mjs | 1 - .../tracing/langgraph/instrument-with-pii.mjs | 1 - .../suites/tracing/langgraph/instrument.mjs | 1 - .../suites/tracing/langgraph/test.ts | 96 +++++++++------- .../openai/instrument-no-truncation.mjs | 1 - .../tracing/openai/instrument-root-span.mjs | 1 - .../instrument-streaming-with-truncation.mjs | 1 - .../tracing/openai/instrument-streaming.mjs | 1 - .../openai/instrument-with-options.mjs | 1 - .../tracing/openai/instrument-with-pii.mjs | 1 - .../openai/instrument-with-truncation.mjs | 1 - .../suites/tracing/openai/instrument.mjs | 1 - .../openai-tool-calls/instrument-with-pii.mjs | 1 - .../openai/openai-tool-calls/instrument.mjs | 1 - .../openai/v6/instrument-root-span.mjs | 1 - .../openai/v6/instrument-with-options.mjs | 1 - .../tracing/openai/v6/instrument-with-pii.mjs | 1 - .../suites/tracing/openai/v6/instrument.mjs | 1 - .../vercelai/instrument-no-truncation.mjs | 1 - .../tracing/vercelai/instrument-with-pii.mjs | 1 - .../vercelai/instrument-with-truncation.mjs | 1 - .../suites/tracing/vercelai/instrument.mjs | 1 - .../vercelai/v5/instrument-with-pii.mjs | 1 - .../suites/tracing/vercelai/v5/instrument.mjs | 1 - packages/core/src/tracing/ai/utils.ts | 16 +-- .../src/tracing/spans/extractGenAiSpans.ts | 1 - packages/core/src/types/options.ts | 14 --- .../core/test/lib/tracing/ai/utils.test.ts | 16 +-- .../tracing/spans/extractGenAiSpans.test.ts | 10 +- 68 files changed, 147 insertions(+), 261 deletions(-) diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/anthropic-ai/index.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/anthropic-ai/index.ts index 19107938d26d..813dc9341058 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/anthropic-ai/index.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/anthropic-ai/index.ts @@ -28,7 +28,6 @@ export default Sentry.withSentry( dsn: env.SENTRY_DSN, traceLifecycle: 'static', tracesSampleRate: 1.0, - streamGenAiSpans: true, }), { async fetch(_request, _env, _ctx) { diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/google-genai/index.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/google-genai/index.ts index 1e56569ff1ea..56a650244de2 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/google-genai/index.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/google-genai/index.ts @@ -46,7 +46,6 @@ export default Sentry.withSentry( dsn: env.SENTRY_DSN, traceLifecycle: 'static', tracesSampleRate: 1.0, - streamGenAiSpans: true, }), { async fetch(_request, _env, _ctx) { diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/langchain/index.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/langchain/index.ts index 900c09fdb077..e157960aa080 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/langchain/index.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/langchain/index.ts @@ -31,7 +31,6 @@ export default Sentry.withSentry( dsn: env.SENTRY_DSN, traceLifecycle: 'static', tracesSampleRate: 1.0, - streamGenAiSpans: true, }), { async fetch(_request, _env, _ctx) { diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/langgraph/index.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/langgraph/index.ts index f6352fd2f85b..6b22dcd4388a 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/langgraph/index.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/langgraph/index.ts @@ -11,7 +11,6 @@ export default Sentry.withSentry( traceLifecycle: 'static', tracesSampleRate: 1.0, dataCollection: { genAI: { inputs: true, outputs: true } }, - streamGenAiSpans: true, }), { async fetch(_request, _env, _ctx) { diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/openai/index.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/openai/index.ts index 67ef4e868d71..bfb474400449 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/openai/index.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/openai/index.ts @@ -33,7 +33,6 @@ export default Sentry.withSentry( dsn: env.SENTRY_DSN, traceLifecycle: 'static', tracesSampleRate: 1.0, - streamGenAiSpans: true, }), { async fetch(_request, _env, _ctx) { diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/workers-ai/index.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/workers-ai/index.ts index d376b90a0a07..e0c2927d69bb 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/workers-ai/index.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/workers-ai/index.ts @@ -13,9 +13,6 @@ export default Sentry.withSentry( dsn: env.SENTRY_DSN, traceLifecycle: 'static', tracesSampleRate: 1.0, - // Keep gen_ai spans embedded in the transaction (instead of streamed as a - // separate envelope container) so they can be asserted on `transaction.spans`. - streamGenAiSpans: false, }), { async fetch(request) { diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/workers-ai/test.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/workers-ai/test.ts index 915b0e06a90c..dbb6970b2e55 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/workers-ai/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/workers-ai/test.ts @@ -22,40 +22,29 @@ it('traces a basic Workers AI text generation request', async ({ signal }) => { .ignore('event') .expect(envelope => { const transactionEvent = envelope[1]?.[0]?.[1] as any; + expect(transactionEvent.transaction).toBe('GET /'); - // The transaction event is framework-generated and carries non-deterministic fields - // (random ports, ids, timestamps, sdk version), so we assert the stable subset. - expect(transactionEvent).toEqual( + const container = envelope[1]?.[1]?.[1] as any; + expect(container).toBeDefined(); + expect(container.items).toHaveLength(1); + + expect(container.items[0]).toEqual( expect.objectContaining({ - type: 'transaction', - transaction: 'GET /', - transaction_info: { source: 'route' }, - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'http.server', - origin: 'auto.http.cloudflare', - status: 'ok', - }), - }), - spans: [ - expect.objectContaining({ - description: 'chat @cf/meta/llama-3.1-8b-instruct', - op: 'gen_ai.chat', - origin: 'auto.ai.cloudflare.workers_ai', - data: { - 'sentry.origin': 'auto.ai.cloudflare.workers_ai', - 'sentry.op': 'gen_ai.chat', - [GEN_AI_PROVIDER_NAME]: 'cloudflare.workers_ai', - [GEN_AI_OPERATION_NAME]: 'chat', - [GEN_AI_REQUEST_MODEL]: '@cf/meta/llama-3.1-8b-instruct', - [GEN_AI_REQUEST_TEMPERATURE]: 0.7, - [GEN_AI_REQUEST_MAX_TOKENS]: 100, - [GEN_AI_USAGE_INPUT_TOKENS]: 12, - [GEN_AI_USAGE_OUTPUT_TOKENS]: 7, - [GEN_AI_USAGE_TOTAL_TOKENS]: 19, - }, - }), - ], + name: 'chat @cf/meta/llama-3.1-8b-instruct', + status: 'ok', + is_segment: false, + attributes: { + 'sentry.origin': { value: 'auto.ai.cloudflare.workers_ai', type: 'string' }, + 'sentry.op': { value: 'gen_ai.chat', type: 'string' }, + [GEN_AI_PROVIDER_NAME]: { value: 'cloudflare.workers_ai', type: 'string' }, + [GEN_AI_OPERATION_NAME]: { value: 'chat', type: 'string' }, + [GEN_AI_REQUEST_MODEL]: { value: '@cf/meta/llama-3.1-8b-instruct', type: 'string' }, + [GEN_AI_REQUEST_TEMPERATURE]: { value: 0.7, type: 'double' }, + [GEN_AI_REQUEST_MAX_TOKENS]: { value: 100, type: 'integer' }, + [GEN_AI_USAGE_INPUT_TOKENS]: { value: 12, type: 'integer' }, + [GEN_AI_USAGE_OUTPUT_TOKENS]: { value: 7, type: 'integer' }, + [GEN_AI_USAGE_TOTAL_TOKENS]: { value: 19, type: 'integer' }, + }, }), ); }) @@ -69,38 +58,29 @@ it('traces a streaming Workers AI text generation request', async ({ signal }) = .ignore('event') .expect(envelope => { const transactionEvent = envelope[1]?.[0]?.[1] as any; + expect(transactionEvent.transaction).toBe('GET /stream'); - expect(transactionEvent).toEqual( + const container = envelope[1]?.[1]?.[1] as any; + expect(container).toBeDefined(); + expect(container.items).toHaveLength(1); + + expect(container.items[0]).toEqual( expect.objectContaining({ - type: 'transaction', - transaction: 'GET /stream', - transaction_info: { source: 'url' }, - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'http.server', - origin: 'auto.http.cloudflare', - status: 'ok', - }), - }), - spans: [ - expect.objectContaining({ - description: 'chat @cf/meta/llama-3.1-8b-instruct', - op: 'gen_ai.chat', - origin: 'auto.ai.cloudflare.workers_ai', - data: { - 'sentry.origin': 'auto.ai.cloudflare.workers_ai', - 'sentry.op': 'gen_ai.chat', - [GEN_AI_PROVIDER_NAME]: 'cloudflare.workers_ai', - [GEN_AI_OPERATION_NAME]: 'chat', - [GEN_AI_REQUEST_MODEL]: '@cf/meta/llama-3.1-8b-instruct', - [GEN_AI_REQUEST_STREAM_ATTRIBUTE]: true, - [GEN_AI_RESPONSE_STREAMING]: true, - [GEN_AI_USAGE_INPUT_TOKENS]: 12, - [GEN_AI_USAGE_OUTPUT_TOKENS]: 7, - [GEN_AI_USAGE_TOTAL_TOKENS]: 19, - }, - }), - ], + name: 'chat @cf/meta/llama-3.1-8b-instruct', + status: 'ok', + is_segment: false, + attributes: { + 'sentry.origin': { value: 'auto.ai.cloudflare.workers_ai', type: 'string' }, + 'sentry.op': { value: 'gen_ai.chat', type: 'string' }, + [GEN_AI_PROVIDER_NAME]: { value: 'cloudflare.workers_ai', type: 'string' }, + [GEN_AI_OPERATION_NAME]: { value: 'chat', type: 'string' }, + [GEN_AI_REQUEST_MODEL]: { value: '@cf/meta/llama-3.1-8b-instruct', type: 'string' }, + [GEN_AI_REQUEST_STREAM_ATTRIBUTE]: { value: true, type: 'boolean' }, + [GEN_AI_RESPONSE_STREAMING]: { value: true, type: 'boolean' }, + [GEN_AI_USAGE_INPUT_TOKENS]: { value: 12, type: 'integer' }, + [GEN_AI_USAGE_OUTPUT_TOKENS]: { value: 7, type: 'integer' }, + [GEN_AI_USAGE_TOTAL_TOKENS]: { value: 19, type: 'integer' }, + }, }), ); }) diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7-als/src/index.ts b/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7-als/src/index.ts index 0d18c070ec3d..6b8cca2cbf31 100644 --- a/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7-als/src/index.ts +++ b/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7-als/src/index.ts @@ -8,7 +8,6 @@ export default Sentry.withSentry( dsn: env.E2E_TEST_DSN, environment: 'qa', tunnel: 'http://localhost:3031/', - streamGenAiSpans: false, tracesSampleRate: 1.0, integrations: [Sentry.vercelAIIntegration()], }), diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7-als/tests/index.test.ts b/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7-als/tests/index.test.ts index 8aea274ca40d..078452236a44 100644 --- a/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7-als/tests/index.test.ts +++ b/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7-als/tests/index.test.ts @@ -1,23 +1,33 @@ import { expect, test } from '@playwright/test'; -import { waitForTransaction } from '@sentry-internal/test-utils'; +import { getSpanOp, waitForRequest } from '@sentry-internal/test-utils'; test('does not capture Vercel AI v7 spans without nodejs_compat', async ({ baseURL }) => { - const transactionPromise = waitForTransaction('cloudflare-vercelai-v7-als', txn => { - return txn.transaction === 'GET /generate'; + // The transaction envelope also carries any extracted gen_ai spans as a span v2 container item, + // so we wait for the whole envelope and assert neither place contains AI spans. + const envelopePromise = waitForRequest('cloudflare-vercelai-v7-als', ({ envelope }) => { + const transactionItem = envelope[1].find(([header]) => header.type === 'transaction'); + return (transactionItem?.[1] as any)?.transaction === 'GET /generate'; }); const response = await fetch(`${baseURL}/generate`); expect(response.status).toBe(200); - const transaction = await transactionPromise; + const { envelope } = await envelopePromise; + const transaction = envelope[1].find(([header]) => header.type === 'transaction')?.[1] as any; expect(transaction.transaction).toBe('GET /generate'); expect(transaction.contexts?.trace?.op).toBe('http.server'); - // v7 uses diagnostics_channel which is not available with nodejs_als, - // so no AI spans should be present. - const aiSpans = (transaction.spans || []).filter( + // v7 uses diagnostics_channel which is not available with nodejs_als, so no AI spans should be + // present — neither embedded in the transaction nor streamed as a span v2 container item. + const embeddedAiSpans = (transaction.spans || []).filter( (span: any) => span.op?.startsWith('gen_ai.') || span.description?.includes('generateText'), ); - expect(aiSpans).toHaveLength(0); + expect(embeddedAiSpans).toHaveLength(0); + + const streamedGenAiSpans = envelope[1] + .filter(([header]) => header.type === 'span') + .flatMap(([, payload]) => (payload as any).items ?? []) + .filter((span: any) => getSpanOp(span)?.startsWith('gen_ai.')); + expect(streamedGenAiSpans).toHaveLength(0); }); diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7-compat/src/index.ts b/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7-compat/src/index.ts index 1cf5d617196f..60807d30fd61 100644 --- a/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7-compat/src/index.ts +++ b/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7-compat/src/index.ts @@ -9,7 +9,6 @@ export default Sentry.withSentry( environment: 'qa', tunnel: 'http://localhost:3031/', tracesSampleRate: 1.0, - streamGenAiSpans: false, integrations: [Sentry.vercelAIIntegration()], }), { diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7-compat/tests/index.test.ts b/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7-compat/tests/index.test.ts index 4b1a983c629b..3ca6c9342870 100644 --- a/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7-compat/tests/index.test.ts +++ b/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7-compat/tests/index.test.ts @@ -1,57 +1,40 @@ import { expect, test } from '@playwright/test'; -import { waitForTransaction } from '@sentry-internal/test-utils'; +import { getSpanOp, waitForStreamedSpans } from '@sentry-internal/test-utils'; test('captures Vercel AI v7 spans with nodejs_compat using tracing channels', async ({ baseURL }) => { - const transactionPromise = waitForTransaction('cloudflare-vercelai-v7-compat', txn => { - return txn.transaction === 'GET /generate'; - }); + // gen_ai spans are extracted into a separate span v2 envelope item + const genAiSpansPromise = waitForStreamedSpans('cloudflare-vercelai-v7-compat', spans => + spans.some(span => getSpanOp(span) === 'gen_ai.invoke_agent'), + ); const response = await fetch(`${baseURL}/generate`); expect(response.status).toBe(200); - const transaction = await transactionPromise; - - expect(transaction.transaction).toBe('GET /generate'); - expect(transaction.contexts?.trace?.op).toBe('http.server'); - expect(transaction.spans).toHaveLength(2); + const genAiSpans = await genAiSpansPromise; - expect(transaction.spans).toEqual( + expect(genAiSpans).toEqual( expect.arrayContaining([ expect.objectContaining({ - description: 'invoke_agent', - op: 'gen_ai.invoke_agent', - origin: 'auto.vercelai.channel', - parent_span_id: expect.any(String), - span_id: expect.any(String), - start_timestamp: expect.any(Number), - timestamp: expect.any(Number), - trace_id: expect.any(String), - data: expect.objectContaining({ - 'gen_ai.operation.name': 'invoke_agent', - 'gen_ai.usage.input_tokens': 10, - 'gen_ai.usage.output_tokens': 20, - 'gen_ai.usage.total_tokens': 30, - 'sentry.op': 'gen_ai.invoke_agent', - 'sentry.origin': 'auto.vercelai.channel', + name: 'invoke_agent', + attributes: expect.objectContaining({ + 'gen_ai.operation.name': { value: 'invoke_agent', type: 'string' }, + 'gen_ai.usage.input_tokens': { value: 10, type: 'integer' }, + 'gen_ai.usage.output_tokens': { value: 20, type: 'integer' }, + 'gen_ai.usage.total_tokens': { value: 30, type: 'integer' }, + 'sentry.op': { value: 'gen_ai.invoke_agent', type: 'string' }, + 'sentry.origin': { value: 'auto.vercelai.channel', type: 'string' }, }), }), expect.objectContaining({ - description: 'generate_content mock-model-id', - op: 'gen_ai.generate_content', - origin: 'auto.vercelai.channel', - parent_span_id: expect.any(String), - span_id: expect.any(String), - start_timestamp: expect.any(Number), - timestamp: expect.any(Number), - trace_id: expect.any(String), - data: expect.objectContaining({ - 'gen_ai.operation.name': 'generate_content', - 'gen_ai.usage.input_tokens': 10, - 'gen_ai.usage.output_tokens': 20, - 'gen_ai.usage.total_tokens': 30, - 'sentry.op': 'gen_ai.generate_content', - 'sentry.origin': 'auto.vercelai.channel', + name: 'generate_content mock-model-id', + attributes: expect.objectContaining({ + 'gen_ai.operation.name': { value: 'generate_content', type: 'string' }, + 'gen_ai.usage.input_tokens': { value: 10, type: 'integer' }, + 'gen_ai.usage.output_tokens': { value: 20, type: 'integer' }, + 'gen_ai.usage.total_tokens': { value: 30, type: 'integer' }, + 'sentry.op': { value: 'gen_ai.generate_content', type: 'string' }, + 'sentry.origin': { value: 'auto.vercelai.channel', type: 'string' }, }), }), ]), diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/sentry.server.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-15/sentry.server.config.ts index d626ed7e4228..d4d1c149eda1 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/sentry.server.config.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/sentry.server.config.ts @@ -12,5 +12,4 @@ Sentry.init({ bufferSize: 1000, }, integrations: [Sentry.vercelAIIntegration()], - streamGenAiSpans: true, }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/sentry.server.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/sentry.server.config.ts index e956683c039e..28d4bdda4ca1 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16/sentry.server.config.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/sentry.server.config.ts @@ -10,7 +10,6 @@ Sentry.init({ dataCollection: { userInfo: true }, // debug: true, integrations: [Sentry.vercelAIIntegration(), Sentry.nodeRuntimeMetricsIntegration({ collectionIntervalMs: 1_000 })], - streamGenAiSpans: true, // Verify Log type is available beforeSendLog(log: Log) { return log; diff --git a/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument-no-truncation.mjs b/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument-no-truncation.mjs index 9fe7bfecf816..5d4afd8e6fed 100644 --- a/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument-no-truncation.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument-no-truncation.mjs @@ -22,5 +22,4 @@ Sentry.init({ } return event; }, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument-streaming-with-truncation.mjs b/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument-streaming-with-truncation.mjs index f5bc3383d644..048f3de408e2 100644 --- a/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument-streaming-with-truncation.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument-streaming-with-truncation.mjs @@ -13,5 +13,4 @@ Sentry.init({ enableTruncation: true, }), ], - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument-streaming.mjs b/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument-streaming.mjs index 1e13bd68fe06..657bed0a3a8c 100644 --- a/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument-streaming.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument-streaming.mjs @@ -8,5 +8,4 @@ Sentry.init({ dataCollection: { genAI: { inputs: true, outputs: true } }, transport: loggingTransport, traceLifecycle: 'stream', - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument-with-options.mjs b/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument-with-options.mjs index f9506ad536d4..1046a4b66191 100644 --- a/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument-with-options.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument-with-options.mjs @@ -21,5 +21,4 @@ Sentry.init({ } return event; }, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument-with-pii.mjs b/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument-with-pii.mjs index d6fe1229b6f6..fc8f4e266829 100644 --- a/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument-with-pii.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument-with-pii.mjs @@ -15,5 +15,4 @@ Sentry.init({ } return event; }, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument-with-truncation.mjs b/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument-with-truncation.mjs index e4b4940561f1..f4330e30592f 100644 --- a/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument-with-truncation.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument-with-truncation.mjs @@ -16,5 +16,4 @@ Sentry.init({ } return event; }, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument.mjs index 9c35d5566e76..9b15a0c635e8 100644 --- a/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/anthropic/instrument.mjs @@ -15,5 +15,4 @@ Sentry.init({ } return event; }, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/google-genai/instrument-no-truncation.mjs b/dev-packages/node-integration-tests/suites/tracing/google-genai/instrument-no-truncation.mjs index 9317ccc7e964..7dc07bc2e077 100644 --- a/dev-packages/node-integration-tests/suites/tracing/google-genai/instrument-no-truncation.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/google-genai/instrument-no-truncation.mjs @@ -22,5 +22,4 @@ Sentry.init({ } return event; }, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/google-genai/instrument-streaming-with-truncation.mjs b/dev-packages/node-integration-tests/suites/tracing/google-genai/instrument-streaming-with-truncation.mjs index 88f1e7f0ebff..0e148493344a 100644 --- a/dev-packages/node-integration-tests/suites/tracing/google-genai/instrument-streaming-with-truncation.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/google-genai/instrument-streaming-with-truncation.mjs @@ -13,5 +13,4 @@ Sentry.init({ enableTruncation: true, }), ], - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/google-genai/instrument-streaming.mjs b/dev-packages/node-integration-tests/suites/tracing/google-genai/instrument-streaming.mjs index 1e13bd68fe06..657bed0a3a8c 100644 --- a/dev-packages/node-integration-tests/suites/tracing/google-genai/instrument-streaming.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/google-genai/instrument-streaming.mjs @@ -8,5 +8,4 @@ Sentry.init({ dataCollection: { genAI: { inputs: true, outputs: true } }, transport: loggingTransport, traceLifecycle: 'stream', - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/google-genai/instrument-with-options.mjs b/dev-packages/node-integration-tests/suites/tracing/google-genai/instrument-with-options.mjs index a81262b1adc7..f1299d9c3542 100644 --- a/dev-packages/node-integration-tests/suites/tracing/google-genai/instrument-with-options.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/google-genai/instrument-with-options.mjs @@ -21,5 +21,4 @@ Sentry.init({ } return event; }, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/google-genai/instrument-with-pii.mjs b/dev-packages/node-integration-tests/suites/tracing/google-genai/instrument-with-pii.mjs index 3466291ee27a..e5ddcf227d1f 100644 --- a/dev-packages/node-integration-tests/suites/tracing/google-genai/instrument-with-pii.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/google-genai/instrument-with-pii.mjs @@ -15,5 +15,4 @@ Sentry.init({ } return event; }, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/google-genai/instrument-with-truncation.mjs b/dev-packages/node-integration-tests/suites/tracing/google-genai/instrument-with-truncation.mjs index 434b443ab6a2..67f83d77e7a4 100644 --- a/dev-packages/node-integration-tests/suites/tracing/google-genai/instrument-with-truncation.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/google-genai/instrument-with-truncation.mjs @@ -16,5 +16,4 @@ Sentry.init({ } return event; }, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/google-genai/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/google-genai/instrument.mjs index 42e55939a2b3..c83310d623f6 100644 --- a/dev-packages/node-integration-tests/suites/tracing/google-genai/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/google-genai/instrument.mjs @@ -15,5 +15,4 @@ Sentry.init({ } return event; }, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/langchain/instrument-no-truncation.mjs b/dev-packages/node-integration-tests/suites/tracing/langchain/instrument-no-truncation.mjs index 127ab4cf0c9a..d119052c6120 100644 --- a/dev-packages/node-integration-tests/suites/tracing/langchain/instrument-no-truncation.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/langchain/instrument-no-truncation.mjs @@ -22,5 +22,4 @@ Sentry.init({ } return event; }, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/langchain/instrument-streaming-with-truncation.mjs b/dev-packages/node-integration-tests/suites/tracing/langchain/instrument-streaming-with-truncation.mjs index 3774b2665391..2a2365407b79 100644 --- a/dev-packages/node-integration-tests/suites/tracing/langchain/instrument-streaming-with-truncation.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/langchain/instrument-streaming-with-truncation.mjs @@ -13,5 +13,4 @@ Sentry.init({ enableTruncation: true, }), ], - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/langchain/instrument-streaming.mjs b/dev-packages/node-integration-tests/suites/tracing/langchain/instrument-streaming.mjs index 1e13bd68fe06..657bed0a3a8c 100644 --- a/dev-packages/node-integration-tests/suites/tracing/langchain/instrument-streaming.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/langchain/instrument-streaming.mjs @@ -8,5 +8,4 @@ Sentry.init({ dataCollection: { genAI: { inputs: true, outputs: true } }, transport: loggingTransport, traceLifecycle: 'stream', - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/langchain/instrument-with-pii.mjs b/dev-packages/node-integration-tests/suites/tracing/langchain/instrument-with-pii.mjs index 07f1ae9580e7..e60b44f864d7 100644 --- a/dev-packages/node-integration-tests/suites/tracing/langchain/instrument-with-pii.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/langchain/instrument-with-pii.mjs @@ -15,5 +15,4 @@ Sentry.init({ } return event; }, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/langchain/instrument-with-truncation.mjs b/dev-packages/node-integration-tests/suites/tracing/langchain/instrument-with-truncation.mjs index af4eadb9b130..8ba041d37cf6 100644 --- a/dev-packages/node-integration-tests/suites/tracing/langchain/instrument-with-truncation.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/langchain/instrument-with-truncation.mjs @@ -16,5 +16,4 @@ Sentry.init({ } return event; }, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/langchain/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/langchain/instrument.mjs index 4117c2513f3a..c81bb4dc1322 100644 --- a/dev-packages/node-integration-tests/suites/tracing/langchain/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/langchain/instrument.mjs @@ -15,5 +15,4 @@ Sentry.init({ } return event; }, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/langchain/v1/instrument-with-pii.mjs b/dev-packages/node-integration-tests/suites/tracing/langchain/v1/instrument-with-pii.mjs index 3fb1f7fe949b..a220d64b148a 100644 --- a/dev-packages/node-integration-tests/suites/tracing/langchain/v1/instrument-with-pii.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/langchain/v1/instrument-with-pii.mjs @@ -15,5 +15,4 @@ Sentry.init({ } return event; }, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/langchain/v1/instrument-with-truncation.mjs b/dev-packages/node-integration-tests/suites/tracing/langchain/v1/instrument-with-truncation.mjs index 09b66adadaff..7ba8b1acd686 100644 --- a/dev-packages/node-integration-tests/suites/tracing/langchain/v1/instrument-with-truncation.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/langchain/v1/instrument-with-truncation.mjs @@ -16,5 +16,4 @@ Sentry.init({ } return event; }, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/langchain/v1/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/langchain/v1/instrument.mjs index 65978637920a..8a2ba440b758 100644 --- a/dev-packages/node-integration-tests/suites/tracing/langchain/v1/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/langchain/v1/instrument.mjs @@ -15,5 +15,4 @@ Sentry.init({ } return event; }, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/langgraph/instrument-agent.mjs b/dev-packages/node-integration-tests/suites/tracing/langgraph/instrument-agent.mjs index 9e2bd4557104..b080c8d5ec03 100644 --- a/dev-packages/node-integration-tests/suites/tracing/langgraph/instrument-agent.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/langgraph/instrument-agent.mjs @@ -7,8 +7,6 @@ Sentry.init({ release: '1.0', tracesSampleRate: 1.0, dataCollection: { genAI: { inputs: true, outputs: true } }, - // This suite asserts on gen_ai spans embedded in the transaction, so opt out of span streaming. - streamGenAiSpans: false, transport: loggingTransport, beforeSendTransaction: event => { // Filter out mock express server transactions diff --git a/dev-packages/node-integration-tests/suites/tracing/langgraph/instrument-no-truncation.mjs b/dev-packages/node-integration-tests/suites/tracing/langgraph/instrument-no-truncation.mjs index 280351128d74..9887a7c88c73 100644 --- a/dev-packages/node-integration-tests/suites/tracing/langgraph/instrument-no-truncation.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/langgraph/instrument-no-truncation.mjs @@ -15,5 +15,4 @@ Sentry.init({ enableTruncation: false, }), ], - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/langgraph/instrument-streaming-with-truncation.mjs b/dev-packages/node-integration-tests/suites/tracing/langgraph/instrument-streaming-with-truncation.mjs index 3650f937ce19..c76ba5928e20 100644 --- a/dev-packages/node-integration-tests/suites/tracing/langgraph/instrument-streaming-with-truncation.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/langgraph/instrument-streaming-with-truncation.mjs @@ -13,5 +13,4 @@ Sentry.init({ enableTruncation: true, }), ], - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/langgraph/instrument-streaming.mjs b/dev-packages/node-integration-tests/suites/tracing/langgraph/instrument-streaming.mjs index 1e13bd68fe06..657bed0a3a8c 100644 --- a/dev-packages/node-integration-tests/suites/tracing/langgraph/instrument-streaming.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/langgraph/instrument-streaming.mjs @@ -8,5 +8,4 @@ Sentry.init({ dataCollection: { genAI: { inputs: true, outputs: true } }, transport: loggingTransport, traceLifecycle: 'stream', - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/langgraph/instrument-with-pii.mjs b/dev-packages/node-integration-tests/suites/tracing/langgraph/instrument-with-pii.mjs index 97de014329cf..bdf24d6bbd0e 100644 --- a/dev-packages/node-integration-tests/suites/tracing/langgraph/instrument-with-pii.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/langgraph/instrument-with-pii.mjs @@ -8,5 +8,4 @@ Sentry.init({ tracesSampleRate: 1.0, dataCollection: { genAI: { inputs: true, outputs: true } }, transport: loggingTransport, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/langgraph/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/langgraph/instrument.mjs index 22d1e17ce40e..42052d281304 100644 --- a/dev-packages/node-integration-tests/suites/tracing/langgraph/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/langgraph/instrument.mjs @@ -8,5 +8,4 @@ Sentry.init({ tracesSampleRate: 1.0, dataCollection: { genAI: { inputs: false, outputs: false } }, transport: loggingTransport, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/langgraph/test.ts b/dev-packages/node-integration-tests/suites/tracing/langgraph/test.ts index 2174b33e406e..de47990be403 100644 --- a/dev-packages/node-integration-tests/suites/tracing/langgraph/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/langgraph/test.ts @@ -368,27 +368,31 @@ describe('LangGraph integration', () => { transaction: event => { const spans = event.spans ?? []; expect(event.transaction).toBe('main'); - expect(spans).toHaveLength(3); + expect(spans).toContainEqual(expect.objectContaining({ op: 'http.client' })); + }, + }) + .expect({ + span: container => { + const spans = container.items; expect(spans).toContainEqual( expect.objectContaining({ - data: expect.objectContaining({ - [GEN_AI_OPERATION_NAME]: 'invoke_agent', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'gen_ai.invoke_agent', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ai.langgraph', - [GEN_AI_AGENT_NAME]: 'helpful_assistant', - [GEN_AI_PIPELINE_NAME]: 'helpful_assistant', - }), - description: 'invoke_agent helpful_assistant', - op: 'gen_ai.invoke_agent', - origin: 'auto.ai.langgraph', + name: 'invoke_agent helpful_assistant', status: 'ok', + attributes: expect.objectContaining({ + [GEN_AI_OPERATION_NAME]: expect.objectContaining({ value: 'invoke_agent' }), + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: expect.objectContaining({ value: 'gen_ai.invoke_agent' }), + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: expect.objectContaining({ value: 'auto.ai.langgraph' }), + [GEN_AI_AGENT_NAME]: expect.objectContaining({ value: 'helpful_assistant' }), + [GEN_AI_PIPELINE_NAME]: expect.objectContaining({ value: 'helpful_assistant' }), + }), }), ); - expect(spans).toContainEqual(expect.objectContaining({ op: 'http.client' })); expect(spans).toContainEqual( expect.objectContaining({ - data: expect.objectContaining({ [GEN_AI_AGENT_NAME]: 'helpful_assistant' }), - op: 'gen_ai.chat', + attributes: expect.objectContaining({ + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: expect.objectContaining({ value: 'gen_ai.chat' }), + [GEN_AI_AGENT_NAME]: expect.objectContaining({ value: 'helpful_assistant' }), + }), }), ); }, @@ -407,43 +411,49 @@ describe('LangGraph integration', () => { transaction: event => { const spans = event.spans ?? []; expect(event.transaction).toBe('main'); - expect(spans).toHaveLength(9); + expect(spans.filter(span => span.op === 'http.client')).toHaveLength(3); + }, + }) + .expect({ + span: container => { + const spans = container.items; expect(spans).toContainEqual( expect.objectContaining({ - data: expect.objectContaining({ - [GEN_AI_OPERATION_NAME]: 'invoke_agent', - [GEN_AI_AGENT_NAME]: 'math_assistant', - }), - op: 'gen_ai.invoke_agent', status: 'ok', + attributes: expect.objectContaining({ + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: expect.objectContaining({ value: 'gen_ai.invoke_agent' }), + [GEN_AI_OPERATION_NAME]: expect.objectContaining({ value: 'invoke_agent' }), + [GEN_AI_AGENT_NAME]: expect.objectContaining({ value: 'math_assistant' }), + }), }), ); expect(spans).toContainEqual( expect.objectContaining({ - data: expect.objectContaining({ - [GEN_AI_OPERATION_NAME]: 'execute_tool', - [GEN_AI_TOOL_NAME]: 'add', - 'gen_ai.tool.type': 'function', - }), - description: 'execute_tool add', - op: 'gen_ai.execute_tool', + name: 'execute_tool add', status: 'ok', + attributes: expect.objectContaining({ + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: expect.objectContaining({ value: 'gen_ai.execute_tool' }), + [GEN_AI_OPERATION_NAME]: expect.objectContaining({ value: 'execute_tool' }), + [GEN_AI_TOOL_NAME]: expect.objectContaining({ value: 'add' }), + 'gen_ai.tool.type': expect.objectContaining({ value: 'function' }), + }), }), ); expect(spans).toContainEqual( expect.objectContaining({ - data: expect.objectContaining({ - [GEN_AI_OPERATION_NAME]: 'execute_tool', - [GEN_AI_TOOL_NAME]: 'multiply', - 'gen_ai.tool.type': 'function', - }), - description: 'execute_tool multiply', - op: 'gen_ai.execute_tool', + name: 'execute_tool multiply', status: 'ok', + attributes: expect.objectContaining({ + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: expect.objectContaining({ value: 'gen_ai.execute_tool' }), + [GEN_AI_OPERATION_NAME]: expect.objectContaining({ value: 'execute_tool' }), + [GEN_AI_TOOL_NAME]: expect.objectContaining({ value: 'multiply' }), + 'gen_ai.tool.type': expect.objectContaining({ value: 'function' }), + }), }), ); - expect(spans.filter(span => span.op === 'http.client')).toHaveLength(3); - expect(spans.filter(span => span.op === 'gen_ai.chat')).toHaveLength(3); + expect( + spans.filter(span => span.attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP]?.value === 'gen_ai.chat'), + ).toHaveLength(3); }, }) .start() @@ -457,12 +467,16 @@ describe('LangGraph integration', () => { .ignore('event') .expect({ transaction: event => { - const spans = event.spans ?? []; - const chatSpans = spans.filter(s => s.op === 'gen_ai.chat'); + expect(event.transaction).toBe('main'); + }, + }) + .expect({ + span: container => { + const chatSpans = container.items.filter( + s => s.attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP]?.value === 'gen_ai.chat', + ); expect(chatSpans).toHaveLength(1); - expect(chatSpans[0]?.data).toMatchObject({ - [GEN_AI_AGENT_NAME]: 'plain_assistant', - }); + expect(chatSpans[0]?.attributes[GEN_AI_AGENT_NAME]?.value).toBe('plain_assistant'); }, }) .start() diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/instrument-no-truncation.mjs b/dev-packages/node-integration-tests/suites/tracing/openai/instrument-no-truncation.mjs index 791b70e2bf7b..6f77c39a09dc 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/instrument-no-truncation.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/openai/instrument-no-truncation.mjs @@ -21,5 +21,4 @@ Sentry.init({ } return event; }, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/instrument-root-span.mjs b/dev-packages/node-integration-tests/suites/tracing/openai/instrument-root-span.mjs index 22d1e17ce40e..42052d281304 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/instrument-root-span.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/openai/instrument-root-span.mjs @@ -8,5 +8,4 @@ Sentry.init({ tracesSampleRate: 1.0, dataCollection: { genAI: { inputs: false, outputs: false } }, transport: loggingTransport, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/instrument-streaming-with-truncation.mjs b/dev-packages/node-integration-tests/suites/tracing/openai/instrument-streaming-with-truncation.mjs index e31ae664dc52..c61dffa4c1f1 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/instrument-streaming-with-truncation.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/openai/instrument-streaming-with-truncation.mjs @@ -13,5 +13,4 @@ Sentry.init({ enableTruncation: true, }), ], - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/instrument-streaming.mjs b/dev-packages/node-integration-tests/suites/tracing/openai/instrument-streaming.mjs index 1e13bd68fe06..657bed0a3a8c 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/instrument-streaming.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/openai/instrument-streaming.mjs @@ -8,5 +8,4 @@ Sentry.init({ dataCollection: { genAI: { inputs: true, outputs: true } }, transport: loggingTransport, traceLifecycle: 'stream', - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/instrument-with-options.mjs b/dev-packages/node-integration-tests/suites/tracing/openai/instrument-with-options.mjs index b8f6a1d0472c..0d6548af1291 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/instrument-with-options.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/openai/instrument-with-options.mjs @@ -20,5 +20,4 @@ Sentry.init({ } return event; }, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/instrument-with-pii.mjs b/dev-packages/node-integration-tests/suites/tracing/openai/instrument-with-pii.mjs index fb9a5c468fe0..2b78a3ea1987 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/instrument-with-pii.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/openai/instrument-with-pii.mjs @@ -14,5 +14,4 @@ Sentry.init({ } return event; }, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/instrument-with-truncation.mjs b/dev-packages/node-integration-tests/suites/tracing/openai/instrument-with-truncation.mjs index 6c836be57862..771f507deafd 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/instrument-with-truncation.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/openai/instrument-with-truncation.mjs @@ -15,5 +15,4 @@ Sentry.init({ } return event; }, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/openai/instrument.mjs index 67e44c5ce801..2097d76a4eff 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/openai/instrument.mjs @@ -14,5 +14,4 @@ Sentry.init({ } return event; }, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/openai-tool-calls/instrument-with-pii.mjs b/dev-packages/node-integration-tests/suites/tracing/openai/openai-tool-calls/instrument-with-pii.mjs index 97de014329cf..bdf24d6bbd0e 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/openai-tool-calls/instrument-with-pii.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/openai/openai-tool-calls/instrument-with-pii.mjs @@ -8,5 +8,4 @@ Sentry.init({ tracesSampleRate: 1.0, dataCollection: { genAI: { inputs: true, outputs: true } }, transport: loggingTransport, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/openai-tool-calls/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/openai/openai-tool-calls/instrument.mjs index 22d1e17ce40e..42052d281304 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/openai-tool-calls/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/openai/openai-tool-calls/instrument.mjs @@ -8,5 +8,4 @@ Sentry.init({ tracesSampleRate: 1.0, dataCollection: { genAI: { inputs: false, outputs: false } }, transport: loggingTransport, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/v6/instrument-root-span.mjs b/dev-packages/node-integration-tests/suites/tracing/openai/v6/instrument-root-span.mjs index 22d1e17ce40e..42052d281304 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/v6/instrument-root-span.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/openai/v6/instrument-root-span.mjs @@ -8,5 +8,4 @@ Sentry.init({ tracesSampleRate: 1.0, dataCollection: { genAI: { inputs: false, outputs: false } }, transport: loggingTransport, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/v6/instrument-with-options.mjs b/dev-packages/node-integration-tests/suites/tracing/openai/v6/instrument-with-options.mjs index 2bd71c13b8f1..0169719e5d59 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/v6/instrument-with-options.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/openai/v6/instrument-with-options.mjs @@ -21,5 +21,4 @@ Sentry.init({ } return event; }, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/v6/instrument-with-pii.mjs b/dev-packages/node-integration-tests/suites/tracing/openai/v6/instrument-with-pii.mjs index fb9a5c468fe0..2b78a3ea1987 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/v6/instrument-with-pii.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/openai/v6/instrument-with-pii.mjs @@ -14,5 +14,4 @@ Sentry.init({ } return event; }, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/v6/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/openai/v6/instrument.mjs index 67e44c5ce801..2097d76a4eff 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/v6/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/openai/v6/instrument.mjs @@ -14,5 +14,4 @@ Sentry.init({ } return event; }, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/vercelai/instrument-no-truncation.mjs b/dev-packages/node-integration-tests/suites/tracing/vercelai/instrument-no-truncation.mjs index a53f03f793ac..12aa5902f889 100644 --- a/dev-packages/node-integration-tests/suites/tracing/vercelai/instrument-no-truncation.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/vercelai/instrument-no-truncation.mjs @@ -15,5 +15,4 @@ Sentry.init({ enableTruncation: false, }), ], - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/vercelai/instrument-with-pii.mjs b/dev-packages/node-integration-tests/suites/tracing/vercelai/instrument-with-pii.mjs index 97de014329cf..bdf24d6bbd0e 100644 --- a/dev-packages/node-integration-tests/suites/tracing/vercelai/instrument-with-pii.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/vercelai/instrument-with-pii.mjs @@ -8,5 +8,4 @@ Sentry.init({ tracesSampleRate: 1.0, dataCollection: { genAI: { inputs: true, outputs: true } }, transport: loggingTransport, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/vercelai/instrument-with-truncation.mjs b/dev-packages/node-integration-tests/suites/tracing/vercelai/instrument-with-truncation.mjs index 6bf2b1d76455..7ea31bb0bfdc 100644 --- a/dev-packages/node-integration-tests/suites/tracing/vercelai/instrument-with-truncation.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/vercelai/instrument-with-truncation.mjs @@ -9,5 +9,4 @@ Sentry.init({ dataCollection: { genAI: { inputs: true, outputs: true } }, transport: loggingTransport, integrations: [Sentry.vercelAIIntegration({ enableTruncation: true })], - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/vercelai/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/vercelai/instrument.mjs index 6e1234b5c975..170ad6f6a702 100644 --- a/dev-packages/node-integration-tests/suites/tracing/vercelai/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/vercelai/instrument.mjs @@ -7,5 +7,4 @@ Sentry.init({ release: '1.0', tracesSampleRate: 1.0, transport: loggingTransport, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/vercelai/v5/instrument-with-pii.mjs b/dev-packages/node-integration-tests/suites/tracing/vercelai/v5/instrument-with-pii.mjs index 091136a37e82..7ca11a34673e 100644 --- a/dev-packages/node-integration-tests/suites/tracing/vercelai/v5/instrument-with-pii.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/vercelai/v5/instrument-with-pii.mjs @@ -9,5 +9,4 @@ Sentry.init({ // inputs and outputs are enabeld by default when opting into dataCollection dataCollection: {}, transport: loggingTransport, - streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/vercelai/v5/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/vercelai/v5/instrument.mjs index 6e1234b5c975..170ad6f6a702 100644 --- a/dev-packages/node-integration-tests/suites/tracing/vercelai/v5/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/vercelai/v5/instrument.mjs @@ -7,5 +7,4 @@ Sentry.init({ release: '1.0', tracesSampleRate: 1.0, transport: loggingTransport, - streamGenAiSpans: true, }); diff --git a/packages/core/src/tracing/ai/utils.ts b/packages/core/src/tracing/ai/utils.ts index 9a7dabced2d6..906b28a782f0 100644 --- a/packages/core/src/tracing/ai/utils.ts +++ b/packages/core/src/tracing/ai/utils.ts @@ -4,7 +4,6 @@ */ import { captureException } from '../../exports'; import { getClient } from '../../currentScopes'; -import { hasSpanStreamingEnabled } from '../spans/hasSpanStreamingEnabled'; import type { Span } from '../../types/span'; import { isThenable } from '../../utils/is'; import { @@ -61,22 +60,17 @@ export function resolveAIRecordingOptions(options? /** * Resolves whether truncation should be enabled. * If the user explicitly set `enableTruncation`, that value is used. - * Otherwise, truncation is disabled whenever gen_ai spans are sent through the span streaming / v2 - * span path, i.e. full span streaming (`traceLifecycle: 'stream'`) or `streamGenAiSpans`. That path - * is not subject to the transaction payload-size limits that truncation works around, so the full - * message data can be retained. `streamGenAiSpans` is opt-out (on unless explicitly set to `false`). + * Otherwise, truncation is disabled because gen_ai spans are always sent through the v2 span path + * (full span streaming via `traceLifecycle: 'stream'`, or extraction into a v2 span envelope for + * static transactions). That path is not subject to the transaction payload-size limits that + * truncation works around, so the full message data can be retained. */ export function shouldEnableTruncation(enableTruncation: boolean | undefined): boolean { if (enableTruncation !== undefined) { return enableTruncation; } - const client = getClient(); - if (!client) { - return true; - } - - return !hasSpanStreamingEnabled(client) && client.getOptions().streamGenAiSpans === false; + return !getClient(); } /** diff --git a/packages/core/src/tracing/spans/extractGenAiSpans.ts b/packages/core/src/tracing/spans/extractGenAiSpans.ts index 26c8606701e4..f41c8a7b89e5 100644 --- a/packages/core/src/tracing/spans/extractGenAiSpans.ts +++ b/packages/core/src/tracing/spans/extractGenAiSpans.ts @@ -20,7 +20,6 @@ export function extractGenAiSpansFromEvent(event: Event, client: Client): SpanCo event.type !== 'transaction' || !event.spans?.length || !event.sdkProcessingMetadata?.hasGenAiSpans || - client.getOptions().streamGenAiSpans === false || hasSpanStreamingEnabled(client) ) { return undefined; diff --git a/packages/core/src/types/options.ts b/packages/core/src/types/options.ts index 4aa93e6ac12e..d150f0cddad0 100644 --- a/packages/core/src/types/options.ts +++ b/packages/core/src/types/options.ts @@ -555,20 +555,6 @@ export interface ClientOptions { expect(shouldEnableTruncation(undefined)).toBe(true); }); - it('defaults to false with a default client (streamGenAiSpans is opt-out)', () => { + it('defaults to false with a default client', () => { setupClient(); expect(shouldEnableTruncation(undefined)).toBe(false); }); - it('defaults to true when streamGenAiSpans is explicitly disabled', () => { - setupClient({ streamGenAiSpans: false }); - expect(shouldEnableTruncation(undefined)).toBe(true); - }); - - it('defaults to false when streamGenAiSpans is enabled', () => { - setupClient({ streamGenAiSpans: true }); - expect(shouldEnableTruncation(undefined)).toBe(false); - }); - it('defaults to false when span streaming is enabled (traceLifecycle: stream)', () => { setupClient({ traceLifecycle: 'stream' }); expect(shouldEnableTruncation(undefined)).toBe(false); }); - it('explicit enableTruncation: true overrides streamGenAiSpans', () => { - setupClient({ streamGenAiSpans: true }); + it('explicit enableTruncation: true overrides the default', () => { + setupClient(); expect(shouldEnableTruncation(true)).toBe(true); }); diff --git a/packages/core/test/lib/tracing/spans/extractGenAiSpans.test.ts b/packages/core/test/lib/tracing/spans/extractGenAiSpans.test.ts index de47457144d3..f21e164dccba 100644 --- a/packages/core/test/lib/tracing/spans/extractGenAiSpans.test.ts +++ b/packages/core/test/lib/tracing/spans/extractGenAiSpans.test.ts @@ -120,7 +120,7 @@ describe('extractGenAiSpansFromEvent', () => { expect(extractGenAiSpansFromEvent(event, makeClient())).toBeUndefined(); }); - it('extracts gen_ai spans by default when streamGenAiSpans is unset', () => { + it('extracts gen_ai spans by default', () => { const event = makeTransactionEvent([makeSpanJSON({ op: 'gen_ai.chat', span_id: 'genai001' })], true); const result = extractGenAiSpansFromEvent(event, makeClient()); @@ -130,14 +130,6 @@ describe('extractGenAiSpansFromEvent', () => { expect(event.spans).toHaveLength(0); }); - it('returns undefined when streamGenAiSpans is explicitly disabled', () => { - const event = makeTransactionEvent([makeSpanJSON({ op: 'gen_ai.chat' })]); - const client = makeClient({ streamGenAiSpans: false }); - - expect(extractGenAiSpansFromEvent(event, client)).toBeUndefined(); - expect(event.spans).toHaveLength(1); - }); - it('returns undefined when span streaming is enabled', () => { const event = makeTransactionEvent([makeSpanJSON({ op: 'gen_ai.chat' })]); const client = makeClient({ traceLifecycle: 'stream' }); From ab3644fb46a67064a926c2268ca77a06466a3b79 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Wed, 29 Jul 2026 10:38:49 +0200 Subject: [PATCH 2/2] test(cloudflare): Remove dangling streamGenAiSpans reference in cloudflare-agent e2e app The cloudflare-agent e2e app landed on develop after this branch was cut and still set `streamGenAiSpans: false` to keep gen_ai spans embedded in the transaction. With the flag gone, gen_ai spans always stream as a separate span container, so the app dropped the option and the AI streaming test now asserts on the streamed span (matched to the request via the transaction trace id). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../tests/ai-streaming.test.ts | 66 +++++++++---------- .../tests/chat-conversation.test.ts | 25 +++---- .../cloudflare-agent/worker/index.ts | 3 - 3 files changed, 39 insertions(+), 55 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-agent/tests/ai-streaming.test.ts b/dev-packages/e2e-tests/test-applications/cloudflare-agent/tests/ai-streaming.test.ts index de64bff1dbfd..7faa10926ce6 100644 --- a/dev-packages/e2e-tests/test-applications/cloudflare-agent/tests/ai-streaming.test.ts +++ b/dev-packages/e2e-tests/test-applications/cloudflare-agent/tests/ai-streaming.test.ts @@ -1,5 +1,6 @@ import { expect, test } from '@playwright/test'; -import { waitForTransaction } from '@sentry-internal/test-utils'; +import { getSpanOp, waitForStreamedSpan, waitForTransaction } from '@sentry-internal/test-utils'; +import type { SerializedStreamedSpan } from '@sentry/core'; // Drives Workers AI through the real Cloudflare Agents SDK + Vercel AI SDK + `workers-ai-provider` // stack (the OpenAI-compatible SSE shape, `choices[].delta.content`) from an Agent's `onRequest`. @@ -7,54 +8,47 @@ import { waitForTransaction } from '@sentry-internal/test-utils'; // production where only input + usage survived. The model output is read from // `gen_ai.output.messages`, so the streaming instrumentation must emit it alongside the // deprecated `gen_ai.response.text`. -function assertGenAiStreamingSpan(spans: Array> | undefined): void { - const genAiSpan = (spans ?? []).find(span => span.op === 'gen_ai.chat'); - - expect(genAiSpan).toBeDefined(); - expect(genAiSpan.origin).toBe('auto.ai.cloudflare.workers_ai'); - expect(genAiSpan.data).toEqual( - expect.objectContaining({ - 'sentry.origin': 'auto.ai.cloudflare.workers_ai', - 'gen_ai.operation.name': 'chat', - 'gen_ai.request.model': '@cf/meta/llama-3.1-8b-instruct', - 'gen_ai.response.streaming': true, - 'gen_ai.response.text': 'The capital of France is Paris.', - 'gen_ai.output.messages': JSON.stringify([ - { role: 'assistant', parts: [{ type: 'text', content: 'The capital of France is Paris.' }] }, - ]), - 'gen_ai.usage.input_tokens': 15, - 'gen_ai.usage.output_tokens': 8, - 'gen_ai.usage.total_tokens': 23, - }), +function assertGenAiStreamingSpan(span: SerializedStreamedSpan): void { + expect(getSpanOp(span)).toBe('gen_ai.chat'); + expect(span.attributes['sentry.origin']?.value).toBe('auto.ai.cloudflare.workers_ai'); + expect(span.attributes['gen_ai.operation.name']?.value).toBe('chat'); + expect(span.attributes['gen_ai.request.model']?.value).toBe('@cf/meta/llama-3.1-8b-instruct'); + expect(span.attributes['gen_ai.response.streaming']?.value).toBe(true); + expect(span.attributes['gen_ai.response.text']?.value).toBe('The capital of France is Paris.'); + expect(span.attributes['gen_ai.output.messages']?.value).toBe( + JSON.stringify([{ role: 'assistant', parts: [{ type: 'text', content: 'The capital of France is Paris.' }] }]), ); + expect(span.attributes['gen_ai.usage.input_tokens']?.value).toBe(15); + expect(span.attributes['gen_ai.usage.output_tokens']?.value).toBe(8); + expect(span.attributes['gen_ai.usage.total_tokens']?.value).toBe(23); } test('captures Workers AI streaming output when driven via an Agent', async ({ request, baseURL }) => { - const transactionPromise = waitForTransaction('cloudflare-agent', transactionEvent => { - return ( - transactionEvent.transaction === 'GET /agents/my-agent/test' && - (transactionEvent.spans ?? []).some(span => span.op === 'gen_ai.chat') - ); - }); + const spanPromise = waitForStreamedSpan('cloudflare-agent', span => getSpanOp(span) === 'gen_ai.chat'); + const transactionPromise = waitForTransaction( + 'cloudflare-agent', + transactionEvent => transactionEvent.transaction === 'GET /agents/my-agent/test', + ); const response = await request.get(`${baseURL}/agents/my-agent/test`); expect(response.ok()).toBe(true); - const transaction = await transactionPromise; - assertGenAiStreamingSpan(transaction.spans); + const [span, transaction] = await Promise.all([spanPromise, transactionPromise]); + expect(span.trace_id).toBe(transaction.contexts?.trace?.trace_id); + assertGenAiStreamingSpan(span); }); test('captures Workers AI streaming output when driven via an AIChatAgent', async ({ request, baseURL }) => { - const transactionPromise = waitForTransaction('cloudflare-agent', transactionEvent => { - return ( - transactionEvent.transaction === 'GET /agents/my-chat-agent/test' && - (transactionEvent.spans ?? []).some(span => span.op === 'gen_ai.chat') - ); - }); + const spanPromise = waitForStreamedSpan('cloudflare-agent', span => getSpanOp(span) === 'gen_ai.chat'); + const transactionPromise = waitForTransaction( + 'cloudflare-agent', + transactionEvent => transactionEvent.transaction === 'GET /agents/my-chat-agent/test', + ); const response = await request.get(`${baseURL}/agents/my-chat-agent/test`); expect(response.ok()).toBe(true); - const transaction = await transactionPromise; - assertGenAiStreamingSpan(transaction.spans); + const [span, transaction] = await Promise.all([spanPromise, transactionPromise]); + expect(span.trace_id).toBe(transaction.contexts?.trace?.trace_id); + assertGenAiStreamingSpan(span); }); diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-agent/tests/chat-conversation.test.ts b/dev-packages/e2e-tests/test-applications/cloudflare-agent/tests/chat-conversation.test.ts index 5d7c6c2fbdd3..997efff03b42 100644 --- a/dev-packages/e2e-tests/test-applications/cloudflare-agent/tests/chat-conversation.test.ts +++ b/dev-packages/e2e-tests/test-applications/cloudflare-agent/tests/chat-conversation.test.ts @@ -1,5 +1,5 @@ import { expect, test } from '@playwright/test'; -import { waitForTransaction } from '@sentry-internal/test-utils'; +import { getSpanOp, waitForStreamedSpan, waitForTransaction } from '@sentry-internal/test-utils'; import { sendChatMessage } from './agent-socket'; // In the Agents model one agent instance is one conversation, so the instance name is the @@ -7,12 +7,11 @@ import { sendChatMessage } from './agent-socket'; const CONVERSATION_ID = 'chat-conv-instance'; test('stamps the conversation id on gen_ai spans created inside a chat turn', async ({ baseURL }) => { - const transactionPromise = waitForTransaction('cloudflare-agent', transactionEvent => { - return ( - transactionEvent.transaction === 'webSocketMessage' && - (transactionEvent.spans ?? []).some(span => span.op === 'gen_ai.chat') - ); - }); + const spanPromise = waitForStreamedSpan('cloudflare-agent', span => getSpanOp(span) === 'gen_ai.chat'); + const transactionPromise = waitForTransaction( + 'cloudflare-agent', + transactionEvent => transactionEvent.transaction === 'webSocketMessage', + ); await sendChatMessage(baseURL!, { binding: 'my-chat-agent', @@ -20,13 +19,7 @@ test('stamps the conversation id on gen_ai spans created inside a chat turn', as prompt: 'What is the capital of France?', }); - const transaction = await transactionPromise; - - const genAiSpan = (transaction.spans ?? []).find(span => span.op === 'gen_ai.chat'); - expect(genAiSpan).toBeDefined(); - expect(genAiSpan?.data).toEqual( - expect.objectContaining({ - 'gen_ai.conversation.id': CONVERSATION_ID, - }), - ); + const [genAiSpan, transaction] = await Promise.all([spanPromise, transactionPromise]); + expect(genAiSpan.trace_id).toBe(transaction.contexts?.trace?.trace_id); + expect(genAiSpan.attributes['gen_ai.conversation.id']?.value).toBe(CONVERSATION_ID); }); diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-agent/worker/index.ts b/dev-packages/e2e-tests/test-applications/cloudflare-agent/worker/index.ts index c6284daca542..46ac9c5d909a 100644 --- a/dev-packages/e2e-tests/test-applications/cloudflare-agent/worker/index.ts +++ b/dev-packages/e2e-tests/test-applications/cloudflare-agent/worker/index.ts @@ -15,9 +15,6 @@ const sentryOptions = (env: Env) => ({ tracesSampleRate: 1, enableRpcTracePropagation: true, durableObjectStorageSpanAllowlist: ['cf_user_key'], - // Keep gen_ai spans embedded in the transaction (instead of streamed as a separate envelope - // container) so they can be asserted on `transaction.spans`. - streamGenAiSpans: false, }); /**