weflayr 0.23.0 → 0.26.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.
- package/README.md +2 -2
- package/dist/{api → src/api}/generated/facade.gen.d.ts +163 -12
- package/dist/{api → src/api}/generated/facade.gen.js +10 -2
- package/dist/src/api/generated/index.d.ts +2 -0
- package/dist/{api → src/api}/generated/index.js +9 -1
- package/dist/{api → src/api}/generated/sdk.gen.d.ts +35 -3
- package/dist/{api → src/api}/generated/sdk.gen.js +88 -4
- package/{src/api/generated/types.gen.ts → dist/src/api/generated/types.gen.d.ts} +692 -116
- package/dist/{api → src/api}/makeEndpoint.js +1 -3
- package/dist/src/auto-instrument.d.ts +42 -0
- package/dist/src/auto-instrument.js +156 -0
- package/dist/src/index.d.ts +9 -0
- package/dist/src/index.js +49 -0
- package/dist/src/instrumentation/anthropic/get_cache_and_reasoning_telemetry.d.ts +20 -0
- package/dist/src/instrumentation/anthropic/get_cache_and_reasoning_telemetry.js +114 -0
- package/dist/src/instrumentation/anthropic/get_cache_config_telemetry.d.ts +28 -0
- package/dist/src/instrumentation/anthropic/get_cache_config_telemetry.js +92 -0
- package/dist/src/instrumentation/anthropic/get_streaming_accumulation_telemetry.d.ts +25 -0
- package/dist/src/instrumentation/anthropic/get_streaming_accumulation_telemetry.js +141 -0
- package/dist/src/instrumentation/anthropic/get_structured_output_schema_telemetry.d.ts +9 -0
- package/dist/src/instrumentation/anthropic/get_structured_output_schema_telemetry.js +17 -0
- package/dist/src/instrumentation/anthropic/get_tool_definitions_telemetry.d.ts +15 -0
- package/dist/src/instrumentation/anthropic/get_tool_definitions_telemetry.js +50 -0
- package/dist/src/instrumentation/anthropic/set_telemetry_attributes.d.ts +15 -0
- package/dist/src/instrumentation/anthropic/set_telemetry_attributes.js +39 -0
- package/dist/src/instrumentation/bedrock/get_cache_config_telemetry.d.ts +22 -0
- package/dist/src/instrumentation/bedrock/get_cache_config_telemetry.js +97 -0
- package/dist/src/instrumentation/bedrock/get_cache_telemetry.d.ts +22 -0
- package/dist/src/instrumentation/bedrock/get_cache_telemetry.js +102 -0
- package/dist/src/instrumentation/bedrock/get_input_message_content_telemetry.d.ts +15 -0
- package/dist/src/instrumentation/bedrock/get_input_message_content_telemetry.js +91 -0
- package/dist/src/instrumentation/bedrock/get_model_id_telemetry.d.ts +21 -0
- package/dist/src/instrumentation/bedrock/get_model_id_telemetry.js +54 -0
- package/dist/src/instrumentation/bedrock/get_reasoning_output_telemetry.d.ts +19 -0
- package/dist/src/instrumentation/bedrock/get_reasoning_output_telemetry.js +81 -0
- package/dist/src/instrumentation/bedrock/get_streaming_output_message_telemetry.d.ts +27 -0
- package/dist/src/instrumentation/bedrock/get_streaming_output_message_telemetry.js +126 -0
- package/dist/src/instrumentation/bedrock/get_tool_call_output_telemetry.d.ts +17 -0
- package/dist/src/instrumentation/bedrock/get_tool_call_output_telemetry.js +85 -0
- package/dist/src/instrumentation/bedrock/get_tool_definitions_telemetry.d.ts +15 -0
- package/dist/src/instrumentation/bedrock/get_tool_definitions_telemetry.js +68 -0
- package/dist/src/instrumentation/bedrock/set_telemetry_attributes.d.ts +18 -0
- package/dist/src/instrumentation/bedrock/set_telemetry_attributes.js +47 -0
- package/dist/src/instrumentation/cohere/get_chat_telemetry.d.ts +23 -0
- package/dist/src/instrumentation/cohere/get_chat_telemetry.js +77 -0
- package/dist/src/instrumentation/cohere/get_embeddings_telemetry.d.ts +18 -0
- package/dist/src/instrumentation/cohere/get_embeddings_telemetry.js +88 -0
- package/dist/src/instrumentation/cohere/get_structured_output_schema_telemetry.d.ts +12 -0
- package/dist/src/instrumentation/cohere/get_structured_output_schema_telemetry.js +18 -0
- package/dist/src/instrumentation/cohere/get_tool_call_output_telemetry.d.ts +31 -0
- package/dist/src/instrumentation/cohere/get_tool_call_output_telemetry.js +106 -0
- package/dist/src/instrumentation/cohere/get_tool_definitions_telemetry.d.ts +27 -0
- package/dist/src/instrumentation/cohere/get_tool_definitions_telemetry.js +75 -0
- package/dist/src/instrumentation/cohere/set_telemetry_attributes.d.ts +17 -0
- package/dist/src/instrumentation/cohere/set_telemetry_attributes.js +44 -0
- package/dist/src/instrumentation/elevenlabs/full_instrumentation.d.ts +28 -0
- package/dist/src/instrumentation/elevenlabs/full_instrumentation.js +209 -0
- package/dist/src/instrumentation/google/get_audio_telemetry.d.ts +9 -0
- package/dist/src/instrumentation/google/get_audio_telemetry.js +28 -0
- package/dist/src/instrumentation/google/get_cache_operations_telemetry.d.ts +23 -0
- package/dist/src/instrumentation/google/get_cache_operations_telemetry.js +154 -0
- package/dist/src/instrumentation/google/get_cache_telemetry.d.ts +16 -0
- package/dist/src/instrumentation/google/get_cache_telemetry.js +38 -0
- package/dist/src/instrumentation/google/get_embeddings_telemetry.d.ts +12 -0
- package/dist/src/instrumentation/google/get_embeddings_telemetry.js +85 -0
- package/dist/src/instrumentation/google/get_provider_name.d.ts +11 -0
- package/{src → dist/src}/instrumentation/google/get_provider_name.js +11 -12
- package/{src/instrumentation/google/get_reasoning_telemetry.js → dist/src/instrumentation/google/get_reasoning_telemetry.d.ts} +1 -11
- package/dist/src/instrumentation/google/get_reasoning_telemetry.js +21 -0
- package/dist/src/instrumentation/google/get_structured_output_schema_telemetry.d.ts +12 -0
- package/dist/src/instrumentation/google/get_structured_output_schema_telemetry.js +19 -0
- package/dist/src/instrumentation/google/set_telemetry_attributes.d.ts +18 -0
- package/dist/src/instrumentation/google/set_telemetry_attributes.js +98 -0
- package/dist/src/instrumentation/openai/get_audio_telemetry.d.ts +25 -0
- package/dist/src/instrumentation/openai/get_audio_telemetry.js +123 -0
- package/dist/src/instrumentation/openai/get_bedrock_mantle_telemetry.d.ts +35 -0
- package/dist/src/instrumentation/openai/get_bedrock_mantle_telemetry.js +148 -0
- package/dist/src/instrumentation/openai/get_cache_and_reasoning_telemetry.d.ts +27 -0
- package/dist/src/instrumentation/openai/get_cache_and_reasoning_telemetry.js +72 -0
- package/dist/src/instrumentation/openai/get_cache_config_telemetry.d.ts +28 -0
- package/dist/src/instrumentation/openai/get_cache_config_telemetry.js +91 -0
- package/dist/src/instrumentation/openai/get_embeddings_telemetry.d.ts +18 -0
- package/dist/src/instrumentation/openai/get_embeddings_telemetry.js +86 -0
- package/dist/src/instrumentation/openai/get_streaming_usage.d.ts +21 -0
- package/dist/src/instrumentation/openai/get_streaming_usage.js +74 -0
- package/dist/src/instrumentation/openai/get_structured_output_schema_telemetry.d.ts +12 -0
- package/dist/src/instrumentation/openai/get_structured_output_schema_telemetry.js +20 -0
- package/dist/src/instrumentation/openai/set_telemetry_attributes.d.ts +17 -0
- package/dist/src/instrumentation/openai/set_telemetry_attributes.js +47 -0
- package/dist/src/instrumentation/shared/get_structured_output_schema_telemetry.d.ts +35 -0
- package/dist/src/instrumentation/shared/get_structured_output_schema_telemetry.js +76 -0
- package/dist/src/instrumentation/vercel/full_instrumentation.d.ts +3 -0
- package/dist/src/instrumentation/vercel/full_instrumentation.js +54 -0
- package/dist/src/instrumentation/vercel/get_cache_creation_telemetry.d.ts +22 -0
- package/dist/src/instrumentation/vercel/get_cache_creation_telemetry.js +66 -0
- package/dist/src/instrumentation/vercel/get_structured_output_schema_telemetry.d.ts +24 -0
- package/dist/src/instrumentation/vercel/get_structured_output_schema_telemetry.js +84 -0
- package/dist/src/logger.d.ts +1 -0
- package/dist/src/logger.js +6 -0
- package/dist/src/otel/exporter.d.ts +19 -0
- package/dist/src/otel/exporter.js +94 -0
- package/dist/src/otel/propagation.d.ts +46 -0
- package/dist/src/otel/propagation.js +110 -0
- package/dist/src/otel/span-filter.d.ts +3 -0
- package/dist/src/otel/span-filter.js +61 -0
- package/dist/src/otel/span-processor.d.ts +55 -0
- package/dist/src/otel/span-processor.js +169 -0
- package/dist/src/otel/vercel-ai-sdk-span-filter.d.ts +12 -0
- package/dist/src/otel/vercel-ai-sdk-span-filter.js +88 -0
- package/dist/src/setup.d.ts +50 -0
- package/dist/src/setup.js +155 -0
- package/package.json +11 -5
- package/.prettierignore +0 -2
- package/.prettierrc.json +0 -7
- package/LICENSE-MIT +0 -21
- package/NOTICE +0 -12
- package/dist/api/generated/index.d.ts +0 -2
- package/dist/api/generated/types.gen.d.ts +0 -749
- package/eslint.config.mjs +0 -36
- package/index.d.ts +0 -167
- package/openapi-ts.config.mjs +0 -9
- package/scripts/generate_facade.mjs +0 -86
- package/src/api/caseConversion.ts +0 -58
- package/src/api/generated/client/client.gen.ts +0 -277
- package/src/api/generated/client/index.ts +0 -27
- package/src/api/generated/client/types.gen.ts +0 -218
- package/src/api/generated/client/utils.gen.ts +0 -316
- package/src/api/generated/client.gen.ts +0 -16
- package/src/api/generated/core/auth.gen.ts +0 -48
- package/src/api/generated/core/bodySerializer.gen.ts +0 -82
- package/src/api/generated/core/params.gen.ts +0 -178
- package/src/api/generated/core/pathSerializer.gen.ts +0 -171
- package/src/api/generated/core/queryKeySerializer.gen.ts +0 -117
- package/src/api/generated/core/serverSentEvents.gen.ts +0 -242
- package/src/api/generated/core/types.gen.ts +0 -110
- package/src/api/generated/core/utils.gen.ts +0 -140
- package/src/api/generated/facade.gen.ts +0 -68
- package/src/api/generated/index.ts +0 -4
- package/src/api/generated/sdk.gen.ts +0 -121
- package/src/api/index.ts +0 -45
- package/src/api/makeEndpoint.ts +0 -54
- package/src/auto-instrument.js +0 -171
- package/src/index.js +0 -19
- package/src/instrumentation/anthropic/get_cache_and_reasoning_telemetry.js +0 -107
- package/src/instrumentation/anthropic/get_streaming_stop_reason_telemetry.js +0 -121
- package/src/instrumentation/anthropic/get_tool_definitions_telemetry.js +0 -50
- package/src/instrumentation/anthropic/set_telemetry_attributes.js +0 -37
- package/src/instrumentation/bedrock/get_cache_telemetry.js +0 -104
- package/src/instrumentation/bedrock/get_input_message_content_telemetry.js +0 -94
- package/src/instrumentation/bedrock/get_streaming_output_message_telemetry.js +0 -87
- package/src/instrumentation/bedrock/set_telemetry_attributes.js +0 -36
- package/src/instrumentation/cohere/get_chat_telemetry.js +0 -88
- package/src/instrumentation/cohere/get_embeddings_telemetry.js +0 -107
- package/src/instrumentation/cohere/set_telemetry_attributes.js +0 -32
- package/src/instrumentation/elevenlabs/full_instrumentation.js +0 -236
- package/src/instrumentation/google/get_audio_telemetry.js +0 -30
- package/src/instrumentation/google/get_cache_telemetry.js +0 -25
- package/src/instrumentation/google/get_embeddings_telemetry.js +0 -99
- package/src/instrumentation/google/set_telemetry_attributes.js +0 -98
- package/src/instrumentation/openai/get_audio_telemetry.js +0 -149
- package/src/instrumentation/openai/get_cache_and_reasoning_telemetry.js +0 -71
- package/src/instrumentation/openai/get_embeddings_telemetry.js +0 -107
- package/src/instrumentation/openai/get_streaming_usage.js +0 -81
- package/src/instrumentation/openai/set_telemetry_attributes.js +0 -39
- package/src/instrumentation/vercel/full_instrumentation.js +0 -44
- package/src/logger.js +0 -8
- package/src/otel/exporter.js +0 -108
- package/src/otel/propagation.js +0 -133
- package/src/otel/span-filter.js +0 -69
- package/src/otel/span-processor.js +0 -162
- package/src/otel/vercel-ai-sdk-span-filter.js +0 -79
- package/src/setup.js +0 -167
- package/tests/api/index.test.js +0 -437
- package/tests/index.test.js +0 -824
- package/tests/instrumentation/anthropic/get_cache_and_reasoning_telemetry.test.js +0 -158
- package/tests/instrumentation/anthropic/get_streaming_stop_reason_telemetry.test.js +0 -117
- package/tests/instrumentation/anthropic/get_tool_definitions_telemetry.test.js +0 -82
- package/tests/instrumentation/bedrock/get_cache_telemetry.test.js +0 -102
- package/tests/instrumentation/bedrock/get_input_message_content_telemetry.test.js +0 -89
- package/tests/instrumentation/bedrock/get_streaming_output_message_telemetry.test.js +0 -87
- package/tests/instrumentation/cohere/get_chat_telemetry.test.js +0 -148
- package/tests/instrumentation/cohere/get_embeddings_telemetry.test.js +0 -132
- package/tests/instrumentation/elevenlabs/full_instrumentation.test.js +0 -258
- package/tests/instrumentation/google/get_embeddings_telemetry.test.js +0 -128
- package/tests/instrumentation/google/get_reasoning_telemetry.test.js +0 -41
- package/tests/instrumentation/google/set_telemetry_attributes.test.js +0 -219
- package/tests/instrumentation/openai/get_audio_telemetry.test.js +0 -170
- package/tests/instrumentation/openai/get_cache_and_reasoning_telemetry.test.js +0 -83
- package/tests/instrumentation/openai/get_embeddings_telemetry.test.js +0 -145
- package/tests/instrumentation/openai/get_streaming_usage.test.js +0 -105
- package/tests/instrumentation/vercel/full_instrumentation.test.js +0 -16
- package/tests/otel/vercel-ai-sdk-span-filter.test.js +0 -159
- package/tsconfig.json +0 -18
- /package/dist/{api → src/api}/caseConversion.d.ts +0 -0
- /package/dist/{api → src/api}/caseConversion.js +0 -0
- /package/dist/{api → src/api}/generated/client/client.gen.d.ts +0 -0
- /package/dist/{api → src/api}/generated/client/client.gen.js +0 -0
- /package/dist/{api → src/api}/generated/client/index.d.ts +0 -0
- /package/dist/{api → src/api}/generated/client/index.js +0 -0
- /package/dist/{api → src/api}/generated/client/types.gen.d.ts +0 -0
- /package/dist/{api → src/api}/generated/client/types.gen.js +0 -0
- /package/dist/{api → src/api}/generated/client/utils.gen.d.ts +0 -0
- /package/dist/{api → src/api}/generated/client/utils.gen.js +0 -0
- /package/dist/{api → src/api}/generated/client.gen.d.ts +0 -0
- /package/dist/{api → src/api}/generated/client.gen.js +0 -0
- /package/dist/{api → src/api}/generated/core/auth.gen.d.ts +0 -0
- /package/dist/{api → src/api}/generated/core/auth.gen.js +0 -0
- /package/dist/{api → src/api}/generated/core/bodySerializer.gen.d.ts +0 -0
- /package/dist/{api → src/api}/generated/core/bodySerializer.gen.js +0 -0
- /package/dist/{api → src/api}/generated/core/params.gen.d.ts +0 -0
- /package/dist/{api → src/api}/generated/core/params.gen.js +0 -0
- /package/dist/{api → src/api}/generated/core/pathSerializer.gen.d.ts +0 -0
- /package/dist/{api → src/api}/generated/core/pathSerializer.gen.js +0 -0
- /package/dist/{api → src/api}/generated/core/queryKeySerializer.gen.d.ts +0 -0
- /package/dist/{api → src/api}/generated/core/queryKeySerializer.gen.js +0 -0
- /package/dist/{api → src/api}/generated/core/serverSentEvents.gen.d.ts +0 -0
- /package/dist/{api → src/api}/generated/core/serverSentEvents.gen.js +0 -0
- /package/dist/{api → src/api}/generated/core/types.gen.d.ts +0 -0
- /package/dist/{api → src/api}/generated/core/types.gen.js +0 -0
- /package/dist/{api → src/api}/generated/core/utils.gen.d.ts +0 -0
- /package/dist/{api → src/api}/generated/core/utils.gen.js +0 -0
- /package/dist/{api → src/api}/generated/types.gen.js +0 -0
- /package/dist/{api → src/api}/index.d.ts +0 -0
- /package/dist/{api → src/api}/index.js +0 -0
- /package/dist/{api → src/api}/makeEndpoint.d.ts +0 -0
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* OpenAI telemetry complement — the registered orchestrator.
|
|
5
|
-
*
|
|
6
|
-
* Wires every weflayr-supplied patch the traceloop OpenAI instrumentor misses:
|
|
7
|
-
* - the audio (TTS/STT) and embeddings endpoints it doesn't cover, and
|
|
8
|
-
* - usage it drops on the chat endpoint it does cover: streamed token usage and
|
|
9
|
-
* non-streamed cached-prompt tokens (these enrich the traceloop instrumentor
|
|
10
|
-
* instance, hence the `instrumentation` argument).
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
const { OpenAIAudioInstrumentor } = require('./get_audio_telemetry');
|
|
14
|
-
const { OpenAIEmbeddingsInstrumentor } = require('./get_embeddings_telemetry');
|
|
15
|
-
const { OpenAIStreamingUsageInstrumentor } = require('./get_streaming_usage');
|
|
16
|
-
const { OpenAIPromptCachingInstrumentor } = require('./get_cache_and_reasoning_telemetry');
|
|
17
|
-
|
|
18
|
-
class OpenAITelemetryInstrumentor {
|
|
19
|
-
constructor() {
|
|
20
|
-
this._subInstrumentors = [
|
|
21
|
-
new OpenAIAudioInstrumentor(),
|
|
22
|
-
new OpenAIEmbeddingsInstrumentor(),
|
|
23
|
-
new OpenAIStreamingUsageInstrumentor(),
|
|
24
|
-
new OpenAIPromptCachingInstrumentor(),
|
|
25
|
-
];
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// The endpoint complements ignore `instrumentation`; the streaming/cache ones
|
|
29
|
-
// patch its internal handlers.
|
|
30
|
-
instrument(instrumentation) {
|
|
31
|
-
for (const sub of this._subInstrumentors) sub.instrument(instrumentation);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
uninstrument() {
|
|
35
|
-
for (const sub of this._subInstrumentors) sub.uninstrument();
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
module.exports = { OpenAITelemetryInstrumentor };
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Instrumentor for the Vercel AI SDK (`ai` package, v7+).
|
|
5
|
-
*
|
|
6
|
-
* The AI SDK ships its own OTel integration in `@ai-sdk/otel`; instrumenting
|
|
7
|
-
* is registering it globally so every generateText/streamText/embed call emits
|
|
8
|
-
* GenAI semconv spans through the tracer provider weflayr's setup() installed.
|
|
9
|
-
* `usage: true` adds the `ai.usage.*` token details (reasoning tokens, uncached
|
|
10
|
-
* input) that the semconv attributes do not carry.
|
|
11
|
-
*
|
|
12
|
-
* The aggregate spans the integration emits around each model call
|
|
13
|
-
* (invoke_agent, agent_step, duplicated embeddings spans) are filtered out
|
|
14
|
-
* before export by otel/vercel-ai-sdk-span-filter.js.
|
|
15
|
-
*/
|
|
16
|
-
class VercelAiSdkInstrumentor {
|
|
17
|
-
instrument() {
|
|
18
|
-
let registerTelemetry;
|
|
19
|
-
let OpenTelemetry;
|
|
20
|
-
try {
|
|
21
|
-
({ registerTelemetry } = require('ai'));
|
|
22
|
-
({ OpenTelemetry } = require('@ai-sdk/otel'));
|
|
23
|
-
} catch (err) {
|
|
24
|
-
throw new Error(
|
|
25
|
-
'weflayr.autoInstrument("vercel_ai_gateway"): failed to load "ai" / "@ai-sdk/otel". ' +
|
|
26
|
-
`Install them with: npm install ai @ai-sdk/otel\nUnderlying error: ${err.message}`
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
// registerTelemetry only exists since v7.
|
|
30
|
-
if (typeof registerTelemetry !== 'function') {
|
|
31
|
-
throw new Error(
|
|
32
|
-
'weflayr.autoInstrument("vercel_ai_gateway") requires version 7+ of the Vercel AI SDK ' +
|
|
33
|
-
'(the "ai" package), which added global telemetry registration. ' +
|
|
34
|
-
'Upgrade with: npm install ai@latest @ai-sdk/otel.\n' +
|
|
35
|
-
'On ai <= 6 telemetry can only be enabled per call: remove "vercel_ai_gateway" from ' +
|
|
36
|
-
'aiSdks (pass an empty list if no other sdks need to be instrumented). Instead, make sure ' +
|
|
37
|
-
'experimental_telemetry: { isEnabled: true } is passed on each generateText/streamText call.'
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
registerTelemetry(new OpenTelemetry({ usage: true }));
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
module.exports = { VercelAiSdkInstrumentor };
|
package/src/logger.js
DELETED
package/src/otel/exporter.js
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const retry = require('async-retry');
|
|
4
|
-
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
|
|
5
|
-
|
|
6
|
-
const { debug } = require('../logger');
|
|
7
|
-
|
|
8
|
-
// OTLP-spec retryable statuses: rate-limit + transient server errors.
|
|
9
|
-
const _RETRYABLE_STATUS = new Set([429, 502, 503, 504]);
|
|
10
|
-
|
|
11
|
-
function _isNonRetryable(err) {
|
|
12
|
-
// Tells async-retry's `bail` whether to give up: bail on 4xx (other than
|
|
13
|
-
// 429); retry on retryable statuses and on transport errors (no status).
|
|
14
|
-
const status = err?.exportResult?.error?.code;
|
|
15
|
-
return status !== undefined && !_RETRYABLE_STATUS.has(status);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function _formatFailure(result, url) {
|
|
19
|
-
const err = result?.error;
|
|
20
|
-
const status = err?.code;
|
|
21
|
-
const body = typeof err?.data === 'string' ? err.data.slice(0, 200) : undefined;
|
|
22
|
-
const message = err?.message || String(err) || 'unknown error';
|
|
23
|
-
let hint = '';
|
|
24
|
-
if (status === 401 || status === 403) {
|
|
25
|
-
hint = ' Check your apiKey / WEFLAYR_API_KEY.';
|
|
26
|
-
} else if (status === 404) {
|
|
27
|
-
hint = ' Check the baseUrl is correct.';
|
|
28
|
-
} else if (!status && /ECONN|ENOTFOUND|timeout/i.test(message)) {
|
|
29
|
-
hint = ' The endpoint is unreachable — check baseUrl and network.';
|
|
30
|
-
}
|
|
31
|
-
return (
|
|
32
|
-
`Weflayr: failed to export telemetry to ${url}` +
|
|
33
|
-
(status ? ` (HTTP ${status})` : '') +
|
|
34
|
-
`: ${message}.${hint}` +
|
|
35
|
-
(body ? ` Response: ${body}` : '')
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Wrap a callback-style OTel exporter `export(spans, cb)` with retry. Retries
|
|
40
|
-
// transport errors + retryable HTTP statuses; bails on other 4xx.
|
|
41
|
-
function _exportWithRetry(originalExport, { maxRetries, maxRetryMs }) {
|
|
42
|
-
return (spans, resultCallback) => {
|
|
43
|
-
retry(
|
|
44
|
-
async (bail) => {
|
|
45
|
-
const result = await new Promise((resolve) => originalExport(spans, resolve));
|
|
46
|
-
if (result?.code === 0) return result;
|
|
47
|
-
const err = new Error(result?.error?.message || 'export failed');
|
|
48
|
-
err.exportResult = result;
|
|
49
|
-
if (_isNonRetryable(err)) {
|
|
50
|
-
bail(err);
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
throw err;
|
|
54
|
-
},
|
|
55
|
-
{ retries: maxRetries - 1, minTimeout: 50, maxRetryTime: maxRetryMs, factor: 2 }
|
|
56
|
-
)
|
|
57
|
-
.then((result) => resultCallback(result))
|
|
58
|
-
.catch((err) => resultCallback(err.exportResult || { code: 1, error: err }));
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function buildExporter({
|
|
63
|
-
baseUrl,
|
|
64
|
-
apiKey,
|
|
65
|
-
timeoutMs = 5000,
|
|
66
|
-
warnOnExportError = true,
|
|
67
|
-
maxRetries = 2,
|
|
68
|
-
maxRetryMs = 1000,
|
|
69
|
-
}) {
|
|
70
|
-
const url = `${baseUrl.replace(/\/$/, '')}/`;
|
|
71
|
-
const exporter = new OTLPTraceExporter({
|
|
72
|
-
url,
|
|
73
|
-
headers: {
|
|
74
|
-
Authorization: `Bearer ${apiKey}`,
|
|
75
|
-
},
|
|
76
|
-
timeoutMillis: timeoutMs,
|
|
77
|
-
});
|
|
78
|
-
const originalExport = exporter.export.bind(exporter);
|
|
79
|
-
const wrappedExport = _exportWithRetry(originalExport, { maxRetries, maxRetryMs });
|
|
80
|
-
exporter.export = (spans, resultCallback) => {
|
|
81
|
-
const start = Date.now();
|
|
82
|
-
debug('exporter.export: posting count=%d', spans.length);
|
|
83
|
-
wrappedExport(spans, (result) => {
|
|
84
|
-
const ms = Date.now() - start;
|
|
85
|
-
if (result && result.code === 0) {
|
|
86
|
-
debug('exporter.export: ok count=%d elapsed_ms=%d', spans.length, ms);
|
|
87
|
-
} else {
|
|
88
|
-
debug(
|
|
89
|
-
'exporter.export: FAIL count=%d elapsed_ms=%d code=%s error=%s',
|
|
90
|
-
spans.length,
|
|
91
|
-
ms,
|
|
92
|
-
result?.code,
|
|
93
|
-
result?.error?.message || result?.error || '(none)'
|
|
94
|
-
);
|
|
95
|
-
if (warnOnExportError) {
|
|
96
|
-
// console.warn writes to stderr synchronously. process.emitWarning
|
|
97
|
-
// is queued for the next tick, so a caller doing process.exit() from
|
|
98
|
-
// their error handler races past it and the warning is lost.
|
|
99
|
-
console.warn(`[weflayr] ${_formatFailure(result, url)}`);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
resultCallback(result);
|
|
103
|
-
});
|
|
104
|
-
};
|
|
105
|
-
return exporter;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
module.exports = { buildExporter, _exportWithRetry, _RETRYABLE_STATUS };
|
package/src/otel/propagation.js
DELETED
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
// Copyright 2024 Langfuse GmbH
|
|
2
|
-
// Licensed under the MIT License.
|
|
3
|
-
// Original source: https://github.com/langfuse/langfuse-js/blob/main/packages/core/src/propagation.ts
|
|
4
|
-
//
|
|
5
|
-
// Modifications copyright 2026 Weflayr
|
|
6
|
-
// This file has been modified from its original form:
|
|
7
|
-
// - Translated from TypeScript to JavaScript
|
|
8
|
-
// - Replaced Langfuse's fixed schema with Weflayr's structured metadata
|
|
9
|
-
// (featureName, customerName, providerName, extraTags) plus an
|
|
10
|
-
// auto-generated per-call uuid
|
|
11
|
-
// - Removed asBaggage cross-process propagation (not needed yet)
|
|
12
|
-
// - Removed experiment framework keys
|
|
13
|
-
// - Structured fields are written under stable `weflayr.*` span attribute
|
|
14
|
-
// keys; extraTags entries are written verbatim
|
|
15
|
-
//
|
|
16
|
-
// See LICENSE-MIT in the package root for the full license text.
|
|
17
|
-
|
|
18
|
-
'use strict';
|
|
19
|
-
|
|
20
|
-
const { randomUUID } = require('node:crypto');
|
|
21
|
-
const { context: otelContext, trace, createContextKey } = require('@opentelemetry/api');
|
|
22
|
-
|
|
23
|
-
const WEFLAYR_METADATA_CONTEXT_KEY = createContextKey('weflayr_metadata');
|
|
24
|
-
|
|
25
|
-
const FEATURE_NAME_ATTR = 'weflayr.feature_name';
|
|
26
|
-
const CUSTOMER_NAME_ATTR = 'weflayr.customer_name';
|
|
27
|
-
const PROVIDER_NAME_ATTR = 'weflayr.provider_name';
|
|
28
|
-
const UUID_ATTR = 'weflayr.uuid';
|
|
29
|
-
// Prefix for user-supplied `extraTags` so downstream normalizers can pick
|
|
30
|
-
// them out without colliding with instrumentor-set `gen_ai.*` attributes.
|
|
31
|
-
const EXTRA_TAG_PREFIX = 'weflayr.tag.';
|
|
32
|
-
|
|
33
|
-
function getMergedMetadataFromContext(parentContext) {
|
|
34
|
-
const ctx = parentContext ?? otelContext.active();
|
|
35
|
-
const existing = ctx.getValue(WEFLAYR_METADATA_CONTEXT_KEY);
|
|
36
|
-
return existing && typeof existing === 'object' ? { ...existing } : {};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function _buildAttributes({ featureName, customerName, providerName, extraTags, spanUuid }) {
|
|
40
|
-
const attributes = {
|
|
41
|
-
[FEATURE_NAME_ATTR]: featureName,
|
|
42
|
-
[CUSTOMER_NAME_ATTR]: customerName,
|
|
43
|
-
[UUID_ATTR]: spanUuid,
|
|
44
|
-
};
|
|
45
|
-
for (const [key, value] of Object.entries(extraTags)) {
|
|
46
|
-
attributes[`${EXTRA_TAG_PREFIX}${key}`] = value;
|
|
47
|
-
}
|
|
48
|
-
if (providerName !== undefined && providerName !== null) {
|
|
49
|
-
attributes[PROVIDER_NAME_ATTR] = providerName;
|
|
50
|
-
}
|
|
51
|
-
return attributes;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Attach Weflayr metadata to every LLM span created inside this scope.
|
|
56
|
-
*
|
|
57
|
-
* Callable form: `propagateMetadata(options, fn)` runs `fn` immediately.
|
|
58
|
-
* Decorator form: `propagateMetadata(options)` returns a wrapper usable as a
|
|
59
|
-
* function-decorator (`@propagateMetadata(...)` on class methods) or as a
|
|
60
|
-
* plain `wrap(fn)` -> wrappedFn helper. Each invocation of the wrapped
|
|
61
|
-
* function mints a fresh `uuid`, matching the callable form's "one uuid per
|
|
62
|
-
* propagateMetadata call" contract.
|
|
63
|
-
*/
|
|
64
|
-
function propagateMetadata(options, fn) {
|
|
65
|
-
const { featureName, customerId } = options ?? {};
|
|
66
|
-
let { customerName } = options ?? {};
|
|
67
|
-
if (customerId != null) {
|
|
68
|
-
process.emitWarning(
|
|
69
|
-
'propagateMetadata: `customerId` is deprecated, use `customerName`',
|
|
70
|
-
'DeprecationWarning'
|
|
71
|
-
);
|
|
72
|
-
customerName = customerName ?? customerId;
|
|
73
|
-
}
|
|
74
|
-
if (featureName == null || customerName == null) {
|
|
75
|
-
throw new Error('propagateMetadata: `featureName` and `customerName` are required');
|
|
76
|
-
}
|
|
77
|
-
const normalizedOptions = { ...options, customerName };
|
|
78
|
-
|
|
79
|
-
if (typeof fn === 'function') {
|
|
80
|
-
return _runWithMetadata(normalizedOptions, fn);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return function decorator(target, key, descriptor) {
|
|
84
|
-
if (descriptor && typeof descriptor.value === 'function') {
|
|
85
|
-
const original = descriptor.value;
|
|
86
|
-
descriptor.value = function wrapped(...args) {
|
|
87
|
-
return _runWithMetadata(normalizedOptions, () => original.apply(this, args));
|
|
88
|
-
};
|
|
89
|
-
return descriptor;
|
|
90
|
-
}
|
|
91
|
-
if (typeof target === 'function') {
|
|
92
|
-
return function wrapped(...args) {
|
|
93
|
-
return _runWithMetadata(normalizedOptions, () => target.apply(this, args));
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
return target;
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function _runWithMetadata(options, fn) {
|
|
101
|
-
const { featureName, customerName, providerName, extraTags } = options;
|
|
102
|
-
const attributes = _buildAttributes({
|
|
103
|
-
featureName,
|
|
104
|
-
customerName,
|
|
105
|
-
providerName,
|
|
106
|
-
extraTags: extraTags ?? {},
|
|
107
|
-
spanUuid: randomUUID().replace(/-/g, ''),
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
const parent = otelContext.active();
|
|
111
|
-
const merged = { ...getMergedMetadataFromContext(parent), ...attributes };
|
|
112
|
-
const newContext = parent.setValue(WEFLAYR_METADATA_CONTEXT_KEY, merged);
|
|
113
|
-
|
|
114
|
-
const activeSpan = trace.getActiveSpan();
|
|
115
|
-
if (activeSpan && activeSpan.isRecording && activeSpan.isRecording()) {
|
|
116
|
-
for (const [k, v] of Object.entries(attributes)) {
|
|
117
|
-
activeSpan.setAttribute(k, v);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return otelContext.with(newContext, fn);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
module.exports = {
|
|
125
|
-
propagateMetadata,
|
|
126
|
-
getMergedMetadataFromContext,
|
|
127
|
-
WEFLAYR_METADATA_CONTEXT_KEY,
|
|
128
|
-
FEATURE_NAME_ATTR,
|
|
129
|
-
CUSTOMER_NAME_ATTR,
|
|
130
|
-
PROVIDER_NAME_ATTR,
|
|
131
|
-
UUID_ATTR,
|
|
132
|
-
EXTRA_TAG_PREFIX,
|
|
133
|
-
};
|
package/src/otel/span-filter.js
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
// Copyright 2024 Langfuse GmbH
|
|
2
|
-
// Licensed under the MIT License.
|
|
3
|
-
// Original source: https://github.com/langfuse/langfuse-js/blob/main/packages/otel/src/span-filter.ts
|
|
4
|
-
//
|
|
5
|
-
// Modifications copyright 2026 Weflayr
|
|
6
|
-
// This file has been modified from its original form:
|
|
7
|
-
// - Translated from TypeScript to JavaScript
|
|
8
|
-
// - Dropped the LANGFUSE_TRACER_NAME special case (we have no SDK-internal tracer)
|
|
9
|
-
// - Dropped isLangfuseSpan
|
|
10
|
-
//
|
|
11
|
-
// See LICENSE-MIT in the package root for the full license text.
|
|
12
|
-
|
|
13
|
-
'use strict';
|
|
14
|
-
|
|
15
|
-
const KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES = [
|
|
16
|
-
'agent_framework',
|
|
17
|
-
'ai',
|
|
18
|
-
'haystack',
|
|
19
|
-
'langsmith',
|
|
20
|
-
'litellm',
|
|
21
|
-
'openinference',
|
|
22
|
-
'opentelemetry.instrumentation.anthropic',
|
|
23
|
-
'opentelemetry.instrumentation.aws_bedrock',
|
|
24
|
-
'opentelemetry.instrumentation.bedrock',
|
|
25
|
-
'opentelemetry.instrumentation.gemini',
|
|
26
|
-
'opentelemetry.instrumentation.google_genai',
|
|
27
|
-
'opentelemetry.instrumentation.google_generativeai',
|
|
28
|
-
'opentelemetry.instrumentation.openai',
|
|
29
|
-
'opentelemetry.instrumentation.openai_v2',
|
|
30
|
-
'weflayr.instrumentation.elevenlabs',
|
|
31
|
-
'strands-agents',
|
|
32
|
-
'vllm',
|
|
33
|
-
'@traceloop/instrumentation-openai',
|
|
34
|
-
'@traceloop/instrumentation-anthropic',
|
|
35
|
-
'@traceloop/instrumentation-bedrock',
|
|
36
|
-
'@traceloop/instrumentation-cohere',
|
|
37
|
-
'@traceloop/instrumentation-google-generativeai',
|
|
38
|
-
'@traceloop/instrumentation-langchain',
|
|
39
|
-
'@traceloop/instrumentation-llamaindex',
|
|
40
|
-
'@traceloop/instrumentation-pinecone',
|
|
41
|
-
'@traceloop/instrumentation-chromadb',
|
|
42
|
-
'@traceloop/instrumentation-qdrant',
|
|
43
|
-
'@traceloop/instrumentation-mcp',
|
|
44
|
-
];
|
|
45
|
-
|
|
46
|
-
const EXACT_LLM_INSTRUMENTATION_SCOPES = new Set(['ai']);
|
|
47
|
-
|
|
48
|
-
function isGenAISpan(span) {
|
|
49
|
-
return Object.keys(span.attributes).some((key) => key.startsWith('gen_ai.'));
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function isKnownLLMInstrumentor(span) {
|
|
53
|
-
const scope = span.instrumentationScope.name;
|
|
54
|
-
return KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES.some(
|
|
55
|
-
(prefix) =>
|
|
56
|
-
scope === prefix ||
|
|
57
|
-
(!EXACT_LLM_INSTRUMENTATION_SCOPES.has(prefix) && scope.startsWith(`${prefix}.`))
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function isDefaultExportSpan(span) {
|
|
62
|
-
return isGenAISpan(span) || isKnownLLMInstrumentor(span);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
module.exports = {
|
|
66
|
-
isGenAISpan,
|
|
67
|
-
isKnownLLMInstrumentor,
|
|
68
|
-
isDefaultExportSpan,
|
|
69
|
-
};
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
// Copyright 2024 Langfuse GmbH
|
|
2
|
-
// Licensed under the MIT License.
|
|
3
|
-
// Original source: https://github.com/langfuse/langfuse-js/blob/main/packages/otel/src/span-processor.ts
|
|
4
|
-
//
|
|
5
|
-
// Modifications copyright 2026 Weflayr
|
|
6
|
-
// This file has been modified from its original form:
|
|
7
|
-
// - Translated from TypeScript to JavaScript
|
|
8
|
-
// - Removed MediaService integration (no media upload support yet)
|
|
9
|
-
// - Removed LangfuseAPIClient / app-root marker logic (not used)
|
|
10
|
-
// - Removed Langfuse-specific span attributes (TRACE_INPUT/OUTPUT/METADATA, IS_APP_ROOT, ENVIRONMENT, RELEASE)
|
|
11
|
-
// - Mask hook now receives the full flat attribute bag, not a {data} envelope, and runs synchronously
|
|
12
|
-
// - default_tags are applied as span attributes in onStart
|
|
13
|
-
// - propagated tags come from Weflayr's tag bag (see propagation.js), not Langfuse's typed schema
|
|
14
|
-
//
|
|
15
|
-
// See LICENSE-MIT in the package root for the full license text.
|
|
16
|
-
|
|
17
|
-
'use strict';
|
|
18
|
-
|
|
19
|
-
const { SpanStatusCode } = require('@opentelemetry/api');
|
|
20
|
-
const { BatchSpanProcessor, SimpleSpanProcessor } = require('@opentelemetry/sdk-trace-base');
|
|
21
|
-
const { EXTRA_TAG_PREFIX, getMergedMetadataFromContext } = require('./propagation');
|
|
22
|
-
const { isDefaultExportSpan, isKnownLLMInstrumentor } = require('./span-filter');
|
|
23
|
-
const {
|
|
24
|
-
VercelAiSdkSpanFilter,
|
|
25
|
-
stampVercelGatewayProviderName,
|
|
26
|
-
} = require('./vercel-ai-sdk-span-filter');
|
|
27
|
-
const { debug } = require('../logger');
|
|
28
|
-
|
|
29
|
-
// setup({captureMessageContent: ...}) records the choice in this env var (the
|
|
30
|
-
// official OTel GenAI one). Instrumentors gate content on their own knobs
|
|
31
|
-
// (openllmetry reads its own config and captures by default), so the processor
|
|
32
|
-
// enforces the flag itself: when the env var is "false", message content is
|
|
33
|
-
// stripped from every span before export.
|
|
34
|
-
const CONTENT_CAPTURE_ENV = 'OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT';
|
|
35
|
-
|
|
36
|
-
// The new-semconv JSON message attributes and the legacy indexed
|
|
37
|
-
// `gen_ai.prompt.{i}.*` / `gen_ai.completion.{i}.*` attributes.
|
|
38
|
-
const CONTENT_ATTRIBUTES = [
|
|
39
|
-
'gen_ai.input.messages',
|
|
40
|
-
'gen_ai.output.messages',
|
|
41
|
-
'gen_ai.system_instructions',
|
|
42
|
-
];
|
|
43
|
-
const CONTENT_ATTRIBUTE_PREFIXES = ['gen_ai.prompt.', 'gen_ai.completion.'];
|
|
44
|
-
|
|
45
|
-
function _isContentAttribute(key) {
|
|
46
|
-
return (
|
|
47
|
-
CONTENT_ATTRIBUTES.includes(key) ||
|
|
48
|
-
CONTENT_ATTRIBUTE_PREFIXES.some((prefix) => key.startsWith(prefix))
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
class WeflayrSpanProcessor {
|
|
53
|
-
constructor({ exporter, exportMode = 'batch', defaultTags = {}, mask, shouldExportSpan } = {}) {
|
|
54
|
-
if (!exporter) {
|
|
55
|
-
throw new Error('WeflayrSpanProcessor: exporter is required');
|
|
56
|
-
}
|
|
57
|
-
this._delegate =
|
|
58
|
-
exportMode === 'immediate'
|
|
59
|
-
? new SimpleSpanProcessor(exporter)
|
|
60
|
-
: new BatchSpanProcessor(exporter);
|
|
61
|
-
this._defaultTags = defaultTags;
|
|
62
|
-
this._mask = mask;
|
|
63
|
-
this._shouldExportSpan = shouldExportSpan ?? isDefaultExportSpan;
|
|
64
|
-
this._vercelAiSdkSpanFilter = new VercelAiSdkSpanFilter();
|
|
65
|
-
// LLM spans we've started but not yet seen end. On forceFlush we end any
|
|
66
|
-
// leftovers with ERROR status — covers cases like openllmetry's anthropic
|
|
67
|
-
// streaming wrapper, which leaks the span when the underlying call
|
|
68
|
-
// rejects. Generic across providers; no per-provider patching needed.
|
|
69
|
-
this._openLlmSpans = new Set();
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
onStart(span, parentContext) {
|
|
73
|
-
for (const [k, v] of Object.entries(this._defaultTags)) {
|
|
74
|
-
span.setAttribute(`${EXTRA_TAG_PREFIX}${k}`, v);
|
|
75
|
-
}
|
|
76
|
-
const propagated = getMergedMetadataFromContext(parentContext);
|
|
77
|
-
for (const [k, v] of Object.entries(propagated)) {
|
|
78
|
-
span.setAttribute(k, v);
|
|
79
|
-
}
|
|
80
|
-
if (isKnownLLMInstrumentor(span)) {
|
|
81
|
-
this._openLlmSpans.add(span);
|
|
82
|
-
}
|
|
83
|
-
this._vercelAiSdkSpanFilter.onStart(span);
|
|
84
|
-
debug(
|
|
85
|
-
'span start: name=%s defaultTags=%d propagatedMetadata=%d',
|
|
86
|
-
span.name,
|
|
87
|
-
Object.keys(this._defaultTags).length,
|
|
88
|
-
Object.keys(propagated).length
|
|
89
|
-
);
|
|
90
|
-
return this._delegate.onStart(span, parentContext);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
onEnd(span) {
|
|
94
|
-
this._openLlmSpans.delete(span);
|
|
95
|
-
try {
|
|
96
|
-
const vercelVerdict = this._vercelAiSdkSpanFilter.shouldExport(span);
|
|
97
|
-
if (vercelVerdict === true) {
|
|
98
|
-
stampVercelGatewayProviderName(span);
|
|
99
|
-
}
|
|
100
|
-
if (vercelVerdict === false || (vercelVerdict === null && !this._shouldExportSpan(span))) {
|
|
101
|
-
debug(
|
|
102
|
-
'span end: filtered out name=%s scope=%s',
|
|
103
|
-
span.name,
|
|
104
|
-
span.instrumentationScope?.name
|
|
105
|
-
);
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
} catch {
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
debug('span end: exporting name=%s scope=%s', span.name, span.instrumentationScope?.name);
|
|
112
|
-
|
|
113
|
-
if ((process.env[CONTENT_CAPTURE_ENV] || '').toLowerCase() === 'false') {
|
|
114
|
-
for (const key of Object.keys(span.attributes)) {
|
|
115
|
-
if (_isContentAttribute(key)) delete span.attributes[key];
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (this._mask) {
|
|
120
|
-
try {
|
|
121
|
-
const masked = this._mask(span.attributes);
|
|
122
|
-
if (masked && masked !== span.attributes) {
|
|
123
|
-
for (const k of Object.keys(span.attributes)) delete span.attributes[k];
|
|
124
|
-
Object.assign(span.attributes, masked);
|
|
125
|
-
}
|
|
126
|
-
} catch {
|
|
127
|
-
for (const k of Object.keys(span.attributes)) delete span.attributes[k];
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return this._delegate.onEnd(span);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
async forceFlush() {
|
|
135
|
-
this._endOrphanedLlmSpans();
|
|
136
|
-
return this._delegate.forceFlush();
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
_endOrphanedLlmSpans() {
|
|
140
|
-
const orphans = [...this._openLlmSpans];
|
|
141
|
-
this._openLlmSpans.clear();
|
|
142
|
-
for (const span of orphans) {
|
|
143
|
-
try {
|
|
144
|
-
if (typeof span.isRecording === 'function' && !span.isRecording()) continue;
|
|
145
|
-
span.setStatus({
|
|
146
|
-
code: SpanStatusCode.ERROR,
|
|
147
|
-
message: 'weflayr: span did not complete before flush',
|
|
148
|
-
});
|
|
149
|
-
span.end();
|
|
150
|
-
debug('forceFlush: ended orphaned LLM span name=%s', span.name);
|
|
151
|
-
} catch (err) {
|
|
152
|
-
debug('forceFlush: failed to end orphan span: %s', err?.message || err);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
shutdown() {
|
|
158
|
-
return this._delegate.shutdown();
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
module.exports = { CONTENT_CAPTURE_ENV, WeflayrSpanProcessor };
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// The Vercel AI SDK's OTel integrations emit aggregate spans around each model
|
|
4
|
-
// call, all repeating the same usage counts:
|
|
5
|
-
// - `@ai-sdk/otel` (ai >= 7, scope "gen_ai"): per-call "chat" spans are
|
|
6
|
-
// wrapped in "agent_step" + "invoke_agent" spans, and embed/embedMany
|
|
7
|
-
// emits an outer "embeddings" operation span around the model-call
|
|
8
|
-
// "embeddings" span(s).
|
|
9
|
-
// - the legacy integration (ai <= 6, scope "ai"): `ai.<fn>.do<Call>` spans
|
|
10
|
-
// are wrapped in an outer `ai.<fn>` span.
|
|
11
|
-
// Exactly one span per model call may reach the exporter — exporting an
|
|
12
|
-
// aggregate alongside its children would bill every call twice.
|
|
13
|
-
|
|
14
|
-
const VERCEL_SEMCONV_SCOPE = 'gen_ai';
|
|
15
|
-
const VERCEL_LEGACY_SCOPE = 'ai';
|
|
16
|
-
|
|
17
|
-
const LEGACY_MODEL_CALL_SPAN_NAME = /\.do(Generate|Stream|Embed)$/;
|
|
18
|
-
|
|
19
|
-
function _parentSpanId(span) {
|
|
20
|
-
return span.parentSpanContext?.spanId ?? span.parentSpanId;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
class VercelAiSdkSpanFilter {
|
|
24
|
-
constructor() {
|
|
25
|
-
// spanId -> gen_ai.operation.name for open "gen_ai"-scope spans, so a
|
|
26
|
-
// span can check what its parent is (children end before their parent).
|
|
27
|
-
this._openSpanOperations = new Map();
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
onStart(span) {
|
|
31
|
-
if (span.instrumentationScope?.name !== VERCEL_SEMCONV_SCOPE) return;
|
|
32
|
-
this._openSpanOperations.set(
|
|
33
|
-
span.spanContext().spanId,
|
|
34
|
-
span.attributes['gen_ai.operation.name']
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Export verdict for a Vercel AI SDK span, or null when the span is not
|
|
40
|
-
* one (the caller falls back to the default filter).
|
|
41
|
-
*/
|
|
42
|
-
shouldExport(span) {
|
|
43
|
-
const scope = span.instrumentationScope?.name;
|
|
44
|
-
if (scope === VERCEL_LEGACY_SCOPE) {
|
|
45
|
-
return LEGACY_MODEL_CALL_SPAN_NAME.test(span.name);
|
|
46
|
-
}
|
|
47
|
-
if (scope !== VERCEL_SEMCONV_SCOPE) {
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
const operation = span.attributes['gen_ai.operation.name'];
|
|
51
|
-
this._openSpanOperations.delete(span.spanContext().spanId);
|
|
52
|
-
if (operation === 'chat') {
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
if (operation === 'embeddings') {
|
|
56
|
-
// Keep the outer aggregate (one event per embed/embedMany call, with the
|
|
57
|
-
// total usage) and drop the nested model-call span(s) repeating it.
|
|
58
|
-
return this._openSpanOperations.get(_parentSpanId(span)) !== 'embeddings';
|
|
59
|
-
}
|
|
60
|
-
// invoke_agent / agent_step (usage aggregates) and execute_tool (no usage).
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// `@ai-sdk/otel` reports the Vercel AI Gateway as the bare provider id
|
|
66
|
-
// "gateway" — too generic a name to special-case downstream. Stamp the weflayr
|
|
67
|
-
// provider override (which the normalizer prefers) with the canonical name.
|
|
68
|
-
// A providerName set through propagateMetadata was stamped at span start and
|
|
69
|
-
// wins.
|
|
70
|
-
function stampVercelGatewayProviderName(span) {
|
|
71
|
-
if (
|
|
72
|
-
span.attributes['gen_ai.provider.name'] === 'gateway' &&
|
|
73
|
-
span.attributes['weflayr.provider_name'] === undefined
|
|
74
|
-
) {
|
|
75
|
-
span.attributes['weflayr.provider_name'] = 'vercel_ai_gateway';
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
module.exports = { VercelAiSdkSpanFilter, stampVercelGatewayProviderName };
|