weflayr 0.26.1 → 0.28.0

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 (100) hide show
  1. package/dist/package.json +8 -5
  2. package/dist/src/auto-instrument.d.ts +4 -3
  3. package/dist/src/auto-instrument.js +18 -17
  4. package/dist/src/get-data-uri-mime-type.d.ts +17 -0
  5. package/dist/src/get-data-uri-mime-type.js +25 -0
  6. package/dist/src/index.d.ts +3 -2
  7. package/dist/src/index.js +5 -1
  8. package/dist/src/instrumentation/anthropic/get_streaming_accumulation_telemetry.d.ts +3 -1
  9. package/dist/src/instrumentation/anthropic/get_streaming_accumulation_telemetry.js +6 -3
  10. package/dist/src/instrumentation/anthropic/get_vertex_billing_telemetry.d.ts +32 -0
  11. package/dist/src/instrumentation/anthropic/get_vertex_billing_telemetry.js +100 -0
  12. package/dist/src/instrumentation/anthropic/get_web_search_telemetry.d.ts +43 -0
  13. package/dist/src/instrumentation/anthropic/get_web_search_telemetry.js +147 -0
  14. package/dist/src/instrumentation/anthropic/set_telemetry_attributes.d.ts +5 -2
  15. package/dist/src/instrumentation/anthropic/set_telemetry_attributes.js +17 -4
  16. package/dist/src/instrumentation/bedrock/get_cache_config_telemetry.d.ts +13 -1
  17. package/dist/src/instrumentation/bedrock/get_cache_config_telemetry.js +56 -1
  18. package/dist/src/instrumentation/bedrock/get_cache_telemetry.js +8 -5
  19. package/dist/src/instrumentation/bedrock/get_input_message_content_telemetry.js +33 -5
  20. package/dist/src/instrumentation/bedrock/get_model_id_telemetry.d.ts +1 -1
  21. package/dist/src/instrumentation/bedrock/get_model_id_telemetry.js +1 -1
  22. package/dist/src/instrumentation/bedrock/get_openai_chat_format_telemetry.d.ts +45 -0
  23. package/dist/src/instrumentation/bedrock/get_openai_chat_format_telemetry.js +251 -0
  24. package/dist/src/instrumentation/bedrock/get_streaming_output_message_telemetry.d.ts +6 -5
  25. package/dist/src/instrumentation/bedrock/get_streaming_output_message_telemetry.js +15 -12
  26. package/dist/src/instrumentation/bedrock/get_tool_definitions_telemetry.d.ts +2 -2
  27. package/dist/src/instrumentation/bedrock/get_tool_definitions_telemetry.js +12 -4
  28. package/dist/src/instrumentation/bedrock/set_telemetry_attributes.d.ts +4 -2
  29. package/dist/src/instrumentation/bedrock/set_telemetry_attributes.js +10 -3
  30. package/dist/src/instrumentation/cohere/get_embeddings_telemetry.js +4 -6
  31. package/dist/src/instrumentation/cohere/get_input_message_content_telemetry.d.ts +23 -0
  32. package/dist/src/instrumentation/cohere/get_input_message_content_telemetry.js +105 -0
  33. package/dist/src/instrumentation/cohere/get_v2_chat_telemetry.d.ts +18 -0
  34. package/dist/src/instrumentation/cohere/get_v2_chat_telemetry.js +186 -0
  35. package/dist/src/instrumentation/cohere/set_telemetry_attributes.d.ts +3 -1
  36. package/dist/src/instrumentation/cohere/set_telemetry_attributes.js +5 -1
  37. package/dist/src/instrumentation/google/get_embeddings_telemetry.d.ts +8 -4
  38. package/dist/src/instrumentation/google/get_embeddings_telemetry.js +68 -35
  39. package/dist/src/instrumentation/google/get_provider_name.d.ts +2 -1
  40. package/dist/src/instrumentation/google/get_provider_name.js +2 -1
  41. package/dist/src/instrumentation/google/get_web_search_telemetry.d.ts +38 -0
  42. package/dist/src/instrumentation/google/get_web_search_telemetry.js +125 -0
  43. package/dist/src/instrumentation/google/set_telemetry_attributes.d.ts +2 -2
  44. package/dist/src/instrumentation/google/set_telemetry_attributes.js +6 -2
  45. package/dist/src/instrumentation/mistral/full_instrumentation.d.ts +60 -0
  46. package/dist/src/instrumentation/mistral/full_instrumentation.js +364 -0
  47. package/dist/src/instrumentation/mistral/get_input_message_content_telemetry.d.ts +35 -0
  48. package/dist/src/instrumentation/mistral/get_input_message_content_telemetry.js +113 -0
  49. package/dist/src/instrumentation/mistral/get_structured_output_schema_telemetry.d.ts +13 -0
  50. package/dist/src/instrumentation/mistral/get_structured_output_schema_telemetry.js +24 -0
  51. package/dist/src/instrumentation/mistral/get_vertex_telemetry.d.ts +54 -0
  52. package/dist/src/instrumentation/mistral/get_vertex_telemetry.js +153 -0
  53. package/dist/src/instrumentation/openai/get_bedrock_mantle_telemetry.d.ts +10 -0
  54. package/dist/src/instrumentation/openai/get_bedrock_mantle_telemetry.js +33 -0
  55. package/dist/src/instrumentation/openai/get_cache_and_reasoning_telemetry.d.ts +13 -6
  56. package/dist/src/instrumentation/openai/get_cache_and_reasoning_telemetry.js +17 -7
  57. package/dist/src/instrumentation/openai/get_cache_config_telemetry.d.ts +21 -4
  58. package/dist/src/instrumentation/openai/get_cache_config_telemetry.js +50 -11
  59. package/dist/src/instrumentation/openai/get_embeddings_telemetry.js +14 -6
  60. package/dist/src/instrumentation/openai/get_file_part_mime_type_telemetry.d.ts +26 -0
  61. package/dist/src/instrumentation/openai/get_file_part_mime_type_telemetry.js +100 -0
  62. package/dist/src/instrumentation/openai/get_openai_compatible_provider_telemetry.d.ts +48 -0
  63. package/dist/src/instrumentation/openai/get_openai_compatible_provider_telemetry.js +123 -0
  64. package/dist/src/instrumentation/openai/get_reasoning_content_telemetry.d.ts +28 -0
  65. package/dist/src/instrumentation/openai/get_reasoning_content_telemetry.js +130 -0
  66. package/dist/src/instrumentation/openai/get_responses_telemetry.d.ts +89 -0
  67. package/dist/src/instrumentation/openai/get_responses_telemetry.js +587 -0
  68. package/dist/src/instrumentation/openai/get_service_tier_telemetry.d.ts +20 -0
  69. package/dist/src/instrumentation/openai/get_service_tier_telemetry.js +84 -0
  70. package/dist/src/instrumentation/openai/get_structured_output_schema_telemetry.d.ts +7 -5
  71. package/dist/src/instrumentation/openai/get_structured_output_schema_telemetry.js +15 -8
  72. package/dist/src/instrumentation/openai/get_vertex_openai_compatible_telemetry.d.ts +23 -0
  73. package/dist/src/instrumentation/openai/get_vertex_openai_compatible_telemetry.js +69 -0
  74. package/dist/src/instrumentation/openai/get_web_search_telemetry.d.ts +74 -0
  75. package/dist/src/instrumentation/openai/get_web_search_telemetry.js +259 -0
  76. package/dist/src/instrumentation/openai/set_telemetry_attributes.d.ts +13 -1
  77. package/dist/src/instrumentation/openai/set_telemetry_attributes.js +33 -2
  78. package/dist/src/instrumentation/shared/get_structured_output_schema_telemetry.d.ts +1 -1
  79. package/dist/src/instrumentation/shared/get_structured_output_schema_telemetry.js +1 -1
  80. package/dist/src/instrumentation/shared/is_vertex_base_url.d.ts +17 -0
  81. package/dist/src/instrumentation/shared/is_vertex_base_url.js +40 -0
  82. package/dist/src/otel/propagation.d.ts +37 -10
  83. package/dist/src/otel/propagation.js +62 -22
  84. package/dist/src/otel/span-filter.d.ts +2 -0
  85. package/dist/src/otel/span-filter.js +10 -1
  86. package/dist/src/otel/span-processor.d.ts +1 -2
  87. package/dist/src/otel/span-processor.js +17 -20
  88. package/dist/src/otel/strip-media-payloads.d.ts +26 -0
  89. package/dist/src/otel/strip-media-payloads.js +135 -0
  90. package/dist/src/propagate-metadata-to-gateway.d.ts +37 -0
  91. package/dist/src/propagate-metadata-to-gateway.js +213 -0
  92. package/package.json +8 -5
  93. package/dist/src/instrumentation/vercel/full_instrumentation.d.ts +0 -3
  94. package/dist/src/instrumentation/vercel/full_instrumentation.js +0 -54
  95. package/dist/src/instrumentation/vercel/get_cache_creation_telemetry.d.ts +0 -22
  96. package/dist/src/instrumentation/vercel/get_cache_creation_telemetry.js +0 -66
  97. package/dist/src/instrumentation/vercel/get_structured_output_schema_telemetry.d.ts +0 -24
  98. package/dist/src/instrumentation/vercel/get_structured_output_schema_telemetry.js +0 -84
  99. package/dist/src/otel/vercel-ai-sdk-span-filter.d.ts +0 -12
  100. package/dist/src/otel/vercel-ai-sdk-span-filter.js +0 -88
@@ -8,7 +8,19 @@
8
8
  * non-streamed cached-prompt tokens (these enrich the traceloop instrumentor
9
9
  * instance, hence the `instrumentation` argument), and
10
10
  * - the request's structured-output schema, and
11
- * - Bedrock Mantle's provider attribution and reasoning output.
11
+ * - the Responses endpoint, which it wraps but records far less of than the
12
+ * Python instrumentor does (and not at all when streamed), and
13
+ * - Bedrock Mantle's provider attribution and reasoning output, and
14
+ * - the provider-run web search (`web_search_options` and the answer's
15
+ * `url_citation` annotations), and
16
+ * - the mime type of an inline file (PDF, ...), which it reads from a
17
+ * field OpenAI's API does not have and so always records as
18
+ * application/octet-stream, and
19
+ * - the provider attribution of the vendors served over an
20
+ * OpenAI-compatible API (Alibaba Model Studio, xAI), and
21
+ * - the billing of the calls to Vertex AI's OpenAI-compatible endpoint, and
22
+ * - a `reasoning_content` output part for any provider that returns one, and
23
+ * - the service tier the call was served in (flex, priority, ...).
12
24
  */
13
25
  Object.defineProperty(exports, "__esModule", { value: true });
14
26
  exports.OpenAITelemetryInstrumentor = void 0;
@@ -16,9 +28,16 @@ const get_structured_output_schema_telemetry_1 = require("../shared/get_structur
16
28
  const get_audio_telemetry_1 = require("./get_audio_telemetry");
17
29
  const get_bedrock_mantle_telemetry_1 = require("./get_bedrock_mantle_telemetry");
18
30
  const get_cache_config_telemetry_1 = require("./get_cache_config_telemetry");
31
+ const get_file_part_mime_type_telemetry_1 = require("./get_file_part_mime_type_telemetry");
32
+ const get_openai_compatible_provider_telemetry_1 = require("./get_openai_compatible_provider_telemetry");
33
+ const get_responses_telemetry_1 = require("./get_responses_telemetry");
19
34
  const get_embeddings_telemetry_1 = require("./get_embeddings_telemetry");
35
+ const get_reasoning_content_telemetry_1 = require("./get_reasoning_content_telemetry");
36
+ const get_service_tier_telemetry_1 = require("./get_service_tier_telemetry");
20
37
  const get_streaming_usage_1 = require("./get_streaming_usage");
21
38
  const get_cache_and_reasoning_telemetry_1 = require("./get_cache_and_reasoning_telemetry");
39
+ const get_vertex_openai_compatible_telemetry_1 = require("./get_vertex_openai_compatible_telemetry");
40
+ const get_web_search_telemetry_1 = require("./get_web_search_telemetry");
22
41
  const get_structured_output_schema_telemetry_2 = require("./get_structured_output_schema_telemetry");
23
42
  class OpenAITelemetryInstrumentor {
24
43
  _subInstrumentors;
@@ -29,7 +48,15 @@ class OpenAITelemetryInstrumentor {
29
48
  new get_streaming_usage_1.OpenAIStreamingUsageInstrumentor(),
30
49
  new get_cache_and_reasoning_telemetry_1.OpenAIPromptCachingInstrumentor(),
31
50
  new get_cache_config_telemetry_1.OpenAICacheConfigInstrumentor(),
51
+ new get_responses_telemetry_1.OpenAIResponsesInstrumentor(),
52
+ new get_file_part_mime_type_telemetry_1.OpenAIFilePartMimeTypeInstrumentor(),
32
53
  new get_bedrock_mantle_telemetry_1.OpenAIBedrockMantleInstrumentor(),
54
+ new get_web_search_telemetry_1.OpenAIWebSearchInstrumentor(),
55
+ new get_openai_compatible_provider_telemetry_1.OpenAICompatibleProviderInstrumentor(get_openai_compatible_provider_telemetry_1.ALIBABA_PROVIDER),
56
+ new get_openai_compatible_provider_telemetry_1.OpenAICompatibleProviderInstrumentor(get_openai_compatible_provider_telemetry_1.SPACEXAI_PROVIDER),
57
+ new get_vertex_openai_compatible_telemetry_1.VertexOpenAICompatibleInstrumentor(),
58
+ new get_reasoning_content_telemetry_1.OpenAIReasoningContentInstrumentor(),
59
+ new get_service_tier_telemetry_1.OpenAIServiceTierInstrumentor(),
33
60
  new get_structured_output_schema_telemetry_1.TraceloopStructuredOutputSchemaInstrumentor(get_structured_output_schema_telemetry_2.readStructuredOutputSchema),
34
61
  ];
35
62
  }
@@ -39,8 +66,12 @@ class OpenAITelemetryInstrumentor {
39
66
  for (const sub of this._subInstrumentors)
40
67
  sub.instrument(instrumentation);
41
68
  }
69
+ // Reversed: several complements patch the same instrumentor methods
70
+ // (`startSpan`, `_endSpan`), so the patches have to be unwound
71
+ // last-patched-first or an earlier wrapper is written back over a later one
72
+ // and stays installed.
42
73
  uninstrument() {
43
- for (const sub of this._subInstrumentors)
74
+ for (const sub of [...this._subInstrumentors].reverse())
44
75
  sub.uninstrument();
45
76
  }
46
77
  }
@@ -4,7 +4,7 @@
4
4
  * Each provider asks for structured output under its own request field (OpenAI
5
5
  * `response_format`, Anthropic `output_config.format`, Cohere `response_format`,
6
6
  * Gemini `responseJsonSchema`), but they all serialize the schema into the
7
- * request the same way - and OpenAI, Anthropic and Vercel bill it as input - so
7
+ * request the same way - and OpenAI and Anthropic bill it as input - so
8
8
  * the complements stamp one provider-agnostic attribute and the worker stores it
9
9
  * in `input_structured_output_schema`.
10
10
  *
@@ -5,7 +5,7 @@
5
5
  * Each provider asks for structured output under its own request field (OpenAI
6
6
  * `response_format`, Anthropic `output_config.format`, Cohere `response_format`,
7
7
  * Gemini `responseJsonSchema`), but they all serialize the schema into the
8
- * request the same way - and OpenAI, Anthropic and Vercel bill it as input - so
8
+ * request the same way - and OpenAI and Anthropic bill it as input - so
9
9
  * the complements stamp one provider-agnostic attribute and the worker stores it
10
10
  * in `input_structured_output_schema`.
11
11
  *
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Whether a client calls Google Cloud's Vertex AI.
3
+ *
4
+ * Vertex serves partner models through the providers' own clients pointed at a
5
+ * Vertex AI host: Claude through Anthropic's `AnthropicVertex`, and Grok,
6
+ * DeepSeek, Llama and the other partner models through the openai client
7
+ * pointed at Vertex's OpenAI-compatible endpoint. Those calls are billed by
8
+ * Google at Vertex's prices, so each complement that sees such a client stamps
9
+ * `weflayr.billing_provider` with `vertex`.
10
+ */
11
+ export declare const VERTEX_BILLING_PROVIDER = "vertex";
12
+ export declare const BILLING_PROVIDER_ATTR = "weflayr.billing_provider";
13
+ /** Whether a client base URL is a Vertex AI host, e.g.
14
+ * 'https://us-east5-aiplatform.googleapis.com/v1' -> true. Never a bare
15
+ * substring match, so 'https://aiplatform.googleapis.com.evil.example' is not
16
+ * one. */
17
+ export declare function isVertexBaseURL(baseURL: unknown): boolean;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ /**
3
+ * Whether a client calls Google Cloud's Vertex AI.
4
+ *
5
+ * Vertex serves partner models through the providers' own clients pointed at a
6
+ * Vertex AI host: Claude through Anthropic's `AnthropicVertex`, and Grok,
7
+ * DeepSeek, Llama and the other partner models through the openai client
8
+ * pointed at Vertex's OpenAI-compatible endpoint. Those calls are billed by
9
+ * Google at Vertex's prices, so each complement that sees such a client stamps
10
+ * `weflayr.billing_provider` with `vertex`.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.BILLING_PROVIDER_ATTR = exports.VERTEX_BILLING_PROVIDER = void 0;
14
+ exports.isVertexBaseURL = isVertexBaseURL;
15
+ exports.VERTEX_BILLING_PROVIDER = 'vertex';
16
+ exports.BILLING_PROVIDER_ATTR = 'weflayr.billing_provider';
17
+ // The global host and the US and EU multi-region hosts. A single Google region
18
+ // is served on its own host, "<region>-aiplatform.googleapis.com".
19
+ const VERTEX_HOSTNAMES = new Set([
20
+ 'aiplatform.googleapis.com',
21
+ 'aiplatform.us.rep.googleapis.com',
22
+ 'aiplatform.eu.rep.googleapis.com',
23
+ ]);
24
+ const VERTEX_REGIONAL_HOSTNAME_SUFFIX = '-aiplatform.googleapis.com';
25
+ /** Whether a client base URL is a Vertex AI host, e.g.
26
+ * 'https://us-east5-aiplatform.googleapis.com/v1' -> true. Never a bare
27
+ * substring match, so 'https://aiplatform.googleapis.com.evil.example' is not
28
+ * one. */
29
+ function isVertexBaseURL(baseURL) {
30
+ if (typeof baseURL !== 'string' || !baseURL)
31
+ return false;
32
+ let hostname;
33
+ try {
34
+ hostname = new URL(baseURL).hostname.toLowerCase();
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ return VERTEX_HOSTNAMES.has(hostname) || hostname.endsWith(VERTEX_REGIONAL_HOSTNAME_SUFFIX);
40
+ }
@@ -3,25 +3,21 @@ import type { AIProviderName } from '../auto-instrument';
3
3
  declare const WEFLAYR_METADATA_CONTEXT_KEY: symbol;
4
4
  declare const FEATURE_NAME_ATTR = "weflayr.feature_name";
5
5
  declare const CUSTOMER_NAME_ATTR = "weflayr.customer_name";
6
- declare const PROVIDER_NAME_ATTR = "weflayr.provider_name";
6
+ declare const CALL_NAME_ATTR = "weflayr.call_name";
7
+ declare const BILLING_PROVIDER_ATTR = "weflayr.billing_provider";
7
8
  declare const UUID_ATTR = "weflayr.uuid";
8
9
  declare const EXTRA_TAG_PREFIX = "weflayr.tag.";
9
10
  export interface PropagateMetadataOptions {
10
11
  /** Product-side feature triggering the LLM call (e.g. `"onboarding-summary"`). */
11
12
  featureName: string;
12
13
  /** Name of the end-user / tenant customer the call is made for. */
13
- customerName?: string;
14
- /** @deprecated Use `customerName` instead. */
15
- customerId?: string;
14
+ customerName: string;
16
15
  /**
17
16
  * Override for the provider seen on the wire. Set this only if it differs
18
- * from the SDK auto-detected by OpenTelemetry — e.g. calls routed through
19
- * `vercel_ai_gateway` that proxies to OpenAI/Anthropic but should be
20
- * reported as the gateway.
17
+ * from the SDK auto-detected by OpenTelemetry, e.g. Mistral calls made
18
+ * through the OpenAI SDK, which should be reported as `mistral`.
21
19
  */
22
20
  providerNameOverride?: AIProviderName;
23
- /** @deprecated Use `providerNameOverride` instead. */
24
- providerName?: AIProviderName;
25
21
  /**
26
22
  * Arbitrary scalar key/value pairs to attach. Values must be `string`,
27
23
  * `number`, or `boolean` so they survive the ClickHouse `Map(String,
@@ -29,8 +25,24 @@ export interface PropagateMetadataOptions {
29
25
  * here — they have dedicated fields.
30
26
  */
31
27
  extraTags?: Record<string, string | number | boolean>;
28
+ /**
29
+ * The uuid to stamp on every span inside the scope, e.g. one
30
+ * `createFeatureRunUuid` made where the run started, so the calls of one run
31
+ * made in several processes, or in several scopes, share it: 32 lowercase
32
+ * hexadecimal characters. When omitted, each invocation creates its own.
33
+ */
34
+ featureRunUuid?: string;
32
35
  }
33
36
  export declare function getMergedMetadataFromContext(parentContext?: Context): Record<string, unknown>;
37
+ /**
38
+ * A new feature run uuid, the kind `propagateMetadata` creates for each scope,
39
+ * e.g. `"3f2b9c1e0d4a4b6f8e7d5c4b3a291807"`.
40
+ *
41
+ * Create one where a run starts and pass it as `featureRunUuid` to every
42
+ * `propagateMetadata` scope of the run, including the ones opened in other
43
+ * processes, so all its calls are recorded under one feature run.
44
+ */
45
+ export declare function createFeatureRunUuid(): string;
34
46
  /**
35
47
  * Attach Weflayr metadata to every LLM span created inside this scope.
36
48
  *
@@ -43,4 +55,19 @@ export declare function getMergedMetadataFromContext(parentContext?: Context): R
43
55
  */
44
56
  export declare function propagateMetadata<T>(options: PropagateMetadataOptions, fn: () => Promise<T> | T): Promise<T> | T;
45
57
  export declare function propagateMetadata(options: PropagateMetadataOptions): (target: unknown, key?: string, descriptor?: PropertyDescriptor) => unknown;
46
- export { WEFLAYR_METADATA_CONTEXT_KEY, FEATURE_NAME_ATTR, CUSTOMER_NAME_ATTR, PROVIDER_NAME_ATTR, UUID_ATTR, EXTRA_TAG_PREFIX, };
58
+ /**
59
+ * Name the LLM call made inside this scope, e.g. `"summarize_profile"`.
60
+ *
61
+ * Callable form: `setCallName(callName, fn)` runs `fn` immediately. Decorator
62
+ * form: `setCallName(callName)` returns a wrapper usable as a function-decorator
63
+ * (`@setCallName(...)` on class methods) or as a plain `wrap(fn)` -> wrappedFn
64
+ * helper.
65
+ *
66
+ * Where `featureName` names the product feature a call belongs to, `callName`
67
+ * names one call within it. The fields of an enclosing `propagateMetadata`
68
+ * scope are kept, and no new `uuid` is minted, so calls under different names
69
+ * still share their feature's uuid.
70
+ */
71
+ export declare function setCallName<T>(callName: string, fn: () => Promise<T> | T): Promise<T> | T;
72
+ export declare function setCallName(callName: string): (target: unknown, key?: string, descriptor?: PropertyDescriptor) => unknown;
73
+ export { WEFLAYR_METADATA_CONTEXT_KEY, FEATURE_NAME_ATTR, CUSTOMER_NAME_ATTR, CALL_NAME_ATTR, BILLING_PROVIDER_ATTR, UUID_ATTR, EXTRA_TAG_PREFIX, };
@@ -9,6 +9,7 @@
9
9
  // - Replaced Langfuse's fixed schema with Weflayr's structured metadata
10
10
  // (featureName, customerName, providerNameOverride, extraTags) plus an
11
11
  // auto-generated per-call uuid
12
+ // - Added `setCallName`, a nested scope naming one LLM call
12
13
  // - Removed asBaggage cross-process propagation (not needed yet)
13
14
  // - Removed experiment framework keys
14
15
  // - Structured fields are written under stable `weflayr.*` span attribute
@@ -16,9 +17,11 @@
16
17
  //
17
18
  // See LICENSE-MIT in the package root for the full license text.
18
19
  Object.defineProperty(exports, "__esModule", { value: true });
19
- exports.EXTRA_TAG_PREFIX = exports.UUID_ATTR = exports.PROVIDER_NAME_ATTR = exports.CUSTOMER_NAME_ATTR = exports.FEATURE_NAME_ATTR = exports.WEFLAYR_METADATA_CONTEXT_KEY = void 0;
20
+ exports.EXTRA_TAG_PREFIX = exports.UUID_ATTR = exports.BILLING_PROVIDER_ATTR = exports.CALL_NAME_ATTR = exports.CUSTOMER_NAME_ATTR = exports.FEATURE_NAME_ATTR = exports.WEFLAYR_METADATA_CONTEXT_KEY = void 0;
20
21
  exports.getMergedMetadataFromContext = getMergedMetadataFromContext;
22
+ exports.createFeatureRunUuid = createFeatureRunUuid;
21
23
  exports.propagateMetadata = propagateMetadata;
24
+ exports.setCallName = setCallName;
22
25
  const node_crypto_1 = require("node:crypto");
23
26
  const api_1 = require("@opentelemetry/api");
24
27
  const WEFLAYR_METADATA_CONTEXT_KEY = (0, api_1.createContextKey)('weflayr_metadata');
@@ -27,8 +30,10 @@ const FEATURE_NAME_ATTR = 'weflayr.feature_name';
27
30
  exports.FEATURE_NAME_ATTR = FEATURE_NAME_ATTR;
28
31
  const CUSTOMER_NAME_ATTR = 'weflayr.customer_name';
29
32
  exports.CUSTOMER_NAME_ATTR = CUSTOMER_NAME_ATTR;
30
- const PROVIDER_NAME_ATTR = 'weflayr.provider_name';
31
- exports.PROVIDER_NAME_ATTR = PROVIDER_NAME_ATTR;
33
+ const CALL_NAME_ATTR = 'weflayr.call_name';
34
+ exports.CALL_NAME_ATTR = CALL_NAME_ATTR;
35
+ const BILLING_PROVIDER_ATTR = 'weflayr.billing_provider';
36
+ exports.BILLING_PROVIDER_ATTR = BILLING_PROVIDER_ATTR;
32
37
  const UUID_ATTR = 'weflayr.uuid';
33
38
  exports.UUID_ATTR = UUID_ATTR;
34
39
  // Prefix for user-supplied `extraTags` so downstream normalizers can pick
@@ -50,53 +55,64 @@ function _buildAttributes({ featureName, customerName, providerNameOverride, ext
50
55
  attributes[`${EXTRA_TAG_PREFIX}${key}`] = value;
51
56
  }
52
57
  if (providerNameOverride !== undefined && providerNameOverride !== null) {
53
- attributes[PROVIDER_NAME_ATTR] = providerNameOverride;
58
+ attributes[BILLING_PROVIDER_ATTR] = providerNameOverride;
54
59
  }
55
60
  return attributes;
56
61
  }
62
+ // The shape of the uuid createFeatureRunUuid returns, which a uuid passed to
63
+ // propagateMetadata must have too, so every feature run is named the same way.
64
+ const FEATURE_RUN_UUID_PATTERN = /^[0-9a-f]{32}$/;
65
+ /**
66
+ * A new feature run uuid, the kind `propagateMetadata` creates for each scope,
67
+ * e.g. `"3f2b9c1e0d4a4b6f8e7d5c4b3a291807"`.
68
+ *
69
+ * Create one where a run starts and pass it as `featureRunUuid` to every
70
+ * `propagateMetadata` scope of the run, including the ones opened in other
71
+ * processes, so all its calls are recorded under one feature run.
72
+ */
73
+ function createFeatureRunUuid() {
74
+ return (0, node_crypto_1.randomUUID)().replace(/-/g, '');
75
+ }
57
76
  function propagateMetadata(options, fn) {
58
- const { featureName, customerId, providerName } = options ?? {};
59
- let { customerName, providerNameOverride } = options ?? {};
60
- if (customerId != null) {
61
- process.emitWarning('propagateMetadata: `customerId` is deprecated, use `customerName`', 'DeprecationWarning');
62
- customerName = customerName ?? customerId;
63
- }
64
- if (providerName != null) {
65
- process.emitWarning('propagateMetadata: `providerName` is deprecated, use `providerNameOverride`', 'DeprecationWarning');
66
- providerNameOverride = providerNameOverride ?? providerName;
67
- }
77
+ const { featureName, customerName } = options ?? {};
68
78
  if (featureName == null || customerName == null) {
69
79
  throw new Error('propagateMetadata: `featureName` and `customerName` are required');
70
80
  }
71
- const normalizedOptions = { ...options, customerName, providerNameOverride };
81
+ if (options.featureRunUuid !== undefined &&
82
+ !FEATURE_RUN_UUID_PATTERN.test(options.featureRunUuid)) {
83
+ throw new Error('propagateMetadata: `featureRunUuid` must be 32 lowercase hexadecimal characters, ' +
84
+ `as createFeatureRunUuid() returns, not "${options.featureRunUuid}"`);
85
+ }
72
86
  if (typeof fn === 'function') {
73
- return _runWithMetadata(normalizedOptions, fn);
87
+ return _runWithMetadata(options, fn);
74
88
  }
75
89
  return function decorator(target, _key, descriptor) {
76
90
  if (descriptor && typeof descriptor.value === 'function') {
77
91
  const original = descriptor.value;
78
92
  descriptor.value = function wrapped(...args) {
79
- return _runWithMetadata(normalizedOptions, () => original.apply(this, args));
93
+ return _runWithMetadata(options, () => original.apply(this, args));
80
94
  };
81
95
  return descriptor;
82
96
  }
83
97
  if (typeof target === 'function') {
84
98
  return function wrapped(...args) {
85
- return _runWithMetadata(normalizedOptions, () => target.apply(this, args));
99
+ return _runWithMetadata(options, () => target.apply(this, args));
86
100
  };
87
101
  }
88
102
  return target;
89
103
  };
90
104
  }
91
105
  function _runWithMetadata(options, fn) {
92
- const { featureName, customerName, providerNameOverride, extraTags } = options;
93
- const attributes = _buildAttributes({
106
+ const { featureName, customerName, providerNameOverride, extraTags, featureRunUuid } = options;
107
+ return _runWithAttributes(_buildAttributes({
94
108
  featureName,
95
109
  customerName,
96
110
  providerNameOverride,
97
111
  extraTags: extraTags ?? {},
98
- spanUuid: (0, node_crypto_1.randomUUID)().replace(/-/g, ''),
99
- });
112
+ spanUuid: featureRunUuid ?? createFeatureRunUuid(),
113
+ }), fn);
114
+ }
115
+ function _runWithAttributes(attributes, fn) {
100
116
  const parent = api_1.context.active();
101
117
  const merged = { ...getMergedMetadataFromContext(parent), ...attributes };
102
118
  const newContext = parent.setValue(WEFLAYR_METADATA_CONTEXT_KEY, merged);
@@ -108,3 +124,27 @@ function _runWithMetadata(options, fn) {
108
124
  }
109
125
  return api_1.context.with(newContext, fn);
110
126
  }
127
+ function setCallName(callName, fn) {
128
+ if (!callName) {
129
+ throw new Error('setCallName: `callName` must not be empty');
130
+ }
131
+ const attributes = { [CALL_NAME_ATTR]: callName };
132
+ if (typeof fn === 'function') {
133
+ return _runWithAttributes(attributes, fn);
134
+ }
135
+ return function decorator(target, _key, descriptor) {
136
+ if (descriptor && typeof descriptor.value === 'function') {
137
+ const original = descriptor.value;
138
+ descriptor.value = function wrapped(...args) {
139
+ return _runWithAttributes(attributes, () => original.apply(this, args));
140
+ };
141
+ return descriptor;
142
+ }
143
+ if (typeof target === 'function') {
144
+ return function wrapped(...args) {
145
+ return _runWithAttributes(attributes, () => target.apply(this, args));
146
+ };
147
+ }
148
+ return target;
149
+ };
150
+ }
@@ -1,3 +1,5 @@
1
1
  import type { ReadableSpan } from '@opentelemetry/sdk-trace-base';
2
+ export declare const SENT_TO_WEFLAYR_GATEWAY_ATTR = "weflayr.sent_to_weflayr_gateway";
2
3
  export declare function isKnownLLMInstrumentor(span: ReadableSpan): boolean;
4
+ export declare function wasSentToWeflayrGateway(span: ReadableSpan): boolean;
3
5
  export declare function isDefaultExportSpan(span: ReadableSpan): boolean;
@@ -15,8 +15,14 @@
15
15
  //
16
16
  // See LICENSE-MIT in the package root for the full license text.
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.SENT_TO_WEFLAYR_GATEWAY_ATTR = void 0;
18
19
  exports.isKnownLLMInstrumentor = isKnownLLMInstrumentor;
20
+ exports.wasSentToWeflayrGateway = wasSentToWeflayrGateway;
19
21
  exports.isDefaultExportSpan = isDefaultExportSpan;
22
+ // Stamped by `enablePropagateMetadataToGateway` on the span of a call sent to
23
+ // the Weflayr AI gateway. The gateway sends that call's telemetry itself, so
24
+ // exporting the span as well would store the same call twice.
25
+ exports.SENT_TO_WEFLAYR_GATEWAY_ATTR = 'weflayr.sent_to_weflayr_gateway';
20
26
  const KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES = [
21
27
  'agent_framework',
22
28
  'ai',
@@ -56,6 +62,9 @@ function isKnownLLMInstrumentor(span) {
56
62
  const scope = span.instrumentationScope.name;
57
63
  return KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES.some((prefix) => scope === prefix || scope.startsWith(`${prefix}.`));
58
64
  }
65
+ function wasSentToWeflayrGateway(span) {
66
+ return Boolean(span.attributes[exports.SENT_TO_WEFLAYR_GATEWAY_ATTR]);
67
+ }
59
68
  function isDefaultExportSpan(span) {
60
- return isKnownLLMInstrumentor(span);
69
+ return isKnownLLMInstrumentor(span) && !wasSentToWeflayrGateway(span);
61
70
  }
@@ -6,7 +6,7 @@ export type Tags = Record<string, string | number | boolean>;
6
6
  /**
7
7
  * Span attributes the mask callback can read or scrub. The OpenTelemetry GenAI
8
8
  * semantic-convention keys get autocomplete; the index signature catches user
9
- * tags and provider-specific attributes (e.g. Vercel AI's `ai.*`).
9
+ * tags and provider-specific attributes (e.g. `weflayr.*`).
10
10
  */
11
11
  export interface OtelSpanAttributes {
12
12
  'gen_ai.system'?: string;
@@ -43,7 +43,6 @@ export declare class WeflayrSpanProcessor implements SpanProcessor {
43
43
  private _mask?;
44
44
  private _maskFunctionSource;
45
45
  private _shouldExportSpan;
46
- private _vercelAiSdkSpanFilter;
47
46
  private _openLlmSpans;
48
47
  constructor({ exporter, exportMode, defaultTags, mask, shouldExportSpan, }: WeflayrSpanProcessorOptions);
49
48
  onStart(span: StartedSpan, parentContext: Context): void;
@@ -20,7 +20,7 @@ const api_1 = require("@opentelemetry/api");
20
20
  const sdk_trace_base_1 = require("@opentelemetry/sdk-trace-base");
21
21
  const propagation_1 = require("./propagation");
22
22
  const span_filter_1 = require("./span-filter");
23
- const vercel_ai_sdk_span_filter_1 = require("./vercel-ai-sdk-span-filter");
23
+ const strip_media_payloads_1 = require("./strip-media-payloads");
24
24
  const logger_1 = require("../logger");
25
25
  // setup({captureMessageContent: ...}) records the choice in this env var (the
26
26
  // official OTel GenAI one). Instrumentors gate content on their own knobs
@@ -30,24 +30,25 @@ const logger_1 = require("../logger");
30
30
  exports.CONTENT_CAPTURE_ENV = 'OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT';
31
31
  // The new-semconv JSON message attributes and the legacy indexed
32
32
  // `gen_ai.prompt.{i}.*` / `gen_ai.completion.{i}.*` attributes - plus the
33
- // request's tool definitions and structured-output schema (`ai.schema` is the
34
- // Vercel AI SDK's spelling): their descriptions are prompt content the same
35
- // way the messages are.
33
+ // request's tool definitions and structured-output schema: their descriptions
34
+ // are prompt content the same way the messages are.
36
35
  const CONTENT_ATTRIBUTES = [
37
36
  'gen_ai.input.messages',
38
37
  'gen_ai.output.messages',
39
38
  'gen_ai.system_instructions',
40
39
  'gen_ai.tool.definitions',
41
40
  'gen_ai.request.structured_output_schema',
42
- // The legacy Vercel AI SDK's (ai <= 6) response content. finishReason and
43
- // providerMetadata are NOT stripped: metadata and billing, not content.
44
- 'ai.schema',
45
- 'ai.response.text',
46
- 'ai.response.object',
47
- 'ai.response.reasoning',
48
- 'ai.response.toolCalls',
41
+ // The embedded texts of weflayr's own embeddings instrumentors.
42
+ 'weflayr.embeddings.input',
43
+ // A provider-run web search: the tool's own description, the queries the
44
+ // answer ran and the pages it read. weflayr.web_search.requests is NOT
45
+ // stripped: it is how many searches the call is billed for, not content.
46
+ 'weflayr.web_search.tool_definitions',
47
+ 'weflayr.web_search.output_blocks',
48
+ 'weflayr.web_search.options',
49
+ 'weflayr.web_search.citations',
49
50
  ];
50
- const CONTENT_ATTRIBUTE_PREFIXES = ['gen_ai.prompt.', 'gen_ai.completion.', 'ai.prompt'];
51
+ const CONTENT_ATTRIBUTE_PREFIXES = ['gen_ai.prompt.', 'gen_ai.completion.'];
51
52
  function _isContentAttribute(key) {
52
53
  return (CONTENT_ATTRIBUTES.includes(key) ||
53
54
  CONTENT_ATTRIBUTE_PREFIXES.some((prefix) => key.startsWith(prefix)));
@@ -62,7 +63,6 @@ class WeflayrSpanProcessor {
62
63
  _mask;
63
64
  _maskFunctionSource;
64
65
  _shouldExportSpan;
65
- _vercelAiSdkSpanFilter;
66
66
  // LLM spans we've started but not yet seen end. On forceFlush we end any
67
67
  // leftovers with ERROR status — covers cases like openllmetry's anthropic
68
68
  // streaming wrapper, which leaks the span when the underlying call
@@ -80,7 +80,6 @@ class WeflayrSpanProcessor {
80
80
  this._mask = mask;
81
81
  this._maskFunctionSource = mask ? String(mask) : null;
82
82
  this._shouldExportSpan = shouldExportSpan ?? span_filter_1.isDefaultExportSpan;
83
- this._vercelAiSdkSpanFilter = new vercel_ai_sdk_span_filter_1.VercelAiSdkSpanFilter();
84
83
  this._openLlmSpans = new Set();
85
84
  }
86
85
  onStart(span, parentContext) {
@@ -94,7 +93,6 @@ class WeflayrSpanProcessor {
94
93
  if ((0, span_filter_1.isKnownLLMInstrumentor)(span)) {
95
94
  this._openLlmSpans.add(span);
96
95
  }
97
- this._vercelAiSdkSpanFilter.onStart(span);
98
96
  (0, logger_1.debug)('span start: name=%s defaultTags=%d propagatedMetadata=%d', span.name, Object.keys(this._defaultTags).length, Object.keys(propagated).length);
99
97
  return this._delegate.onStart(span, parentContext);
100
98
  }
@@ -102,11 +100,7 @@ class WeflayrSpanProcessor {
102
100
  // Same concrete object stored in onStart, viewed here as a ReadableSpan.
103
101
  this._openLlmSpans.delete(span);
104
102
  try {
105
- const vercelVerdict = this._vercelAiSdkSpanFilter.shouldExport(span);
106
- if (vercelVerdict === true) {
107
- (0, vercel_ai_sdk_span_filter_1.stampVercelGatewayProviderName)(span);
108
- }
109
- if (vercelVerdict === false || (vercelVerdict === null && !this._shouldExportSpan(span))) {
103
+ if (!this._shouldExportSpan(span)) {
110
104
  (0, logger_1.debug)('span end: filtered out name=%s scope=%s', span.name, span.instrumentationScope?.name);
111
105
  return;
112
106
  }
@@ -121,6 +115,9 @@ class WeflayrSpanProcessor {
121
115
  delete span.attributes[key];
122
116
  }
123
117
  }
118
+ else {
119
+ (0, strip_media_payloads_1.stripMediaPayloads)(span.attributes);
120
+ }
124
121
  if (this._mask) {
125
122
  try {
126
123
  const masked = this._mask(span.attributes);
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Remove media payloads (base64 images, audio, documents) from the message
3
+ * content span attributes, right before export.
4
+ *
5
+ * An inline image, audio clip or document reaches the span in one of five shapes,
6
+ * depending on the instrumentor: a `blob` part carrying the base64 bytes in
7
+ * `content` (OpenAI chat completions, Anthropic), a `uri` part whose uri is a
8
+ * `data:` URI holding the same bytes (OpenAI Responses), Anthropic's own content
9
+ * block with the bytes under `source.data` (Bedrock), an OpenAI `file` block
10
+ * with the bytes under `file.file_data`, an OpenAI Responses `input_audio` block
11
+ * kept whole under `content` with the bytes under `content.input_audio.data`. A single
12
+ * image is hundreds of kilobytes, so keeping it would make every event on the
13
+ * queue that much bigger for nothing: the payload is never read back.
14
+ *
15
+ * Each of those becomes `{"type": "blob", "mime_type": "<mime_type>"}`, the same
16
+ * byte-less stub the google, cohere and bedrock instrumentation complements
17
+ * build themselves: the analyses reading these messages still see that the call
18
+ * carried content the stored text does not. A `uri` part pointing at a hosted
19
+ * image is a reference, not a payload, and is kept as is.
20
+ */
21
+ import type { OtelSpanAttributes } from './span-processor';
22
+ /**
23
+ * Rewrite in place every message content attribute of `attributes` that carries
24
+ * media bytes.
25
+ */
26
+ export declare function stripMediaPayloads(attributes: OtelSpanAttributes): void;