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
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Media parts of the input messages on Mistral chat spans.
3
+ *
4
+ * The SDK's built-in tracing shapes exactly two kinds of content block: text,
5
+ * and `image_url`, which it records as a `uri` part. Every other block reaches
6
+ * `gen_ai.input.messages` as a bare `{"type": "<block type>"}` (the fallback
7
+ * branch of its `formatting.ts`), so a PDF sent as `document_url` is stored
8
+ * under a name with no mime type, where every other provider stores the
9
+ * byte-less `{"type": "blob", "mime_type": "application/pdf"}`.
10
+ *
11
+ * The mime type it lacks is in the request the caller sent: the block's url is
12
+ * a `data:` URI holding the bytes. This complement walks the parts the tracing
13
+ * just stamped alongside the request's blocks and replaces those nameless parts
14
+ * with the same stub the other providers produce. Blocks the tracing did shape,
15
+ * and blocks whose url points at a hosted file rather than at inline bytes, are
16
+ * left alone.
17
+ */
18
+ import type { Span } from '@opentelemetry/api';
19
+ export declare const INPUT_MESSAGES_ATTRIBUTE = "gen_ai.input.messages";
20
+ /**
21
+ * `gen_ai.input.messages` with every nameless media part replaced by its blob
22
+ * stub, or null when there was nothing to repair (the tracing's own JSON is
23
+ * then left untouched).
24
+ *
25
+ * A message's content blocks map one-to-one onto its parts, in order, which is
26
+ * what lets the repair walk them in step.
27
+ */
28
+ export declare function repairMediaInputParts(inputMessagesJson: unknown, requestMessages: unknown): string | null;
29
+ /**
30
+ * Re-stamp `gen_ai.input.messages` when the request carries inline media.
31
+ *
32
+ * Only ever rewrites an attribute the tracing already set, so it never adds
33
+ * message content the tracing did not record.
34
+ */
35
+ export declare function setInputMessageContentTelemetry(span: Span, requestBody: Record<string, unknown>): void;
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ /**
3
+ * Media parts of the input messages on Mistral chat spans.
4
+ *
5
+ * The SDK's built-in tracing shapes exactly two kinds of content block: text,
6
+ * and `image_url`, which it records as a `uri` part. Every other block reaches
7
+ * `gen_ai.input.messages` as a bare `{"type": "<block type>"}` (the fallback
8
+ * branch of its `formatting.ts`), so a PDF sent as `document_url` is stored
9
+ * under a name with no mime type, where every other provider stores the
10
+ * byte-less `{"type": "blob", "mime_type": "application/pdf"}`.
11
+ *
12
+ * The mime type it lacks is in the request the caller sent: the block's url is
13
+ * a `data:` URI holding the bytes. This complement walks the parts the tracing
14
+ * just stamped alongside the request's blocks and replaces those nameless parts
15
+ * with the same stub the other providers produce. Blocks the tracing did shape,
16
+ * and blocks whose url points at a hosted file rather than at inline bytes, are
17
+ * left alone.
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.INPUT_MESSAGES_ATTRIBUTE = void 0;
21
+ exports.repairMediaInputParts = repairMediaInputParts;
22
+ exports.setInputMessageContentTelemetry = setInputMessageContentTelemetry;
23
+ const get_data_uri_mime_type_1 = require("../../get-data-uri-mime-type");
24
+ exports.INPUT_MESSAGES_ATTRIBUTE = 'gen_ai.input.messages';
25
+ function asObject(value) {
26
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
27
+ ? value
28
+ : null;
29
+ }
30
+ /** The url of a content block, which Mistral names after the block's type:
31
+ * `{"type": "document_url", "document_url": "data:..."}`. Some blocks wrap it
32
+ * in a `{"url": ...}` object, as `image_url` does. */
33
+ function getBlockUrl(block) {
34
+ let url = block[String(block.type ?? '')];
35
+ const wrappedUrl = asObject(url);
36
+ if (wrappedUrl)
37
+ url = wrappedUrl.url;
38
+ return String(url ?? '');
39
+ }
40
+ /**
41
+ * The byte-less part for a content block carrying inline bytes, or null for a
42
+ * block that carries none.
43
+ *
44
+ * e.g. `{"type": "document_url", "document_url": "data:application/pdf;base64,AAA"}`
45
+ * -> `{"type": "blob", "mime_type": "application/pdf"}`.
46
+ */
47
+ function getMediaStub(rawBlock) {
48
+ const block = asObject(rawBlock);
49
+ if (block === null)
50
+ return null;
51
+ const mimeType = (0, get_data_uri_mime_type_1.getDataUriMimeType)(getBlockUrl(block));
52
+ if (mimeType === null)
53
+ return null;
54
+ return { type: 'blob', mime_type: mimeType };
55
+ }
56
+ /** True for the `{"type": "<block type>"}` the tracing falls back to on a
57
+ * block it does not shape: a type and nothing else. */
58
+ function isNamelessMediaPart(part) {
59
+ const object = asObject(part);
60
+ return object !== null && Object.keys(object).length === 1 && 'type' in object;
61
+ }
62
+ /**
63
+ * `gen_ai.input.messages` with every nameless media part replaced by its blob
64
+ * stub, or null when there was nothing to repair (the tracing's own JSON is
65
+ * then left untouched).
66
+ *
67
+ * A message's content blocks map one-to-one onto its parts, in order, which is
68
+ * what lets the repair walk them in step.
69
+ */
70
+ function repairMediaInputParts(inputMessagesJson, requestMessages) {
71
+ if (typeof inputMessagesJson !== 'string' || !Array.isArray(requestMessages))
72
+ return null;
73
+ let messages;
74
+ try {
75
+ messages = JSON.parse(inputMessagesJson);
76
+ }
77
+ catch {
78
+ return null;
79
+ }
80
+ if (!Array.isArray(messages))
81
+ return null;
82
+ let repaired = false;
83
+ const messageCount = Math.min(messages.length, requestMessages.length);
84
+ for (let messageIndex = 0; messageIndex < messageCount; messageIndex++) {
85
+ const parts = asObject(messages[messageIndex])?.parts;
86
+ const content = asObject(requestMessages[messageIndex])?.content;
87
+ if (!Array.isArray(parts) || !Array.isArray(content))
88
+ continue;
89
+ content.forEach((block, index) => {
90
+ if (index >= parts.length || !isNamelessMediaPart(parts[index]))
91
+ return;
92
+ const mediaStub = getMediaStub(block);
93
+ if (mediaStub !== null) {
94
+ parts[index] = mediaStub;
95
+ repaired = true;
96
+ }
97
+ });
98
+ }
99
+ return repaired ? JSON.stringify(messages) : null;
100
+ }
101
+ /**
102
+ * Re-stamp `gen_ai.input.messages` when the request carries inline media.
103
+ *
104
+ * Only ever rewrites an attribute the tracing already set, so it never adds
105
+ * message content the tracing did not record.
106
+ */
107
+ function setInputMessageContentTelemetry(span, requestBody) {
108
+ const attributes = span.attributes ?? {};
109
+ const repairedMessages = repairMediaInputParts(attributes[exports.INPUT_MESSAGES_ATTRIBUTE], requestBody.messages);
110
+ if (repairedMessages !== null) {
111
+ span.setAttribute(exports.INPUT_MESSAGES_ATTRIBUTE, repairedMessages);
112
+ }
113
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Structured-output schema capture for Mistral spans.
3
+ *
4
+ * Mistral pins an answer to a JSON schema with
5
+ * `response_format: { type: 'json_schema', json_schema: { schema: {...} } }`.
6
+ * The SDK's built-in tracing enriches the span with the request's sampling
7
+ * params, messages and tool definitions, but never the schema. The request
8
+ * body read here is the one sent on the wire, so its keys are snake_case
9
+ * whatever the caller wrote.
10
+ */
11
+ /** The schema under `response_format.json_schema.schema`, or undefined when
12
+ * the request asked for no structured output. */
13
+ export declare function extractStructuredOutputSchema(requestBody: Record<string, unknown>): unknown;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ /**
3
+ * Structured-output schema capture for Mistral spans.
4
+ *
5
+ * Mistral pins an answer to a JSON schema with
6
+ * `response_format: { type: 'json_schema', json_schema: { schema: {...} } }`.
7
+ * The SDK's built-in tracing enriches the span with the request's sampling
8
+ * params, messages and tool definitions, but never the schema. The request
9
+ * body read here is the one sent on the wire, so its keys are snake_case
10
+ * whatever the caller wrote.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.extractStructuredOutputSchema = extractStructuredOutputSchema;
14
+ /** The schema under `response_format.json_schema.schema`, or undefined when
15
+ * the request asked for no structured output. */
16
+ function extractStructuredOutputSchema(requestBody) {
17
+ const responseFormat = requestBody.response_format;
18
+ if (typeof responseFormat !== 'object' || responseFormat === null)
19
+ return undefined;
20
+ const jsonSchema = responseFormat.json_schema;
21
+ if (typeof jsonSchema !== 'object' || jsonSchema === null)
22
+ return undefined;
23
+ return jsonSchema.schema;
24
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Tracing for Mistral's models called on Vertex AI through `MistralGCP`.
3
+ *
4
+ * `@mistralai/mistralai-gcp` is Mistral's client for its models on Vertex AI
5
+ * (`rawPredict` / `streamRawPredict`). Unlike `@mistralai/mistralai`, it
6
+ * registers no `TracingHook`, so the SDK's built-in tracing that
7
+ * `MistralInstrumentor` opts into never sees its calls. This registers that
8
+ * same hook, from `@mistralai/mistralai`, on every `MistralGCP` client: its
9
+ * `SDKHooks` runs `sdkInit` from the client's constructor, where the hook is
10
+ * registered before the original runs, so the tracing hook's own `sdkInit`
11
+ * wraps the client's HTTP client too. The client's sub-clients (`chat`, `fim`)
12
+ * run `sdkInit` again on the same `SDKHooks`, which then registers nothing.
13
+ *
14
+ * Next to it, a hook of its own stamps two things the tracing hook cannot know:
15
+ * - `weflayr.billing_provider` set to `vertex`, which the worker prices by
16
+ * (Google bills these calls at Vertex's prices), while `gen_ai.provider.name`,
17
+ * which it parses by, stays Mistral's;
18
+ * - the failure of a call Vertex refuses: its error body is Google's
19
+ * (`{"error": {"code": 404, "message": ..., "status": "NOT_FOUND"}}`), not the
20
+ * `{"object": "error"}` one the tracing hook recognizes, so it would otherwise
21
+ * end the span as a success.
22
+ */
23
+ export declare const MISTRAL_GCP_HOOKS_MODULE = "@mistralai/mistralai-gcp/hooks/hooks.js";
24
+ export type LoadModule = (specifier: string) => Promise<any>;
25
+ /** Stamps the billing provider when the request is sent, and the failure when
26
+ * Vertex refuses it, on the span the tracing hook keeps on the hook context. */
27
+ export declare class VertexHook {
28
+ private readonly tracingSpanKey;
29
+ constructor(tracingSpanKey: string);
30
+ beforeRequest(hookContext: any, request: Request): Request;
31
+ afterError(hookContext: any, response: Response | null, error: unknown): Promise<{
32
+ response: Response | null;
33
+ error: unknown;
34
+ }>;
35
+ }
36
+ /**
37
+ * The message and the type of the error Vertex answered with. Its body is
38
+ * Google's error object, alone or in a one-item list, or plain text.
39
+ *
40
+ * Example: a 404 whose body is `{"error": {"code": 404, "message": "Not
41
+ * found.", "status": "NOT_FOUND"}}` gives `["Not found.", "NOT_FOUND"]`, a 400
42
+ * whose body is `reasoning_effort is not supported` gives
43
+ * `["reasoning_effort is not supported", null]`.
44
+ */
45
+ export declare function readVertexError(response: Response): Promise<[string, string | null]>;
46
+ /** Register the tracing hook, and the Vertex one, on every `MistralGCP`. */
47
+ export declare class MistralVertexInstrumentor {
48
+ private readonly loadModule;
49
+ private _sdkHooksClass;
50
+ private _originalSdkInit;
51
+ constructor(loadModule: LoadModule);
52
+ instrument(tracingModule: any): Promise<void>;
53
+ uninstrument(): void;
54
+ }
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+ /**
3
+ * Tracing for Mistral's models called on Vertex AI through `MistralGCP`.
4
+ *
5
+ * `@mistralai/mistralai-gcp` is Mistral's client for its models on Vertex AI
6
+ * (`rawPredict` / `streamRawPredict`). Unlike `@mistralai/mistralai`, it
7
+ * registers no `TracingHook`, so the SDK's built-in tracing that
8
+ * `MistralInstrumentor` opts into never sees its calls. This registers that
9
+ * same hook, from `@mistralai/mistralai`, on every `MistralGCP` client: its
10
+ * `SDKHooks` runs `sdkInit` from the client's constructor, where the hook is
11
+ * registered before the original runs, so the tracing hook's own `sdkInit`
12
+ * wraps the client's HTTP client too. The client's sub-clients (`chat`, `fim`)
13
+ * run `sdkInit` again on the same `SDKHooks`, which then registers nothing.
14
+ *
15
+ * Next to it, a hook of its own stamps two things the tracing hook cannot know:
16
+ * - `weflayr.billing_provider` set to `vertex`, which the worker prices by
17
+ * (Google bills these calls at Vertex's prices), while `gen_ai.provider.name`,
18
+ * which it parses by, stays Mistral's;
19
+ * - the failure of a call Vertex refuses: its error body is Google's
20
+ * (`{"error": {"code": 404, "message": ..., "status": "NOT_FOUND"}}`), not the
21
+ * `{"object": "error"}` one the tracing hook recognizes, so it would otherwise
22
+ * end the span as a success.
23
+ */
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.MistralVertexInstrumentor = exports.VertexHook = exports.MISTRAL_GCP_HOOKS_MODULE = void 0;
26
+ exports.readVertexError = readVertexError;
27
+ const api_1 = require("@opentelemetry/api");
28
+ const logger_1 = require("../../logger");
29
+ const is_vertex_base_url_1 = require("../shared/is_vertex_base_url");
30
+ exports.MISTRAL_GCP_HOOKS_MODULE = '@mistralai/mistralai-gcp/hooks/hooks.js';
31
+ const ERROR_TYPE_ATTR = 'error.type';
32
+ const FIRST_ERROR_STATUS_CODE = 400;
33
+ // The codes Node throws for a package that is not installed, from require and
34
+ // from import.
35
+ const MODULE_NOT_FOUND_CODES = new Set(['MODULE_NOT_FOUND', 'ERR_MODULE_NOT_FOUND']);
36
+ /** Stamps the billing provider when the request is sent, and the failure when
37
+ * Vertex refuses it, on the span the tracing hook keeps on the hook context. */
38
+ class VertexHook {
39
+ tracingSpanKey;
40
+ constructor(tracingSpanKey) {
41
+ this.tracingSpanKey = tracingSpanKey;
42
+ }
43
+ beforeRequest(hookContext, request) {
44
+ // Never break the user's Mistral call over an enrichment failure.
45
+ try {
46
+ const span = hookContext?.[this.tracingSpanKey];
47
+ if (span?.isRecording() && (0, is_vertex_base_url_1.isVertexBaseURL)(request.url)) {
48
+ span.setAttribute(is_vertex_base_url_1.BILLING_PROVIDER_ATTR, is_vertex_base_url_1.VERTEX_BILLING_PROVIDER);
49
+ }
50
+ }
51
+ catch (err) {
52
+ (0, logger_1.debug)('mistral vertex: billing provider not stamped: %s', err?.message || err);
53
+ }
54
+ return request;
55
+ }
56
+ async afterError(hookContext, response, error) {
57
+ try {
58
+ await recordVertexError(hookContext?.[this.tracingSpanKey], response);
59
+ }
60
+ catch (err) {
61
+ (0, logger_1.debug)('mistral vertex: error not recorded: %s', err?.message || err);
62
+ }
63
+ return { response, error };
64
+ }
65
+ }
66
+ exports.VertexHook = VertexHook;
67
+ async function recordVertexError(span, response) {
68
+ if (!span?.isRecording() || !response || response.status < FIRST_ERROR_STATUS_CODE)
69
+ return;
70
+ const [errorMessage, errorType] = await readVertexError(response);
71
+ span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: errorMessage });
72
+ if (errorType)
73
+ span.setAttribute(ERROR_TYPE_ATTR, errorType);
74
+ }
75
+ /**
76
+ * The message and the type of the error Vertex answered with. Its body is
77
+ * Google's error object, alone or in a one-item list, or plain text.
78
+ *
79
+ * Example: a 404 whose body is `{"error": {"code": 404, "message": "Not
80
+ * found.", "status": "NOT_FOUND"}}` gives `["Not found.", "NOT_FOUND"]`, a 400
81
+ * whose body is `reasoning_effort is not supported` gives
82
+ * `["reasoning_effort is not supported", null]`.
83
+ */
84
+ async function readVertexError(response) {
85
+ const text = await response.clone().text();
86
+ let body;
87
+ try {
88
+ body = JSON.parse(text);
89
+ }
90
+ catch {
91
+ body = undefined;
92
+ }
93
+ if (Array.isArray(body) && body.length > 0)
94
+ body = body[0];
95
+ const googleError = typeof body === 'object' && body !== null ? body.error : undefined;
96
+ if (typeof googleError !== 'object' || googleError === null) {
97
+ return [text || `HTTP ${response.status}`, null];
98
+ }
99
+ const { message, status } = googleError;
100
+ return [String(message || `HTTP ${response.status}`), status ? String(status) : null];
101
+ }
102
+ /** Register the tracing hook, and the Vertex one, on every `MistralGCP`. */
103
+ class MistralVertexInstrumentor {
104
+ loadModule;
105
+ _sdkHooksClass;
106
+ _originalSdkInit;
107
+ constructor(loadModule) {
108
+ this.loadModule = loadModule;
109
+ }
110
+ async instrument(tracingModule) {
111
+ let hooksModule;
112
+ try {
113
+ hooksModule = await this.loadModule(exports.MISTRAL_GCP_HOOKS_MODULE);
114
+ }
115
+ catch (err) {
116
+ // Calls to Vertex need @mistralai/mistralai-gcp, a package of its own.
117
+ if (MODULE_NOT_FOUND_CODES.has(err?.code ?? ''))
118
+ return;
119
+ throw err;
120
+ }
121
+ if (this._originalSdkInit)
122
+ return;
123
+ const { TracingHook, TRACING_SPAN_KEY } = tracingModule;
124
+ const sdkHooksClass = hooksModule.SDKHooks;
125
+ const originalSdkInit = sdkHooksClass.prototype.sdkInit;
126
+ const instrumentedHooks = new WeakSet();
127
+ sdkHooksClass.prototype.sdkInit = function sdkInitWithTracing(...args) {
128
+ if (!instrumentedHooks.has(this)) {
129
+ instrumentedHooks.add(this);
130
+ const tracingHook = new TracingHook();
131
+ const vertexHook = new VertexHook(TRACING_SPAN_KEY);
132
+ this.registerSDKInitHook(tracingHook);
133
+ this.registerBeforeRequestHook(tracingHook);
134
+ this.registerBeforeRequestHook(vertexHook);
135
+ this.registerAfterSuccessHook(tracingHook);
136
+ // Ahead of the tracing hook, which ends the span.
137
+ this.registerAfterErrorHook(vertexHook);
138
+ this.registerAfterErrorHook(tracingHook);
139
+ }
140
+ return originalSdkInit.apply(this, args);
141
+ };
142
+ this._sdkHooksClass = sdkHooksClass;
143
+ this._originalSdkInit = originalSdkInit;
144
+ }
145
+ uninstrument() {
146
+ if (!this._originalSdkInit)
147
+ return;
148
+ this._sdkHooksClass.prototype.sdkInit = this._originalSdkInit;
149
+ this._sdkHooksClass = undefined;
150
+ this._originalSdkInit = undefined;
151
+ }
152
+ }
153
+ exports.MistralVertexInstrumentor = MistralVertexInstrumentor;
@@ -15,6 +15,11 @@
15
15
  * OpenLLMetry instrumentor passes the client correctly, so this gap is
16
16
  * Node-only.
17
17
  *
18
+ * - **The region is missing.** AWS bills a Bedrock call at the price of the
19
+ * region it is sent to, and on Mantle the host names it, so it is read off
20
+ * the client's base URL. A call carrying no region is priced by no row at
21
+ * all.
22
+ *
18
23
  * - **The chain of thought is dropped.** Mantle's open-weight models (gpt-oss,
19
24
  * qwen, ...) return their reasoning under `reasoning`, which neither the
20
25
  * output-message builder nor the stream accumulator knows about. Without it a
@@ -25,6 +30,11 @@
25
30
  export declare class OpenAIBedrockMantleInstrumentor {
26
31
  private _patched;
27
32
  instrument(instrumentation: any): void;
33
+ /**
34
+ * Stamp the AWS region Mantle's host names, the region AWS bills the call at.
35
+ * A client reached at another host names no region.
36
+ */
37
+ private _patchProviderRegion;
28
38
  /** Hand the vendor detection the real client, not the resource it is called with. */
29
39
  private _patchVendorDetection;
30
40
  /** Accumulate `delta.reasoning` across the streamed chunks. */
@@ -16,6 +16,11 @@
16
16
  * OpenLLMetry instrumentor passes the client correctly, so this gap is
17
17
  * Node-only.
18
18
  *
19
+ * - **The region is missing.** AWS bills a Bedrock call at the price of the
20
+ * region it is sent to, and on Mantle the host names it, so it is read off
21
+ * the client's base URL. A call carrying no region is priced by no row at
22
+ * all.
23
+ *
19
24
  * - **The chain of thought is dropped.** Mantle's open-weight models (gpt-oss,
20
25
  * qwen, ...) return their reasoning under `reasoning`, which neither the
21
26
  * output-message builder nor the stream accumulator knows about. Without it a
@@ -26,6 +31,9 @@
26
31
  Object.defineProperty(exports, "__esModule", { value: true });
27
32
  exports.OpenAIBedrockMantleInstrumentor = void 0;
28
33
  const OUTPUT_MESSAGES_ATTR = 'gen_ai.output.messages';
34
+ const PROVIDER_REGION_ATTR = 'weflayr.provider_region';
35
+ // Mantle's host names the AWS region it serves from.
36
+ const MANTLE_HOST_RE = /bedrock-mantle\.([a-z0-9-]+)\.api\.aws/i;
29
37
  // Reasoning streamed so far, per open span. Streamed deltas arrive while the
30
38
  // stream is consumed, but the output message is only built when the span ends,
31
39
  // so the two hooks below meet here.
@@ -72,9 +80,34 @@ class OpenAIBedrockMantleInstrumentor {
72
80
  if (!instrumentation)
73
81
  return;
74
82
  this._patchVendorDetection(instrumentation);
83
+ this._patchProviderRegion(instrumentation);
75
84
  this._patchStreamedReasoning(instrumentation);
76
85
  this._patchOutputMessages(instrumentation);
77
86
  }
87
+ /**
88
+ * Stamp the AWS region Mantle's host names, the region AWS bills the call at.
89
+ * A client reached at another host names no region.
90
+ */
91
+ _patchProviderRegion(instrumentation) {
92
+ const original = instrumentation.startSpan;
93
+ if (typeof original !== 'function')
94
+ return;
95
+ instrumentation.startSpan = function startSpanWithProviderRegion(request) {
96
+ const span = original.call(this, request);
97
+ try {
98
+ const client = request && request.client;
99
+ const resolved = client && !client.baseURL && client._client ? client._client : client;
100
+ const hostMatch = MANTLE_HOST_RE.exec(String((resolved && resolved.baseURL) || ''));
101
+ if (hostMatch)
102
+ span.setAttribute(PROVIDER_REGION_ATTR, hostMatch[1]);
103
+ }
104
+ catch {
105
+ // Never break span creation over an enrichment failure.
106
+ }
107
+ return span;
108
+ };
109
+ this._patched.push([instrumentation, 'startSpan', original]);
110
+ }
78
111
  /** Hand the vendor detection the real client, not the resource it is called with. */
79
112
  _patchVendorDetection(instrumentation) {
80
113
  const original = instrumentation._detectVendorFromURL;
@@ -7,18 +7,25 @@
7
7
  * cached-token count. The Python OpenLLMetry instrumentor sets
8
8
  * `gen_ai.usage.cache_read_input_tokens` from the former, so this brings the Node
9
9
  * SDK to parity; the latter (GPT-5.6+'s cache-write count) has no OpenLLMetry
10
- * equivalent on either side, so this is its only source.
10
+ * equivalent on either side, so this is its only source. Alibaba's explicit
11
+ * context cache (a `cache_control: {type: "ephemeral"}` marker on a message
12
+ * content block, confirmed against a real workspace on 2026-09-21) reports the
13
+ * same fact under the Anthropic-shaped `.cache_creation_input_tokens` key
14
+ * instead, read here too.
11
15
  *
12
16
  * It wraps the instrumentor instance's `_endSpan({ span, type, result })`, which
13
17
  * finalizes the span after the response resolves, letting us stamp the
14
18
  * cached-token counts off `result.usage` while the span is still open.
15
- * `cache_write_tokens` is stamped as `gen_ai.usage.cache_creation_input_tokens`,
16
- * the attribute name the normalizer already reads for Anthropic/Bedrock/Vercel.
19
+ * Either cache-write key is stamped as `gen_ai.usage.cache_creation_input_tokens`,
20
+ * the attribute name the normalizer already reads for Anthropic/Bedrock.
17
21
  *
18
22
  * The same hook surfaces the reasoning-token count: reasoning models report it as
19
- * `usage.completion_tokens_details.reasoning_tokens` (a subset of output_tokens)
20
- * which the Node instrumentor drops, so this stamps the flat `reasoning_tokens`
21
- * attribute (the form the Python instrumentor emits and the normalizer reads).
23
+ * `usage.completion_tokens_details.reasoning_tokens`, which the Node instrumentor
24
+ * drops, so this stamps the flat `reasoning_tokens` attribute (the form the Python
25
+ * instrumentor emits and the normalizer reads). OpenAI counts those tokens inside
26
+ * `completion_tokens`, xAI counts them outside it (see the worker's
27
+ * `normalizers/spacexai.py`); either way what is stamped here is the raw count the
28
+ * response reported, and the normalizer decides what to add it to.
22
29
  */
23
30
  export declare class OpenAIPromptCachingInstrumentor {
24
31
  private _patched;
@@ -8,18 +8,25 @@
8
8
  * cached-token count. The Python OpenLLMetry instrumentor sets
9
9
  * `gen_ai.usage.cache_read_input_tokens` from the former, so this brings the Node
10
10
  * SDK to parity; the latter (GPT-5.6+'s cache-write count) has no OpenLLMetry
11
- * equivalent on either side, so this is its only source.
11
+ * equivalent on either side, so this is its only source. Alibaba's explicit
12
+ * context cache (a `cache_control: {type: "ephemeral"}` marker on a message
13
+ * content block, confirmed against a real workspace on 2026-09-21) reports the
14
+ * same fact under the Anthropic-shaped `.cache_creation_input_tokens` key
15
+ * instead, read here too.
12
16
  *
13
17
  * It wraps the instrumentor instance's `_endSpan({ span, type, result })`, which
14
18
  * finalizes the span after the response resolves, letting us stamp the
15
19
  * cached-token counts off `result.usage` while the span is still open.
16
- * `cache_write_tokens` is stamped as `gen_ai.usage.cache_creation_input_tokens`,
17
- * the attribute name the normalizer already reads for Anthropic/Bedrock/Vercel.
20
+ * Either cache-write key is stamped as `gen_ai.usage.cache_creation_input_tokens`,
21
+ * the attribute name the normalizer already reads for Anthropic/Bedrock.
18
22
  *
19
23
  * The same hook surfaces the reasoning-token count: reasoning models report it as
20
- * `usage.completion_tokens_details.reasoning_tokens` (a subset of output_tokens)
21
- * which the Node instrumentor drops, so this stamps the flat `reasoning_tokens`
22
- * attribute (the form the Python instrumentor emits and the normalizer reads).
24
+ * `usage.completion_tokens_details.reasoning_tokens`, which the Node instrumentor
25
+ * drops, so this stamps the flat `reasoning_tokens` attribute (the form the Python
26
+ * instrumentor emits and the normalizer reads). OpenAI counts those tokens inside
27
+ * `completion_tokens`, xAI counts them outside it (see the worker's
28
+ * `normalizers/spacexai.py`); either way what is stamped here is the raw count the
29
+ * response reported, and the normalizer decides what to add it to.
23
30
  */
24
31
  Object.defineProperty(exports, "__esModule", { value: true });
25
32
  exports.OpenAIPromptCachingInstrumentor = void 0;
@@ -44,7 +51,10 @@ class OpenAIPromptCachingInstrumentor {
44
51
  if (cachedTokens && recording) {
45
52
  span.setAttribute(CACHE_READ_INPUT_TOKENS_ATTR, cachedTokens);
46
53
  }
47
- const cacheWriteTokens = usage && usage.prompt_tokens_details && usage.prompt_tokens_details.cache_write_tokens;
54
+ const cacheWriteTokens = usage &&
55
+ usage.prompt_tokens_details &&
56
+ (usage.prompt_tokens_details.cache_write_tokens ||
57
+ usage.prompt_tokens_details.cache_creation_input_tokens);
48
58
  if (cacheWriteTokens && recording) {
49
59
  span.setAttribute(CACHE_CREATION_INPUT_TOKENS_ATTR, cacheWriteTokens);
50
60
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Request-side prompt-caching config capture for OpenAI chat spans.
2
+ * Request-side param capture for OpenAI chat and Responses spans.
3
3
  *
4
4
  * OpenAI callers influence the (automatic, server-side) prompt cache with
5
5
  * request parameters the traceloop instrumentor never records:
@@ -11,15 +11,32 @@
11
11
  * message content block (`prompt_cache_options: { mode: "explicit" }`
12
12
  * disables the automatic breakpoint, requiring these to cache anything).
13
13
  *
14
+ * It covers the chat and the Responses endpoints alike: both take the same
15
+ * params, and a marker sits on a content block of either `messages` (chat) or
16
+ * `input` (Responses), recorded as `messages[i]` / `input[i]`. A Responses
17
+ * request's `instructions` count as its item 0 when it sends them, as the
18
+ * worker numbers every provider's system prompt.
19
+ *
14
20
  * Wrap `startSpan({ type, params })` (covering the streamed and non-streamed
15
21
  * paths) to stamp the values as `weflayr.cache.key` / `weflayr.cache.retention`
16
22
  * (raw retention/ttl string) and, when any content block carries the marker,
17
- * `weflayr.cache.breakpoints` (a JSON list of `{"location": "messages[i]"}`
18
- * entries — the marker itself carries no TTL, unlike Anthropic's
19
- * `cache_control`; the normalizer owns the vocabulary).
23
+ * `weflayr.cache.breakpoints`: a JSON list of `{"location": "messages[i]"}` /
24
+ * `{"location": "input[i]"}` entries for OpenAI's own `prompt_cache_breakpoint`
25
+ * marker (which carries no TTL of its own; the normalizer falls back to the
26
+ * request-level retention and owns the vocabulary) or, for a content block
27
+ * carrying Anthropic's `cache_control` convention instead (Alibaba (Qwen) is
28
+ * reached through this same openai-shape client and opts into its *explicit*
29
+ * cache this way, unlike real OpenAI), `{"location": ..., "ttl": ...}`, the raw
30
+ * `ttl` string carried on the marker itself, omitted when it sets none.
20
31
  * `prompt_cache_options: { mode: "explicit" }` is stamped as
21
32
  * `weflayr.cache.mode` so the normalizer knows not to also synthesize the
22
33
  * automatic "auto"-location breakpoint OpenAI does not create in this mode.
34
+ *
35
+ * The same wrapper stamps the output-token limit when the caller sent it as
36
+ * `max_completion_tokens`: the traceloop instrumentor reads only `max_tokens`,
37
+ * which the reasoning models (o-series, GPT-5 and later) reject, so their calls
38
+ * would record no limit at all. It lives here because this is the one place the
39
+ * request's own params are read.
23
40
  */
24
41
  export declare class OpenAICacheConfigInstrumentor {
25
42
  private _patched;