weflayr 0.27.0 → 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 (99) hide show
  1. package/dist/package.json +3 -3
  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_model_id_telemetry.d.ts +1 -1
  20. package/dist/src/instrumentation/bedrock/get_model_id_telemetry.js +1 -1
  21. package/dist/src/instrumentation/bedrock/get_openai_chat_format_telemetry.d.ts +45 -0
  22. package/dist/src/instrumentation/bedrock/get_openai_chat_format_telemetry.js +251 -0
  23. package/dist/src/instrumentation/bedrock/get_streaming_output_message_telemetry.d.ts +6 -5
  24. package/dist/src/instrumentation/bedrock/get_streaming_output_message_telemetry.js +15 -12
  25. package/dist/src/instrumentation/bedrock/get_tool_definitions_telemetry.d.ts +2 -2
  26. package/dist/src/instrumentation/bedrock/get_tool_definitions_telemetry.js +12 -4
  27. package/dist/src/instrumentation/bedrock/set_telemetry_attributes.d.ts +4 -2
  28. package/dist/src/instrumentation/bedrock/set_telemetry_attributes.js +10 -3
  29. package/dist/src/instrumentation/cohere/get_embeddings_telemetry.js +4 -6
  30. package/dist/src/instrumentation/cohere/get_input_message_content_telemetry.d.ts +23 -0
  31. package/dist/src/instrumentation/cohere/get_input_message_content_telemetry.js +105 -0
  32. package/dist/src/instrumentation/cohere/get_v2_chat_telemetry.d.ts +18 -0
  33. package/dist/src/instrumentation/cohere/get_v2_chat_telemetry.js +186 -0
  34. package/dist/src/instrumentation/cohere/set_telemetry_attributes.d.ts +3 -1
  35. package/dist/src/instrumentation/cohere/set_telemetry_attributes.js +5 -1
  36. package/dist/src/instrumentation/google/get_embeddings_telemetry.d.ts +8 -4
  37. package/dist/src/instrumentation/google/get_embeddings_telemetry.js +68 -35
  38. package/dist/src/instrumentation/google/get_provider_name.d.ts +2 -1
  39. package/dist/src/instrumentation/google/get_provider_name.js +2 -1
  40. package/dist/src/instrumentation/google/get_web_search_telemetry.d.ts +38 -0
  41. package/dist/src/instrumentation/google/get_web_search_telemetry.js +125 -0
  42. package/dist/src/instrumentation/google/set_telemetry_attributes.d.ts +2 -2
  43. package/dist/src/instrumentation/google/set_telemetry_attributes.js +6 -2
  44. package/dist/src/instrumentation/mistral/full_instrumentation.d.ts +60 -0
  45. package/dist/src/instrumentation/mistral/full_instrumentation.js +364 -0
  46. package/dist/src/instrumentation/mistral/get_input_message_content_telemetry.d.ts +35 -0
  47. package/dist/src/instrumentation/mistral/get_input_message_content_telemetry.js +113 -0
  48. package/dist/src/instrumentation/mistral/get_structured_output_schema_telemetry.d.ts +13 -0
  49. package/dist/src/instrumentation/mistral/get_structured_output_schema_telemetry.js +24 -0
  50. package/dist/src/instrumentation/mistral/get_vertex_telemetry.d.ts +54 -0
  51. package/dist/src/instrumentation/mistral/get_vertex_telemetry.js +153 -0
  52. package/dist/src/instrumentation/openai/get_bedrock_mantle_telemetry.d.ts +10 -0
  53. package/dist/src/instrumentation/openai/get_bedrock_mantle_telemetry.js +33 -0
  54. package/dist/src/instrumentation/openai/get_cache_and_reasoning_telemetry.d.ts +13 -6
  55. package/dist/src/instrumentation/openai/get_cache_and_reasoning_telemetry.js +17 -7
  56. package/dist/src/instrumentation/openai/get_cache_config_telemetry.d.ts +21 -4
  57. package/dist/src/instrumentation/openai/get_cache_config_telemetry.js +50 -11
  58. package/dist/src/instrumentation/openai/get_embeddings_telemetry.js +14 -6
  59. package/dist/src/instrumentation/openai/get_file_part_mime_type_telemetry.d.ts +26 -0
  60. package/dist/src/instrumentation/openai/get_file_part_mime_type_telemetry.js +100 -0
  61. package/dist/src/instrumentation/openai/get_openai_compatible_provider_telemetry.d.ts +48 -0
  62. package/dist/src/instrumentation/openai/get_openai_compatible_provider_telemetry.js +123 -0
  63. package/dist/src/instrumentation/openai/get_reasoning_content_telemetry.d.ts +28 -0
  64. package/dist/src/instrumentation/openai/get_reasoning_content_telemetry.js +130 -0
  65. package/dist/src/instrumentation/openai/get_responses_telemetry.d.ts +89 -0
  66. package/dist/src/instrumentation/openai/get_responses_telemetry.js +587 -0
  67. package/dist/src/instrumentation/openai/get_service_tier_telemetry.d.ts +20 -0
  68. package/dist/src/instrumentation/openai/get_service_tier_telemetry.js +84 -0
  69. package/dist/src/instrumentation/openai/get_structured_output_schema_telemetry.d.ts +7 -5
  70. package/dist/src/instrumentation/openai/get_structured_output_schema_telemetry.js +15 -8
  71. package/dist/src/instrumentation/openai/get_vertex_openai_compatible_telemetry.d.ts +23 -0
  72. package/dist/src/instrumentation/openai/get_vertex_openai_compatible_telemetry.js +69 -0
  73. package/dist/src/instrumentation/openai/get_web_search_telemetry.d.ts +74 -0
  74. package/dist/src/instrumentation/openai/get_web_search_telemetry.js +259 -0
  75. package/dist/src/instrumentation/openai/set_telemetry_attributes.d.ts +13 -1
  76. package/dist/src/instrumentation/openai/set_telemetry_attributes.js +33 -2
  77. package/dist/src/instrumentation/shared/get_structured_output_schema_telemetry.d.ts +1 -1
  78. package/dist/src/instrumentation/shared/get_structured_output_schema_telemetry.js +1 -1
  79. package/dist/src/instrumentation/shared/is_vertex_base_url.d.ts +17 -0
  80. package/dist/src/instrumentation/shared/is_vertex_base_url.js +40 -0
  81. package/dist/src/otel/propagation.d.ts +37 -10
  82. package/dist/src/otel/propagation.js +62 -22
  83. package/dist/src/otel/span-filter.d.ts +2 -0
  84. package/dist/src/otel/span-filter.js +10 -1
  85. package/dist/src/otel/span-processor.d.ts +1 -2
  86. package/dist/src/otel/span-processor.js +17 -20
  87. package/dist/src/otel/strip-media-payloads.d.ts +26 -0
  88. package/dist/src/otel/strip-media-payloads.js +135 -0
  89. package/dist/src/propagate-metadata-to-gateway.d.ts +37 -0
  90. package/dist/src/propagate-metadata-to-gateway.js +213 -0
  91. package/package.json +3 -3
  92. package/dist/src/instrumentation/vercel/full_instrumentation.d.ts +0 -3
  93. package/dist/src/instrumentation/vercel/full_instrumentation.js +0 -54
  94. package/dist/src/instrumentation/vercel/get_cache_creation_telemetry.d.ts +0 -22
  95. package/dist/src/instrumentation/vercel/get_cache_creation_telemetry.js +0 -66
  96. package/dist/src/instrumentation/vercel/get_structured_output_schema_telemetry.d.ts +0 -24
  97. package/dist/src/instrumentation/vercel/get_structured_output_schema_telemetry.js +0 -84
  98. package/dist/src/otel/vercel-ai-sdk-span-filter.d.ts +0 -12
  99. package/dist/src/otel/vercel-ai-sdk-span-filter.js +0 -88
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "weflayr",
3
- "version": "0.27.0",
3
+ "version": "0.28.0",
4
4
  "description": "Weflayr JS/TS SDK — instrument any LLM client",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",
@@ -31,8 +31,8 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@opentelemetry/api": "^1.9.1",
34
- "@opentelemetry/exporter-trace-otlp-http": "^0.221.0",
35
- "@opentelemetry/instrumentation": "^0.221.0",
34
+ "@opentelemetry/exporter-trace-otlp-http": "^0.222.0",
35
+ "@opentelemetry/instrumentation": "^0.222.0",
36
36
  "@opentelemetry/resources": "^2.8.0",
37
37
  "@opentelemetry/sdk-trace-base": "^2.8.0",
38
38
  "@opentelemetry/sdk-trace-node": "^2.8.0",
@@ -6,7 +6,7 @@ export declare const AiSdk: Readonly<{
6
6
  COHERE: "cohere";
7
7
  GOOGLE_GENAI: "google-genai";
8
8
  ELEVENLABS: "elevenlabs";
9
- VERCEL_AI_GATEWAY: "vercel_ai_gateway";
9
+ MISTRAL: "mistral";
10
10
  }>;
11
11
  export type AiSdk = (typeof AiSdk)[keyof typeof AiSdk];
12
12
  export declare const AIProviderName: Readonly<{
@@ -17,9 +17,10 @@ export declare const AIProviderName: Readonly<{
17
17
  COHERE: "cohere";
18
18
  ELEVENLABS: "elevenlabs";
19
19
  MISTRAL: "mistral";
20
- VERCEL_AI_GATEWAY: "vercel_ai_gateway";
21
- GEMINI: "gemini";
20
+ GOOGLE: "google";
22
21
  VERTEX: "vertex";
22
+ ALIBABA: "alibaba";
23
+ SPACEXAI: "spacexai";
23
24
  }>;
24
25
  export type AIProviderName = (typeof AIProviderName)[keyof typeof AIProviderName];
25
26
  interface RegistryEntry {
@@ -12,13 +12,13 @@ exports.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
+ MISTRAL: 'mistral',
16
16
  });
17
- // Allowed values for `propagateMetadata({ providerName })`: the actual AI
17
+ // Allowed values for `propagateMetadata({ providerNameOverride })`: the actual AI
18
18
  // providers a call can be attributed to. Unlike AiSdk this names providers,
19
- // not SDKs — google-genai is an SDK whose calls are labelled gemini or vertex,
20
- // and mistral has no Node OTel instrumentor so it is reached through the
21
- // OpenAI SDK (OpenAI-compatible endpoint) but reported as itself.
19
+ // not SDKs — google-genai is an SDK whose calls are labeled google or vertex,
20
+ // and mistral can also be reached through the OpenAI SDK (OpenAI-compatible
21
+ // endpoint) while being reported as itself.
22
22
  exports.AIProviderName = Object.freeze({
23
23
  OPENAI: 'openai',
24
24
  AZURE_OPENAI: 'azure',
@@ -27,15 +27,16 @@ exports.AIProviderName = Object.freeze({
27
27
  COHERE: 'cohere',
28
28
  ELEVENLABS: 'elevenlabs',
29
29
  MISTRAL: 'mistral',
30
- VERCEL_AI_GATEWAY: 'vercel_ai_gateway',
31
- GEMINI: 'gemini',
30
+ GOOGLE: 'google',
32
31
  VERTEX: 'vertex',
32
+ ALIBABA: 'alibaba',
33
+ SPACEXAI: 'spacexai',
33
34
  });
34
35
  // The primary instrumentor for each provider SDK. Most are third-party
35
36
  // traceloop packages, loaded by `package` name. Providers with no usable
36
- // third-party instrumentor (e.g. ElevenLabs) instead carry a `requirePath` +
37
- // `className` pointing at the instrumentor weflayr ships itself; `package` then
38
- // names the provider SDK to suggest installing if its import fails.
37
+ // third-party instrumentor (ElevenLabs, and Mistral, whose SDK traces itself)
38
+ // instead carry a `requirePath` + `className` pointing at the instrumentor
39
+ // weflayr ships itself; `package` then names the provider SDK.
39
40
  const REGISTRY = {
40
41
  [exports.AiSdk.OPENAI]: { package: '@traceloop/instrumentation-openai' },
41
42
  [exports.AiSdk.ANTHROPIC]: { package: '@traceloop/instrumentation-anthropic' },
@@ -47,10 +48,10 @@ const REGISTRY = {
47
48
  requirePath: './instrumentation/elevenlabs/full_instrumentation',
48
49
  className: 'ElevenLabsInstrumentor',
49
50
  },
50
- [exports.AiSdk.VERCEL_AI_GATEWAY]: {
51
- package: 'ai',
52
- requirePath: './instrumentation/vercel/full_instrumentation',
53
- className: 'VercelAiSdkInstrumentor',
51
+ [exports.AiSdk.MISTRAL]: {
52
+ package: '@mistralai/mistralai',
53
+ requirePath: './instrumentation/mistral/full_instrumentation',
54
+ className: 'MistralInstrumentor',
54
55
  },
55
56
  };
56
57
  exports.REGISTRY = REGISTRY;
@@ -105,10 +106,10 @@ async function _instrumentAiSdk(aiSdk) {
105
106
  }
106
107
  const entry = REGISTRY[aiSdk];
107
108
  if (entry.requirePath) {
108
- // No usable third-party instrumentor — use the hand-written one bundled
109
- // with weflayr (it covers every endpoint of the provider).
109
+ // No usable third-party instrumentor: use the one bundled with weflayr
110
+ // (it covers every endpoint of the provider).
110
111
  const Instrumentor = require(entry.requirePath)[entry.className];
111
- new Instrumentor().instrument();
112
+ await new Instrumentor().instrument();
112
113
  (0, logger_1.debug)('autoInstrument: registered aiSdk=%s package=%s', aiSdk, entry.package);
113
114
  return;
114
115
  }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Reading the mime type out of a `data:` URI.
3
+ *
4
+ * Providers hand inline media to the span in several shapes, and four of them
5
+ * carry the bytes as a data URI: OpenAI's `image_url.url` and `file.file_data`,
6
+ * Mistral's `image_url` and `document_url`, and Cohere v2's `image_url.url`.
7
+ * The instrumentation complements and the span processor all need the mime type
8
+ * out of those, so they read it here rather than each parsing the URI again.
9
+ */
10
+ /**
11
+ * The mime type of a `data:<mime_type>[;base64],<payload>` URI, or null when
12
+ * the string is not a data URI.
13
+ *
14
+ * e.g. `"data:image/png;base64,AAA"` -> `"image/png"`,
15
+ * `"https://example.com/cat.png"` -> `null`.
16
+ */
17
+ export declare function getDataUriMimeType(uri: string): string | null;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ /**
3
+ * Reading the mime type out of a `data:` URI.
4
+ *
5
+ * Providers hand inline media to the span in several shapes, and four of them
6
+ * carry the bytes as a data URI: OpenAI's `image_url.url` and `file.file_data`,
7
+ * Mistral's `image_url` and `document_url`, and Cohere v2's `image_url.url`.
8
+ * The instrumentation complements and the span processor all need the mime type
9
+ * out of those, so they read it here rather than each parsing the URI again.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.getDataUriMimeType = getDataUriMimeType;
13
+ const DATA_URI_PREFIX = 'data:';
14
+ /**
15
+ * The mime type of a `data:<mime_type>[;base64],<payload>` URI, or null when
16
+ * the string is not a data URI.
17
+ *
18
+ * e.g. `"data:image/png;base64,AAA"` -> `"image/png"`,
19
+ * `"https://example.com/cat.png"` -> `null`.
20
+ */
21
+ function getDataUriMimeType(uri) {
22
+ if (!uri.startsWith(DATA_URI_PREFIX))
23
+ return null;
24
+ return uri.slice(DATA_URI_PREFIX.length).split(';')[0].split(',')[0];
25
+ }
@@ -1,9 +1,10 @@
1
1
  import { setup, flush, type SetupOptions } from './setup';
2
2
  import { autoInstrument, AiSdk, AIProviderName } from './auto-instrument';
3
- import { propagateMetadata, type PropagateMetadataOptions } from './otel/propagation';
3
+ import { createFeatureRunUuid, propagateMetadata, setCallName, type PropagateMetadataOptions } from './otel/propagation';
4
+ import { enablePropagateMetadataToGateway } from './propagate-metadata-to-gateway';
4
5
  import type { Tags, OtelSpanAttributes } from './otel/span-processor';
5
6
  import * as api from './api';
6
- export { setup, flush, autoInstrument, AiSdk, AIProviderName, propagateMetadata, api };
7
+ export { setup, flush, autoInstrument, AiSdk, AIProviderName, createFeatureRunUuid, propagateMetadata, setCallName, enablePropagateMetadataToGateway, api, };
7
8
  export type { SetupOptions, PropagateMetadataOptions, Tags, OtelSpanAttributes };
8
9
  export declare const client: typeof api.client;
9
10
  export type { ApiClientOptions } from './api';
package/dist/src/index.js CHANGED
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.client = exports.api = exports.propagateMetadata = exports.AIProviderName = exports.AiSdk = exports.autoInstrument = exports.flush = exports.setup = void 0;
36
+ exports.client = exports.api = exports.enablePropagateMetadataToGateway = exports.setCallName = exports.propagateMetadata = exports.createFeatureRunUuid = exports.AIProviderName = exports.AiSdk = exports.autoInstrument = exports.flush = exports.setup = void 0;
37
37
  const setup_1 = require("./setup");
38
38
  Object.defineProperty(exports, "setup", { enumerable: true, get: function () { return setup_1.setup; } });
39
39
  Object.defineProperty(exports, "flush", { enumerable: true, get: function () { return setup_1.flush; } });
@@ -42,7 +42,11 @@ Object.defineProperty(exports, "autoInstrument", { enumerable: true, get: functi
42
42
  Object.defineProperty(exports, "AiSdk", { enumerable: true, get: function () { return auto_instrument_1.AiSdk; } });
43
43
  Object.defineProperty(exports, "AIProviderName", { enumerable: true, get: function () { return auto_instrument_1.AIProviderName; } });
44
44
  const propagation_1 = require("./otel/propagation");
45
+ Object.defineProperty(exports, "createFeatureRunUuid", { enumerable: true, get: function () { return propagation_1.createFeatureRunUuid; } });
45
46
  Object.defineProperty(exports, "propagateMetadata", { enumerable: true, get: function () { return propagation_1.propagateMetadata; } });
47
+ Object.defineProperty(exports, "setCallName", { enumerable: true, get: function () { return propagation_1.setCallName; } });
48
+ const propagate_metadata_to_gateway_1 = require("./propagate-metadata-to-gateway");
49
+ Object.defineProperty(exports, "enablePropagateMetadataToGateway", { enumerable: true, get: function () { return propagate_metadata_to_gateway_1.enablePropagateMetadataToGateway; } });
46
50
  // Public-API client (revenue, key metrics, customer tags).
47
51
  const api = __importStar(require("./api"));
48
52
  exports.api = api;
@@ -6,7 +6,9 @@
6
6
  * - the `message_delta` chunk's `delta.stop_reason`, so streamed spans format
7
7
  * their output message with an empty `finish_reason`, and
8
8
  * - the `content_block_delta` chunks' `input_json_delta` fragments, so a
9
- * streamed tool call's `tool_call` part carries no `arguments`.
9
+ * streamed tool call's `tool_call` part carries no `arguments`. This covers
10
+ * both a caller's own `tool_use` block and the `server_tool_use` block of a
11
+ * provider-run web search, whose query streams the same way.
10
12
  *
11
13
  * `_streamingWrapPromise(client, moduleExports, { span, type, promise })`
12
14
  * returns `new moduleExports.APIPromise(...)` whose parse step wraps the real
@@ -7,7 +7,9 @@
7
7
  * - the `message_delta` chunk's `delta.stop_reason`, so streamed spans format
8
8
  * their output message with an empty `finish_reason`, and
9
9
  * - the `content_block_delta` chunks' `input_json_delta` fragments, so a
10
- * streamed tool call's `tool_call` part carries no `arguments`.
10
+ * streamed tool call's `tool_call` part carries no `arguments`. This covers
11
+ * both a caller's own `tool_use` block and the `server_tool_use` block of a
12
+ * provider-run web search, whose query streams the same way.
11
13
  *
12
14
  * `_streamingWrapPromise(client, moduleExports, { span, type, promise })`
13
15
  * returns `new moduleExports.APIPromise(...)` whose parse step wraps the real
@@ -109,9 +111,10 @@ class AnthropicStreamingAccumulationInstrumentor {
109
111
  for (const [index, argumentsJson] of gaps.toolArgumentsJsonByIndex) {
110
112
  const block = Array.isArray(args.result.content) && args.result.content[index];
111
113
  // Only fill the gap: a block whose input upstream DID accumulate
112
- // (a future version) is left alone.
114
+ // (a future version) is left alone. `server_tool_use` is the
115
+ // provider-run tool (web search), whose query streams the same way.
113
116
  if (block &&
114
- block.type === 'tool_use' &&
117
+ (block.type === 'tool_use' || block.type === 'server_tool_use') &&
115
118
  (!block.input || !Object.keys(block.input).length)) {
116
119
  try {
117
120
  block.input = JSON.parse(argumentsJson);
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Billing attribution for Claude called on Vertex AI through `AnthropicVertex`.
3
+ *
4
+ * `AnthropicVertex` (`@anthropic-ai/vertex-sdk`) is Anthropic's client for
5
+ * Claude on Vertex: the same `Messages` resource as the plain `Anthropic`
6
+ * client, pointed at a Vertex AI host. The traceloop instrumentor attributes
7
+ * its calls to "anthropic", which is right for parsing them (the payload and
8
+ * usage are Anthropic's) but not for pricing them: Google bills them at
9
+ * Vertex's prices. So their spans get `weflayr.billing_provider` set to
10
+ * `vertex`, which the worker prices by, while `gen_ai.provider.name`, which it
11
+ * parses by, stays "anthropic".
12
+ *
13
+ * traceloop starts the span in `startSpan`, which is handed the request but not
14
+ * the client. So the client of each call is recorded, for the duration of the
15
+ * call, around the method `patchAnthropic` builds for each wrapped `create`,
16
+ * and read back in `startSpan`, which that method calls synchronously before
17
+ * the request is sent, the error path included.
18
+ *
19
+ * `@anthropic-ai/vertex-sdk` loads `@anthropic-ai/sdk` by its subpaths only
20
+ * (`@anthropic-ai/sdk/client`, `.../resources/index`), which the traceloop
21
+ * hook, registered on the main module, never sees: an app that only uses
22
+ * `AnthropicVertex` would get no span at all. Its `Messages` class is the same
23
+ * object as the main module's `Anthropic.Messages`, so loading the main module
24
+ * once here is what gets it patched.
25
+ */
26
+ export declare class AnthropicVertexBillingInstrumentor {
27
+ private readonly loadAnthropicSdk;
28
+ private _patched;
29
+ constructor(loadAnthropicSdk?: () => void);
30
+ instrument(instrumentation: any): void;
31
+ uninstrument(): void;
32
+ }
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ /**
3
+ * Billing attribution for Claude called on Vertex AI through `AnthropicVertex`.
4
+ *
5
+ * `AnthropicVertex` (`@anthropic-ai/vertex-sdk`) is Anthropic's client for
6
+ * Claude on Vertex: the same `Messages` resource as the plain `Anthropic`
7
+ * client, pointed at a Vertex AI host. The traceloop instrumentor attributes
8
+ * its calls to "anthropic", which is right for parsing them (the payload and
9
+ * usage are Anthropic's) but not for pricing them: Google bills them at
10
+ * Vertex's prices. So their spans get `weflayr.billing_provider` set to
11
+ * `vertex`, which the worker prices by, while `gen_ai.provider.name`, which it
12
+ * parses by, stays "anthropic".
13
+ *
14
+ * traceloop starts the span in `startSpan`, which is handed the request but not
15
+ * the client. So the client of each call is recorded, for the duration of the
16
+ * call, around the method `patchAnthropic` builds for each wrapped `create`,
17
+ * and read back in `startSpan`, which that method calls synchronously before
18
+ * the request is sent, the error path included.
19
+ *
20
+ * `@anthropic-ai/vertex-sdk` loads `@anthropic-ai/sdk` by its subpaths only
21
+ * (`@anthropic-ai/sdk/client`, `.../resources/index`), which the traceloop
22
+ * hook, registered on the main module, never sees: an app that only uses
23
+ * `AnthropicVertex` would get no span at all. Its `Messages` class is the same
24
+ * object as the main module's `Anthropic.Messages`, so loading the main module
25
+ * once here is what gets it patched.
26
+ */
27
+ Object.defineProperty(exports, "__esModule", { value: true });
28
+ exports.AnthropicVertexBillingInstrumentor = void 0;
29
+ const is_vertex_base_url_1 = require("../shared/is_vertex_base_url");
30
+ // The anthropic client of the Messages call in progress, undefined outside of
31
+ // one. A module variable is enough: it is set and read in the same synchronous
32
+ // stretch of the call.
33
+ let clientOfTheCallInProgress;
34
+ function requireAnthropicSdk() {
35
+ try {
36
+ require('@anthropic-ai/sdk');
37
+ }
38
+ catch (err) {
39
+ throw new Error(`weflayr: @anthropic-ai/sdk could not be loaded (${err?.message || err}). ` +
40
+ 'Install it with: npm install @anthropic-ai/sdk');
41
+ }
42
+ }
43
+ class AnthropicVertexBillingInstrumentor {
44
+ loadAnthropicSdk;
45
+ // [instrumentation, methodName, originalFunction] for uninstrument().
46
+ _patched = [];
47
+ constructor(loadAnthropicSdk = requireAnthropicSdk) {
48
+ this.loadAnthropicSdk = loadAnthropicSdk;
49
+ }
50
+ instrument(instrumentation) {
51
+ if (!instrumentation)
52
+ return;
53
+ const originalPatchAnthropic = instrumentation.patchAnthropic;
54
+ const originalStartSpan = instrumentation.startSpan;
55
+ if (typeof originalPatchAnthropic !== 'function' || typeof originalStartSpan !== 'function') {
56
+ throw new Error('weflayr: @traceloop/instrumentation-anthropic no longer defines patchAnthropic and ' +
57
+ 'startSpan, so Claude calls made through AnthropicVertex cannot be billed to Vertex.');
58
+ }
59
+ instrumentation.patchAnthropic = function patchAnthropicRecordingTheClient(...patchArgs) {
60
+ const buildTracedMethod = originalPatchAnthropic.apply(this, patchArgs);
61
+ return (original) => {
62
+ const tracedMethod = buildTracedMethod(original);
63
+ return function methodRecordingTheClient(...args) {
64
+ const previousClient = clientOfTheCallInProgress;
65
+ clientOfTheCallInProgress = this?._client;
66
+ try {
67
+ return tracedMethod.apply(this, args);
68
+ }
69
+ finally {
70
+ clientOfTheCallInProgress = previousClient;
71
+ }
72
+ };
73
+ };
74
+ };
75
+ this._patched.push([instrumentation, 'patchAnthropic', originalPatchAnthropic]);
76
+ instrumentation.startSpan = function startSpanBilledToVertex(args) {
77
+ const span = originalStartSpan.call(this, args);
78
+ try {
79
+ if (span?.isRecording?.() && (0, is_vertex_base_url_1.isVertexBaseURL)(clientOfTheCallInProgress?.baseURL)) {
80
+ span.setAttribute(is_vertex_base_url_1.BILLING_PROVIDER_ATTR, is_vertex_base_url_1.VERTEX_BILLING_PROVIDER);
81
+ }
82
+ }
83
+ catch {
84
+ // Never break span creation over an enrichment failure.
85
+ }
86
+ return span;
87
+ };
88
+ this._patched.push([instrumentation, 'startSpan', originalStartSpan]);
89
+ // After the two patches above, so the traceloop patch this load triggers
90
+ // builds its methods through them.
91
+ this.loadAnthropicSdk();
92
+ }
93
+ uninstrument() {
94
+ for (const [owner, methodName, original] of [...this._patched].reverse()) {
95
+ owner[methodName] = original;
96
+ }
97
+ this._patched = [];
98
+ }
99
+ }
100
+ exports.AnthropicVertexBillingInstrumentor = AnthropicVertexBillingInstrumentor;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Provider-run web search capture for Anthropic spans.
3
+ *
4
+ * Anthropic runs the search itself when the request carries its server tool
5
+ * (`{ type: 'web_search_20250305', name: 'web_search', ... }`), and bills every
6
+ * search the call ran, reported as `usage.server_tool_use.web_search_requests`.
7
+ *
8
+ * Unlike the Python instrumentor, `@traceloop/instrumentation-anthropic` does
9
+ * put the answer's search blocks on the span, but in its own shapes: a
10
+ * `server_tool_use` block becomes a `server_tool_call` part, and a
11
+ * `web_search_tool_result` block falls through its unknown-block branch and is
12
+ * copied verbatim, `encrypted_content` blobs included, several kilobytes per
13
+ * result, which must never reach the queue. It also drops
14
+ * `usage.server_tool_use`, so the searches are not billed.
15
+ *
16
+ * So this complement does three things, all off `_endSpan`'s accumulated result
17
+ * (which both the streamed and non-streamed paths reach):
18
+ *
19
+ * 1. stamps the request's server tool, the answer's search blocks and the
20
+ * billed count under `weflayr.web_search.*`, in Anthropic's own vocabulary,
21
+ * for `normalizers/anthropic.py` to map;
22
+ * 2. drops the `encrypted_content` blob of every result on the way, the same
23
+ * rule as inline media;
24
+ * 3. removes the search blocks from the result before the instrumentor formats
25
+ * the output message, so the search is recorded once and in one shape. The
26
+ * Python instrumentor writes no such parts, and both SDKs must produce the
27
+ * same event for the same call.
28
+ */
29
+ /** The request's web search server-tool definitions, as sent. A user-defined
30
+ * function tool carries no `type`, so it never matches. */
31
+ export declare function extractWebSearchToolDefinitions(tools: any): any[];
32
+ /** The answer's search blocks: what the model searched for, and the pages it read
33
+ * back, with the page blobs dropped. */
34
+ export declare function extractWebSearchOutputBlocks(content: any): any[];
35
+ export declare class AnthropicWebSearchInstrumentor {
36
+ private _patched;
37
+ instrument(instrumentation: any): void;
38
+ uninstrument(): void;
39
+ }
40
+ /** Stamp the call's searches and the count it is billed for, then take the search
41
+ * blocks out of the result so the instrumentor's own output message does not
42
+ * carry them a second time. */
43
+ export declare function stampAndStripSearchBlocks(span: any, result: any): void;
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ /**
3
+ * Provider-run web search capture for Anthropic spans.
4
+ *
5
+ * Anthropic runs the search itself when the request carries its server tool
6
+ * (`{ type: 'web_search_20250305', name: 'web_search', ... }`), and bills every
7
+ * search the call ran, reported as `usage.server_tool_use.web_search_requests`.
8
+ *
9
+ * Unlike the Python instrumentor, `@traceloop/instrumentation-anthropic` does
10
+ * put the answer's search blocks on the span, but in its own shapes: a
11
+ * `server_tool_use` block becomes a `server_tool_call` part, and a
12
+ * `web_search_tool_result` block falls through its unknown-block branch and is
13
+ * copied verbatim, `encrypted_content` blobs included, several kilobytes per
14
+ * result, which must never reach the queue. It also drops
15
+ * `usage.server_tool_use`, so the searches are not billed.
16
+ *
17
+ * So this complement does three things, all off `_endSpan`'s accumulated result
18
+ * (which both the streamed and non-streamed paths reach):
19
+ *
20
+ * 1. stamps the request's server tool, the answer's search blocks and the
21
+ * billed count under `weflayr.web_search.*`, in Anthropic's own vocabulary,
22
+ * for `normalizers/anthropic.py` to map;
23
+ * 2. drops the `encrypted_content` blob of every result on the way, the same
24
+ * rule as inline media;
25
+ * 3. removes the search blocks from the result before the instrumentor formats
26
+ * the output message, so the search is recorded once and in one shape. The
27
+ * Python instrumentor writes no such parts, and both SDKs must produce the
28
+ * same event for the same call.
29
+ */
30
+ Object.defineProperty(exports, "__esModule", { value: true });
31
+ exports.AnthropicWebSearchInstrumentor = void 0;
32
+ exports.extractWebSearchToolDefinitions = extractWebSearchToolDefinitions;
33
+ exports.extractWebSearchOutputBlocks = extractWebSearchOutputBlocks;
34
+ exports.stampAndStripSearchBlocks = stampAndStripSearchBlocks;
35
+ const REQUESTS_ATTR = 'weflayr.web_search.requests';
36
+ const TOOL_DEFINITIONS_ATTR = 'weflayr.web_search.tool_definitions';
37
+ const OUTPUT_BLOCKS_ATTR = 'weflayr.web_search.output_blocks';
38
+ // Anthropic versions its server tools by date, so the type of the web search
39
+ // tool changes with each revision while the prefix stays.
40
+ const WEB_SEARCH_TOOL_TYPE_PREFIX = 'web_search';
41
+ // The answer blocks a server-run search produces: the query the model ran, then
42
+ // the pages it got back.
43
+ const SEARCH_QUERY_BLOCK_TYPE = 'server_tool_use';
44
+ const SEARCH_RESULTS_BLOCK_TYPE = 'web_search_tool_result';
45
+ // Per result, an opaque blob of the page Anthropic read. Nothing reads it back.
46
+ const ENCRYPTED_RESULT_FIELD = 'encrypted_content';
47
+ function isWebSearchBlock(block) {
48
+ return (!!block && (block.type === SEARCH_QUERY_BLOCK_TYPE || block.type === SEARCH_RESULTS_BLOCK_TYPE));
49
+ }
50
+ /** A `web_search_tool_result` block without the opaque page blob each of its
51
+ * results carries. */
52
+ function dropEncryptedResults(block) {
53
+ if (!Array.isArray(block.content))
54
+ return block;
55
+ return {
56
+ ...block,
57
+ content: block.content.map((result) => {
58
+ if (!result || typeof result !== 'object')
59
+ return result;
60
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
61
+ const { [ENCRYPTED_RESULT_FIELD]: _encrypted, ...kept } = result;
62
+ return kept;
63
+ }),
64
+ };
65
+ }
66
+ /** The request's web search server-tool definitions, as sent. A user-defined
67
+ * function tool carries no `type`, so it never matches. */
68
+ function extractWebSearchToolDefinitions(tools) {
69
+ if (!Array.isArray(tools))
70
+ return [];
71
+ return tools.filter((tool) => tool && typeof tool.type === 'string' && tool.type.startsWith(WEB_SEARCH_TOOL_TYPE_PREFIX));
72
+ }
73
+ /** The answer's search blocks: what the model searched for, and the pages it read
74
+ * back, with the page blobs dropped. */
75
+ function extractWebSearchOutputBlocks(content) {
76
+ if (!Array.isArray(content))
77
+ return [];
78
+ return content.filter(isWebSearchBlock).map(dropEncryptedResults);
79
+ }
80
+ class AnthropicWebSearchInstrumentor {
81
+ // [instrumentation, methodName, originalFunction] for uninstrument().
82
+ _patched = [];
83
+ // No-op (and never throws) if the instrumentor's internals have changed shape:
84
+ // the worst case is a missing web search, never a crash.
85
+ instrument(instrumentation) {
86
+ if (!instrumentation)
87
+ return;
88
+ // Request side: the same `startSpan(request)` the tool-definitions
89
+ // complement wraps, for both the streamed and non-streamed paths.
90
+ const startSpanOriginal = instrumentation.startSpan;
91
+ if (typeof startSpanOriginal === 'function') {
92
+ instrumentation.startSpan = function startSpanWithWebSearch(request) {
93
+ const span = startSpanOriginal.call(this, request);
94
+ try {
95
+ const definitions = extractWebSearchToolDefinitions(request?.params?.tools);
96
+ if (definitions.length) {
97
+ span.setAttribute(TOOL_DEFINITIONS_ATTR, JSON.stringify(definitions));
98
+ }
99
+ }
100
+ catch {
101
+ // Never break span creation over an enrichment failure.
102
+ }
103
+ return span;
104
+ };
105
+ this._patched.push([instrumentation, 'startSpan', startSpanOriginal]);
106
+ }
107
+ // Response side: the accumulated result, before the output message is
108
+ // formatted from it.
109
+ const endSpanOriginal = instrumentation._endSpan;
110
+ if (typeof endSpanOriginal === 'function') {
111
+ instrumentation._endSpan = function endSpanWithWebSearch(args) {
112
+ try {
113
+ stampAndStripSearchBlocks(args && args.span, args && args.result);
114
+ }
115
+ catch {
116
+ // Never break span finalization over an enrichment failure.
117
+ }
118
+ return endSpanOriginal.call(this, args);
119
+ };
120
+ this._patched.push([instrumentation, '_endSpan', endSpanOriginal]);
121
+ }
122
+ }
123
+ uninstrument() {
124
+ // Restore newest first, so a double instrument() unwinds back to the true
125
+ // original instead of reinstalling the first wrapper.
126
+ for (const [owner, methodName, original] of [...this._patched].reverse()) {
127
+ owner[methodName] = original;
128
+ }
129
+ this._patched = [];
130
+ }
131
+ }
132
+ exports.AnthropicWebSearchInstrumentor = AnthropicWebSearchInstrumentor;
133
+ /** Stamp the call's searches and the count it is billed for, then take the search
134
+ * blocks out of the result so the instrumentor's own output message does not
135
+ * carry them a second time. */
136
+ function stampAndStripSearchBlocks(span, result) {
137
+ if (!span || !span.isRecording || !span.isRecording() || !result)
138
+ return;
139
+ const requests = result.usage?.server_tool_use?.web_search_requests;
140
+ if (requests)
141
+ span.setAttribute(REQUESTS_ATTR, requests);
142
+ const blocks = extractWebSearchOutputBlocks(result.content);
143
+ if (!blocks.length)
144
+ return;
145
+ span.setAttribute(OUTPUT_BLOCKS_ATTR, JSON.stringify(blocks));
146
+ result.content = result.content.filter((block) => !isWebSearchBlock(block));
147
+ }
@@ -4,8 +4,11 @@
4
4
  * Wires the weflayr-supplied patches the traceloop Anthropic instrumentor
5
5
  * misses: cached-prompt token counts on the chat span (it reads them off the
6
6
  * response but writes no `gen_ai.usage.cache_*` attribute), the request's tool
7
- * definitions and structured-output schema, and the streamed stop reason and
8
- * tool-call arguments (its stream accumulator drops both).
7
+ * definitions and structured-output schema, the streamed stop reason and
8
+ * tool-call arguments (its stream accumulator drops both), and the provider-run
9
+ * web search (the tool's type, the queries the answer ran and the searches
10
+ * Anthropic bills for), and the billing of the calls `AnthropicVertex` makes
11
+ * to Claude on Vertex AI, which it attributes to Anthropic.
9
12
  */
10
13
  export declare class AnthropicTelemetryInstrumentor {
11
14
  private _subInstrumentors;
@@ -5,8 +5,11 @@
5
5
  * Wires the weflayr-supplied patches the traceloop Anthropic instrumentor
6
6
  * misses: cached-prompt token counts on the chat span (it reads them off the
7
7
  * response but writes no `gen_ai.usage.cache_*` attribute), the request's tool
8
- * definitions and structured-output schema, and the streamed stop reason and
9
- * tool-call arguments (its stream accumulator drops both).
8
+ * definitions and structured-output schema, the streamed stop reason and
9
+ * tool-call arguments (its stream accumulator drops both), and the provider-run
10
+ * web search (the tool's type, the queries the answer ran and the searches
11
+ * Anthropic bills for), and the billing of the calls `AnthropicVertex` makes
12
+ * to Claude on Vertex AI, which it attributes to Anthropic.
10
13
  */
11
14
  Object.defineProperty(exports, "__esModule", { value: true });
12
15
  exports.AnthropicTelemetryInstrumentor = void 0;
@@ -15,16 +18,25 @@ const get_cache_config_telemetry_1 = require("./get_cache_config_telemetry");
15
18
  const get_cache_and_reasoning_telemetry_1 = require("./get_cache_and_reasoning_telemetry");
16
19
  const get_streaming_accumulation_telemetry_1 = require("./get_streaming_accumulation_telemetry");
17
20
  const get_tool_definitions_telemetry_1 = require("./get_tool_definitions_telemetry");
21
+ const get_vertex_billing_telemetry_1 = require("./get_vertex_billing_telemetry");
22
+ const get_web_search_telemetry_1 = require("./get_web_search_telemetry");
18
23
  const get_structured_output_schema_telemetry_2 = require("./get_structured_output_schema_telemetry");
19
24
  class AnthropicTelemetryInstrumentor {
20
25
  _subInstrumentors;
21
26
  constructor() {
27
+ // Order matters between the last two of these three: each wraps `_endSpan`
28
+ // and does its own work BEFORE delegating, so the one registered LAST runs
29
+ // FIRST. The web-search complement reads the streamed `server_tool_use`
30
+ // block's query, which the streamed-accumulation one fills in, so
31
+ // accumulation has to be registered after it to run before it.
22
32
  this._subInstrumentors = [
23
33
  new get_cache_and_reasoning_telemetry_1.AnthropicPromptCachingInstrumentor(),
24
34
  new get_cache_config_telemetry_1.AnthropicCacheConfigInstrumentor(),
25
- new get_streaming_accumulation_telemetry_1.AnthropicStreamingAccumulationInstrumentor(),
26
35
  new get_tool_definitions_telemetry_1.AnthropicToolDefinitionsInstrumentor(),
36
+ new get_web_search_telemetry_1.AnthropicWebSearchInstrumentor(),
37
+ new get_streaming_accumulation_telemetry_1.AnthropicStreamingAccumulationInstrumentor(),
27
38
  new get_structured_output_schema_telemetry_1.TraceloopStructuredOutputSchemaInstrumentor(get_structured_output_schema_telemetry_2.readStructuredOutputSchema),
39
+ new get_vertex_billing_telemetry_1.AnthropicVertexBillingInstrumentor(),
28
40
  ];
29
41
  }
30
42
  instrument(instrumentation) {
@@ -32,7 +44,8 @@ class AnthropicTelemetryInstrumentor {
32
44
  sub.instrument(instrumentation);
33
45
  }
34
46
  uninstrument() {
35
- for (const sub of this._subInstrumentors)
47
+ // Newest first: several of them patch the same function.
48
+ for (const sub of [...this._subInstrumentors].reverse())
36
49
  sub.uninstrument();
37
50
  }
38
51
  }