weflayr 0.22.0 → 0.22.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/.prettierignore +2 -0
  2. package/dist/api/caseConversion.d.ts +16 -0
  3. package/dist/api/caseConversion.js +45 -0
  4. package/dist/api/generated/client/client.gen.d.ts +2 -0
  5. package/dist/api/generated/client/client.gen.js +220 -0
  6. package/dist/api/generated/client/index.d.ts +10 -0
  7. package/dist/api/generated/client/index.js +17 -0
  8. package/dist/api/generated/client/types.gen.d.ts +120 -0
  9. package/dist/api/generated/client/types.gen.js +3 -0
  10. package/dist/api/generated/client/utils.gen.d.ts +37 -0
  11. package/dist/api/generated/client/utils.gen.js +239 -0
  12. package/dist/api/generated/client.gen.d.ts +12 -0
  13. package/dist/api/generated/client.gen.js +6 -0
  14. package/dist/api/generated/core/auth.gen.d.ts +25 -0
  15. package/dist/api/generated/core/auth.gen.js +18 -0
  16. package/dist/api/generated/core/bodySerializer.gen.d.ts +25 -0
  17. package/dist/api/generated/core/bodySerializer.gen.js +60 -0
  18. package/dist/api/generated/core/params.gen.d.ts +43 -0
  19. package/dist/api/generated/core/params.gen.js +112 -0
  20. package/dist/api/generated/core/pathSerializer.gen.d.ts +33 -0
  21. package/dist/api/generated/core/pathSerializer.gen.js +115 -0
  22. package/dist/api/generated/core/queryKeySerializer.gen.d.ts +18 -0
  23. package/dist/api/generated/core/queryKeySerializer.gen.js +98 -0
  24. package/dist/api/generated/core/serverSentEvents.gen.d.ts +71 -0
  25. package/dist/api/generated/core/serverSentEvents.gen.js +135 -0
  26. package/dist/api/generated/core/types.gen.d.ts +83 -0
  27. package/dist/api/generated/core/types.gen.js +3 -0
  28. package/dist/api/generated/core/utils.gen.d.ts +19 -0
  29. package/dist/api/generated/core/utils.gen.js +93 -0
  30. package/dist/api/generated/facade.gen.d.ts +208 -0
  31. package/dist/api/generated/facade.gen.js +52 -0
  32. package/dist/api/generated/index.d.ts +2 -0
  33. package/dist/api/generated/index.js +17 -0
  34. package/dist/api/generated/sdk.gen.d.ts +63 -0
  35. package/dist/api/generated/sdk.gen.js +125 -0
  36. package/dist/api/generated/types.gen.d.ts +711 -0
  37. package/dist/api/generated/types.gen.js +3 -0
  38. package/dist/api/index.d.ts +27 -0
  39. package/dist/api/index.js +48 -0
  40. package/dist/api/makeEndpoint.d.ts +27 -0
  41. package/dist/api/makeEndpoint.js +37 -0
  42. package/index.d.ts +24 -15
  43. package/openapi-ts.config.mjs +9 -0
  44. package/package.json +9 -2
  45. package/scripts/generate_facade.mjs +86 -0
  46. package/src/api/caseConversion.ts +58 -0
  47. package/src/api/generated/client/client.gen.ts +277 -0
  48. package/src/api/generated/client/index.ts +27 -0
  49. package/src/api/generated/client/types.gen.ts +218 -0
  50. package/src/api/generated/client/utils.gen.ts +316 -0
  51. package/src/api/generated/client.gen.ts +16 -0
  52. package/src/api/generated/core/auth.gen.ts +48 -0
  53. package/src/api/generated/core/bodySerializer.gen.ts +82 -0
  54. package/src/api/generated/core/params.gen.ts +178 -0
  55. package/src/api/generated/core/pathSerializer.gen.ts +171 -0
  56. package/src/api/generated/core/queryKeySerializer.gen.ts +117 -0
  57. package/src/api/generated/core/serverSentEvents.gen.ts +242 -0
  58. package/src/api/generated/core/types.gen.ts +110 -0
  59. package/src/api/generated/core/utils.gen.ts +140 -0
  60. package/src/api/generated/facade.gen.ts +80 -0
  61. package/src/api/generated/index.ts +4 -0
  62. package/src/api/generated/sdk.gen.ts +139 -0
  63. package/src/api/generated/types.gen.ts +792 -0
  64. package/src/api/index.ts +45 -0
  65. package/src/api/makeEndpoint.ts +54 -0
  66. package/src/auto-instrument.js +19 -6
  67. package/src/index.js +5 -0
  68. package/src/instrumentation/google/get_provider_name.js +2 -1
  69. package/src/instrumentation/vercel/full_instrumentation.js +44 -0
  70. package/src/otel/propagation.js +21 -12
  71. package/src/otel/span-processor.js +11 -1
  72. package/src/otel/vercel-ai-sdk-span-filter.js +79 -0
  73. package/tests/api/index.test.js +330 -0
  74. package/tests/index.test.js +48 -15
  75. package/tests/instrumentation/vercel/full_instrumentation.test.js +16 -0
  76. package/tests/otel/vercel-ai-sdk-span-filter.test.js +159 -0
  77. package/tsconfig.json +18 -0
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Entry point for the Weflayr public API (revenue, key metrics, customer tags).
3
+ *
4
+ * The endpoint functions and types under ./generated are produced from the
5
+ * platform's OpenAPI spec (`make generate-sdk-api-clients`) and speak the
6
+ * wire's snake_case, same as the Python SDK. generated/facade.gen.ts (also
7
+ * produced by that command, via scripts/generate_facade.mjs) wraps every
8
+ * endpoint so JS callers use camelCase instead - see ./caseConversion and
9
+ * ./makeEndpoint. This module is the handwritten front door that configures
10
+ * their client:
11
+ *
12
+ * const weflayr = require('weflayr');
13
+ *
14
+ * const apiClient = weflayr.client({ apiKey: 'wf-...' });
15
+ * const { data } = await weflayr.api.getAllRevenue({ client: apiClient, throwOnError: true });
16
+ */
17
+
18
+ import { type Client, createClient, createConfig } from './generated/client';
19
+
20
+ export const DEFAULT_BASE_URL = 'https://app.weflayr.com/api';
21
+
22
+ export interface ApiClientOptions {
23
+ /** A project API key, created in the Weflayr dashboard. Defaults to the `WEFLAYR_API_KEY` environment variable. */
24
+ apiKey?: string;
25
+ /** Where the API lives — override for local or self-hosted deployments. */
26
+ baseUrl?: string;
27
+ }
28
+
29
+ /** A client for the public API, authenticated with a project API key. */
30
+ export function client(options: ApiClientOptions = {}): Client {
31
+ const apiKey = options.apiKey || process.env.WEFLAYR_API_KEY;
32
+ if (!apiKey) {
33
+ throw new Error(
34
+ 'weflayr.client: `apiKey` is required (pass it explicitly or set WEFLAYR_API_KEY)'
35
+ );
36
+ }
37
+ return createClient(
38
+ createConfig({
39
+ baseUrl: options.baseUrl ?? DEFAULT_BASE_URL,
40
+ auth: () => apiKey,
41
+ })
42
+ );
43
+ }
44
+
45
+ export * from './generated/facade.gen';
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Wraps a generated public-API endpoint function so its `body`/`path`/`query`
3
+ * are converted from camelCase to the wire's snake_case on the way out, and
4
+ * its `data`/`error` are converted back to camelCase on the way in.
5
+ *
6
+ * Instantiated once per endpoint in generated/facade.gen.ts, produced by
7
+ * scripts/generate_facade.mjs - see that script for how the per-endpoint
8
+ * type arguments are derived.
9
+ */
10
+
11
+ import { toCamelCaseDeep, toSnakeCaseDeep } from './caseConversion';
12
+ import type { Client } from './generated/client';
13
+
14
+ export interface ClientOption {
15
+ client: Client;
16
+ throwOnError?: boolean;
17
+ headers?: HeadersInit;
18
+ }
19
+
20
+ export interface CallResult<TData, TError> {
21
+ data?: TData;
22
+ error?: TError;
23
+ request?: Request;
24
+ response?: Response;
25
+ }
26
+
27
+ type WireResult = { data?: unknown; error?: unknown };
28
+
29
+ export function makeEndpoint<TParams extends object, TData, TError>(
30
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
31
+ wireFn: (options: any) => Promise<WireResult>
32
+ ): (options: ClientOption & TParams) => Promise<CallResult<TData, TError>> {
33
+ return async (options) => {
34
+ const wireOptions = { ...options } as Record<string, unknown>;
35
+ for (const key of ['body', 'path', 'query']) {
36
+ if (wireOptions[key] !== undefined) {
37
+ wireOptions[key] = toSnakeCaseDeep(wireOptions[key]);
38
+ }
39
+ }
40
+
41
+ let result: WireResult;
42
+ try {
43
+ result = await wireFn(wireOptions);
44
+ } catch (error) {
45
+ throw toCamelCaseDeep(error as object);
46
+ }
47
+
48
+ return {
49
+ ...result,
50
+ data: result.data !== undefined ? (toCamelCaseDeep(result.data) as TData) : undefined,
51
+ error: result.error !== undefined ? (toCamelCaseDeep(result.error) as TError) : undefined,
52
+ };
53
+ };
54
+ }
@@ -12,16 +12,24 @@ const AiSdk = Object.freeze({
12
12
  COHERE: 'cohere',
13
13
  GOOGLE_GENAI: 'google-genai',
14
14
  ELEVENLABS: 'elevenlabs',
15
+ VERCEL_AI_GATEWAY: 'vercel_ai_gateway',
15
16
  });
16
17
 
17
- // Allowed values for `propagateMetadata({ providerName })`: every SDK the
18
- // auto-instrumentation registry knows, plus providers reached via the OpenAI
19
- // SDK on the wire but reported as themselves
20
- // Those are instrumented via `autoInstrument({ aiSdks: 'openai' })` and labelled
21
- // through `propagateMetadata({ providerName })`, not their own instrumentor.
18
+ // Allowed values for `propagateMetadata({ providerName })`: the actual AI
19
+ // providers a call can be attributed to. Unlike AiSdk this names providers,
20
+ // not SDKs — google-genai is an SDK whose calls are labelled gemini or vertex,
21
+ // and mistral has no Node OTel instrumentor so it is reached through the
22
+ // OpenAI SDK (OpenAI-compatible endpoint) but reported as itself.
22
23
  const AIProviderName = Object.freeze({
23
- ...AiSdk,
24
+ OPENAI: 'openai',
25
+ ANTHROPIC: 'anthropic',
26
+ BEDROCK: 'bedrock',
27
+ COHERE: 'cohere',
28
+ ELEVENLABS: 'elevenlabs',
29
+ MISTRAL: 'mistral',
24
30
  VERCEL_AI_GATEWAY: 'vercel_ai_gateway',
31
+ GEMINI: 'gemini',
32
+ VERTEX: 'vertex',
25
33
  });
26
34
 
27
35
  // The primary instrumentor for each provider SDK. Most are third-party
@@ -40,6 +48,11 @@ const REGISTRY = {
40
48
  requirePath: './instrumentation/elevenlabs/full_instrumentation',
41
49
  className: 'ElevenLabsInstrumentor',
42
50
  },
51
+ [AiSdk.VERCEL_AI_GATEWAY]: {
52
+ package: 'ai',
53
+ requirePath: './instrumentation/vercel/full_instrumentation',
54
+ className: 'VercelAiSdkInstrumentor',
55
+ },
43
56
  };
44
57
 
45
58
  // One complementary instrumentor per provider: a `set_telemetry_attributes`
package/src/index.js CHANGED
@@ -3,6 +3,9 @@
3
3
  const { setup, flush } = require('./setup');
4
4
  const { autoInstrument, AiSdk, AIProviderName } = require('./auto-instrument');
5
5
  const { propagateMetadata } = require('./otel/propagation');
6
+ // Public-API client (revenue, key metrics, customer tags), compiled from the
7
+ // TypeScript in src/api by `npm run build`.
8
+ const api = require('../dist/api');
6
9
 
7
10
  module.exports = {
8
11
  setup,
@@ -11,4 +14,6 @@ module.exports = {
11
14
  AiSdk,
12
15
  AIProviderName,
13
16
  propagateMetadata,
17
+ client: api.client,
18
+ api,
14
19
  };
@@ -7,7 +7,8 @@
7
7
  * `gen_ai.provider.name = "gcp.gen_ai"` and can't tell Vertex from the Gemini
8
8
  * Developer API. We read the client's Vertex flag and return the value to set as
9
9
  * `gen_ai.system` (which the normalizer prefers): `vertex_ai` / `gemini`,
10
- * matching what the Python google-genai instrumentor reports natively. Node-only.
10
+ * matching what the Python google-genai instrumentor reports natively. The
11
+ * worker normalizes `vertex_ai` to the canonical `vertex`. Node-only.
11
12
  */
12
13
 
13
14
  // Read the Vertex-vs-Dev backend off the Models instance's api client.
@@ -0,0 +1,44 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Instrumentor for the Vercel AI SDK (`ai` package, v7+).
5
+ *
6
+ * The AI SDK ships its own OTel integration in `@ai-sdk/otel`; instrumenting
7
+ * is registering it globally so every generateText/streamText/embed call emits
8
+ * GenAI semconv spans through the tracer provider weflayr's setup() installed.
9
+ * `usage: true` adds the `ai.usage.*` token details (reasoning tokens, uncached
10
+ * input) that the semconv attributes do not carry.
11
+ *
12
+ * The aggregate spans the integration emits around each model call
13
+ * (invoke_agent, agent_step, duplicated embeddings spans) are filtered out
14
+ * before export by otel/vercel-ai-sdk-span-filter.js.
15
+ */
16
+ class VercelAiSdkInstrumentor {
17
+ instrument() {
18
+ let registerTelemetry;
19
+ let OpenTelemetry;
20
+ try {
21
+ ({ registerTelemetry } = require('ai'));
22
+ ({ OpenTelemetry } = require('@ai-sdk/otel'));
23
+ } catch (err) {
24
+ throw new Error(
25
+ 'weflayr.autoInstrument("vercel_ai_gateway"): failed to load "ai" / "@ai-sdk/otel". ' +
26
+ `Install them with: npm install ai @ai-sdk/otel\nUnderlying error: ${err.message}`
27
+ );
28
+ }
29
+ // registerTelemetry only exists since v7.
30
+ if (typeof registerTelemetry !== 'function') {
31
+ throw new Error(
32
+ 'weflayr.autoInstrument("vercel_ai_gateway") requires version 7+ of the Vercel AI SDK ' +
33
+ '(the "ai" package), which added global telemetry registration. ' +
34
+ 'Upgrade with: npm install ai@latest @ai-sdk/otel.\n' +
35
+ 'On ai <= 6 telemetry can only be enabled per call: remove "vercel_ai_gateway" from ' +
36
+ 'aiSdks (pass an empty list if no other sdks need to be instrumented). Instead, make sure ' +
37
+ 'experimental_telemetry: { isEnabled: true } is passed on each generateText/streamText call.'
38
+ );
39
+ }
40
+ registerTelemetry(new OpenTelemetry({ usage: true }));
41
+ }
42
+ }
43
+
44
+ module.exports = { VercelAiSdkInstrumentor };
@@ -6,7 +6,7 @@
6
6
  // This file has been modified from its original form:
7
7
  // - Translated from TypeScript to JavaScript
8
8
  // - Replaced Langfuse's fixed schema with Weflayr's structured metadata
9
- // (featureName, customerId, providerName, extraTags) plus an
9
+ // (featureName, customerName, providerName, extraTags) plus an
10
10
  // auto-generated per-call uuid
11
11
  // - Removed asBaggage cross-process propagation (not needed yet)
12
12
  // - Removed experiment framework keys
@@ -23,7 +23,7 @@ const { context: otelContext, trace, createContextKey } = require('@opentelemetr
23
23
  const WEFLAYR_METADATA_CONTEXT_KEY = createContextKey('weflayr_metadata');
24
24
 
25
25
  const FEATURE_NAME_ATTR = 'weflayr.feature_name';
26
- const CUSTOMER_ID_ATTR = 'weflayr.customer_id';
26
+ const CUSTOMER_NAME_ATTR = 'weflayr.customer_name';
27
27
  const PROVIDER_NAME_ATTR = 'weflayr.provider_name';
28
28
  const UUID_ATTR = 'weflayr.uuid';
29
29
  // Prefix for user-supplied `extraTags` so downstream normalizers can pick
@@ -36,10 +36,10 @@ function getMergedMetadataFromContext(parentContext) {
36
36
  return existing && typeof existing === 'object' ? { ...existing } : {};
37
37
  }
38
38
 
39
- function _buildAttributes({ featureName, customerId, providerName, extraTags, spanUuid }) {
39
+ function _buildAttributes({ featureName, customerName, providerName, extraTags, spanUuid }) {
40
40
  const attributes = {
41
41
  [FEATURE_NAME_ATTR]: featureName,
42
- [CUSTOMER_ID_ATTR]: customerId,
42
+ [CUSTOMER_NAME_ATTR]: customerName,
43
43
  [UUID_ATTR]: spanUuid,
44
44
  };
45
45
  for (const [key, value] of Object.entries(extraTags)) {
@@ -63,25 +63,34 @@ function _buildAttributes({ featureName, customerId, providerName, extraTags, sp
63
63
  */
64
64
  function propagateMetadata(options, fn) {
65
65
  const { featureName, customerId } = options ?? {};
66
- if (featureName == null || customerId == null) {
67
- throw new Error('propagateMetadata: `featureName` and `customerId` are required');
66
+ let { customerName } = options ?? {};
67
+ if (customerId != null) {
68
+ process.emitWarning(
69
+ 'propagateMetadata: `customerId` is deprecated, use `customerName`',
70
+ 'DeprecationWarning'
71
+ );
72
+ customerName = customerName ?? customerId;
68
73
  }
74
+ if (featureName == null || customerName == null) {
75
+ throw new Error('propagateMetadata: `featureName` and `customerName` are required');
76
+ }
77
+ const normalizedOptions = { ...options, customerName };
69
78
 
70
79
  if (typeof fn === 'function') {
71
- return _runWithMetadata(options, fn);
80
+ return _runWithMetadata(normalizedOptions, fn);
72
81
  }
73
82
 
74
83
  return function decorator(target, key, descriptor) {
75
84
  if (descriptor && typeof descriptor.value === 'function') {
76
85
  const original = descriptor.value;
77
86
  descriptor.value = function wrapped(...args) {
78
- return _runWithMetadata(options, () => original.apply(this, args));
87
+ return _runWithMetadata(normalizedOptions, () => original.apply(this, args));
79
88
  };
80
89
  return descriptor;
81
90
  }
82
91
  if (typeof target === 'function') {
83
92
  return function wrapped(...args) {
84
- return _runWithMetadata(options, () => target.apply(this, args));
93
+ return _runWithMetadata(normalizedOptions, () => target.apply(this, args));
85
94
  };
86
95
  }
87
96
  return target;
@@ -89,10 +98,10 @@ function propagateMetadata(options, fn) {
89
98
  }
90
99
 
91
100
  function _runWithMetadata(options, fn) {
92
- const { featureName, customerId, providerName, extraTags } = options;
101
+ const { featureName, customerName, providerName, extraTags } = options;
93
102
  const attributes = _buildAttributes({
94
103
  featureName,
95
- customerId,
104
+ customerName,
96
105
  providerName,
97
106
  extraTags: extraTags ?? {},
98
107
  spanUuid: randomUUID().replace(/-/g, ''),
@@ -117,7 +126,7 @@ module.exports = {
117
126
  getMergedMetadataFromContext,
118
127
  WEFLAYR_METADATA_CONTEXT_KEY,
119
128
  FEATURE_NAME_ATTR,
120
- CUSTOMER_ID_ATTR,
129
+ CUSTOMER_NAME_ATTR,
121
130
  PROVIDER_NAME_ATTR,
122
131
  UUID_ATTR,
123
132
  EXTRA_TAG_PREFIX,
@@ -20,6 +20,10 @@ const { SpanStatusCode } = require('@opentelemetry/api');
20
20
  const { BatchSpanProcessor, SimpleSpanProcessor } = require('@opentelemetry/sdk-trace-base');
21
21
  const { EXTRA_TAG_PREFIX, getMergedMetadataFromContext } = require('./propagation');
22
22
  const { isDefaultExportSpan, isKnownLLMInstrumentor } = require('./span-filter');
23
+ const {
24
+ VercelAiSdkSpanFilter,
25
+ stampVercelGatewayProviderName,
26
+ } = require('./vercel-ai-sdk-span-filter');
23
27
  const { debug } = require('../logger');
24
28
 
25
29
  // setup({captureMessageContent: ...}) records the choice in this env var (the
@@ -57,6 +61,7 @@ class WeflayrSpanProcessor {
57
61
  this._defaultTags = defaultTags;
58
62
  this._mask = mask;
59
63
  this._shouldExportSpan = shouldExportSpan ?? isDefaultExportSpan;
64
+ this._vercelAiSdkSpanFilter = new VercelAiSdkSpanFilter();
60
65
  // LLM spans we've started but not yet seen end. On forceFlush we end any
61
66
  // leftovers with ERROR status — covers cases like openllmetry's anthropic
62
67
  // streaming wrapper, which leaks the span when the underlying call
@@ -75,6 +80,7 @@ class WeflayrSpanProcessor {
75
80
  if (isKnownLLMInstrumentor(span)) {
76
81
  this._openLlmSpans.add(span);
77
82
  }
83
+ this._vercelAiSdkSpanFilter.onStart(span);
78
84
  debug(
79
85
  'span start: name=%s defaultTags=%d propagatedMetadata=%d',
80
86
  span.name,
@@ -87,7 +93,11 @@ class WeflayrSpanProcessor {
87
93
  onEnd(span) {
88
94
  this._openLlmSpans.delete(span);
89
95
  try {
90
- if (!this._shouldExportSpan(span)) {
96
+ const vercelVerdict = this._vercelAiSdkSpanFilter.shouldExport(span);
97
+ if (vercelVerdict === true) {
98
+ stampVercelGatewayProviderName(span);
99
+ }
100
+ if (vercelVerdict === false || (vercelVerdict === null && !this._shouldExportSpan(span))) {
91
101
  debug(
92
102
  'span end: filtered out name=%s scope=%s',
93
103
  span.name,
@@ -0,0 +1,79 @@
1
+ 'use strict';
2
+
3
+ // The Vercel AI SDK's OTel integrations emit aggregate spans around each model
4
+ // call, all repeating the same usage counts:
5
+ // - `@ai-sdk/otel` (ai >= 7, scope "gen_ai"): per-call "chat" spans are
6
+ // wrapped in "agent_step" + "invoke_agent" spans, and embed/embedMany
7
+ // emits an outer "embeddings" operation span around the model-call
8
+ // "embeddings" span(s).
9
+ // - the legacy integration (ai <= 6, scope "ai"): `ai.<fn>.do<Call>` spans
10
+ // are wrapped in an outer `ai.<fn>` span.
11
+ // Exactly one span per model call may reach the exporter — exporting an
12
+ // aggregate alongside its children would bill every call twice.
13
+
14
+ const VERCEL_SEMCONV_SCOPE = 'gen_ai';
15
+ const VERCEL_LEGACY_SCOPE = 'ai';
16
+
17
+ const LEGACY_MODEL_CALL_SPAN_NAME = /\.do(Generate|Stream|Embed)$/;
18
+
19
+ function _parentSpanId(span) {
20
+ return span.parentSpanContext?.spanId ?? span.parentSpanId;
21
+ }
22
+
23
+ class VercelAiSdkSpanFilter {
24
+ constructor() {
25
+ // spanId -> gen_ai.operation.name for open "gen_ai"-scope spans, so a
26
+ // span can check what its parent is (children end before their parent).
27
+ this._openSpanOperations = new Map();
28
+ }
29
+
30
+ onStart(span) {
31
+ if (span.instrumentationScope?.name !== VERCEL_SEMCONV_SCOPE) return;
32
+ this._openSpanOperations.set(
33
+ span.spanContext().spanId,
34
+ span.attributes['gen_ai.operation.name']
35
+ );
36
+ }
37
+
38
+ /**
39
+ * Export verdict for a Vercel AI SDK span, or null when the span is not
40
+ * one (the caller falls back to the default filter).
41
+ */
42
+ shouldExport(span) {
43
+ const scope = span.instrumentationScope?.name;
44
+ if (scope === VERCEL_LEGACY_SCOPE) {
45
+ return LEGACY_MODEL_CALL_SPAN_NAME.test(span.name);
46
+ }
47
+ if (scope !== VERCEL_SEMCONV_SCOPE) {
48
+ return null;
49
+ }
50
+ const operation = span.attributes['gen_ai.operation.name'];
51
+ this._openSpanOperations.delete(span.spanContext().spanId);
52
+ if (operation === 'chat') {
53
+ return true;
54
+ }
55
+ if (operation === 'embeddings') {
56
+ // Keep the outer aggregate (one event per embed/embedMany call, with the
57
+ // total usage) and drop the nested model-call span(s) repeating it.
58
+ return this._openSpanOperations.get(_parentSpanId(span)) !== 'embeddings';
59
+ }
60
+ // invoke_agent / agent_step (usage aggregates) and execute_tool (no usage).
61
+ return false;
62
+ }
63
+ }
64
+
65
+ // `@ai-sdk/otel` reports the Vercel AI Gateway as the bare provider id
66
+ // "gateway" — too generic a name to special-case downstream. Stamp the weflayr
67
+ // provider override (which the normalizer prefers) with the canonical name.
68
+ // A providerName set through propagateMetadata was stamped at span start and
69
+ // wins.
70
+ function stampVercelGatewayProviderName(span) {
71
+ if (
72
+ span.attributes['gen_ai.provider.name'] === 'gateway' &&
73
+ span.attributes['weflayr.provider_name'] === undefined
74
+ ) {
75
+ span.attributes['weflayr.provider_name'] = 'vercel_ai_gateway';
76
+ }
77
+ }
78
+
79
+ module.exports = { VercelAiSdkSpanFilter, stampVercelGatewayProviderName };