@juspay/neurolink 13.4.10 → 13.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -3
- package/README.md +3 -2
- package/dist/adapters/video/videoAnalyzer.d.ts +1 -0
- package/dist/adapters/video/videoAnalyzer.js +4 -0
- package/dist/autoresearch/index.d.ts +1 -0
- package/dist/autoresearch/index.js +4 -0
- package/dist/autoresearch/worker.js +4 -0
- package/dist/browser/neurolink.min.js +466 -466
- package/dist/cli/commands/decide.js +25 -5
- package/dist/cli/commands/observability.js +11 -4
- package/dist/cli/commands/telemetry.js +36 -6
- package/dist/cli/utils/formatters.d.ts +17 -0
- package/dist/cli/utils/formatters.js +39 -0
- package/dist/constants/langfuse.d.ts +17 -1
- package/dist/constants/langfuse.js +23 -1
- package/dist/constants/telemetry.d.ts +6 -0
- package/dist/constants/telemetry.js +6 -0
- package/dist/core/baseProvider.js +2 -0
- package/dist/core/geminiLoopAdapter.js +19 -0
- package/dist/core/loopEngine.js +10 -7
- package/dist/core/modules/StreamHandler.js +5 -4
- package/dist/core/modules/TelemetryHandler.d.ts +5 -1
- package/dist/core/modules/TelemetryHandler.js +16 -0
- package/dist/core/modules/ToolsManager.js +3 -0
- package/dist/core/nativeGenerateLoop.js +1 -1
- package/dist/evaluation/index.d.ts +1 -0
- package/dist/evaluation/index.js +4 -0
- package/dist/evaluation/ragasEvaluator.js +1 -0
- package/dist/factories/providerDescriptors.d.ts +4 -3
- package/dist/factories/providerDescriptors.js +6 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mcp/externalServerManager.js +6 -2
- package/dist/mcp/toolRegistry.js +3 -0
- package/dist/neurolink.d.ts +20 -10
- package/dist/neurolink.js +134 -65
- package/dist/observability/FEATURE-STATUS.md +0 -4
- package/dist/observability/exporterRegistry.d.ts +6 -1
- package/dist/observability/exporterRegistry.js +18 -0
- package/dist/observability/exporterSpanProcessor.d.ts +20 -6
- package/dist/observability/exporterSpanProcessor.js +166 -34
- package/dist/observability/exporters/baseExporter.d.ts +22 -3
- package/dist/observability/exporters/baseExporter.js +87 -5
- package/dist/observability/exporters/langfuseExporter.d.ts +29 -3
- package/dist/observability/exporters/langfuseExporter.js +251 -41
- package/dist/observability/langfuseMasking.d.ts +1 -1
- package/dist/observability/langfuseMasking.js +1 -1
- package/dist/observability/otlpEncoding.d.ts +18 -5
- package/dist/observability/otlpEncoding.js +69 -12
- package/dist/observability/utils/spanSerializer.d.ts +3 -2
- package/dist/observability/utils/spanSerializer.js +5 -3
- package/dist/processors/base/BaseFileProcessor.d.ts +1 -0
- package/dist/processors/base/BaseFileProcessor.js +4 -0
- package/dist/processors/index.d.ts +1 -0
- package/dist/processors/index.js +4 -0
- package/dist/processors/registry/ProcessorRegistry.d.ts +1 -0
- package/dist/processors/registry/ProcessorRegistry.js +5 -0
- package/dist/providers/amazonBedrock/client.js +7 -5
- package/dist/providers/amazonSagemaker.js +32 -12
- package/dist/providers/anthropic/client.js +1 -0
- package/dist/providers/anthropic/loopAdapter.js +7 -3
- package/dist/providers/catalog/provider-catalog.schema.json +64 -3
- package/dist/providers/catalog/schema.js +81 -1
- package/dist/providers/catalog/xor.json +30 -10
- package/dist/providers/configuredDecision.d.ts +8 -1
- package/dist/providers/configuredDecision.js +22 -0
- package/dist/providers/decide/dialects/openaiDecisions.js +8 -0
- package/dist/providers/decide/dialects/systemOne.js +28 -2
- package/dist/providers/decide/guards.d.ts +6 -0
- package/dist/providers/decide/guards.js +22 -0
- package/dist/providers/googleAiStudio/client.js +2 -0
- package/dist/providers/googleVertex/client.d.ts +6 -5
- package/dist/providers/googleVertex/client.js +46 -19
- package/dist/providers/openaiChatCompletionsBase.js +4 -4
- package/dist/providers/systemOneDecision.d.ts +30 -7
- package/dist/providers/systemOneDecision.js +71 -14
- package/dist/proxy/codexUsage.js +59 -2
- package/dist/proxy/proxyTracer.d.ts +25 -1
- package/dist/proxy/proxyTracer.js +146 -22
- package/dist/proxy/proxyTranslationEngine.js +4 -2
- package/dist/proxy/sseInterceptor.js +6 -4
- package/dist/rag/chunkers/BaseChunker.js +1 -0
- package/dist/rag/index.d.ts +1 -0
- package/dist/rag/index.js +4 -0
- package/dist/rag/ragIntegration.js +1 -0
- package/dist/rag/reranker/reranker.js +2 -0
- package/dist/rag/retrieval/vectorQueryTool.js +1 -0
- package/dist/server/routes/codexProxyRoutes.js +3 -1
- package/dist/server/utils/validation.d.ts +3 -3
- package/dist/services/server/ai/observability/instrumentation.d.ts +8 -7
- package/dist/services/server/ai/observability/instrumentation.js +217 -47
- package/dist/services/server/ai/observability/langfuseSetup.d.ts +19 -1
- package/dist/services/server/ai/observability/langfuseSetup.js +62 -4
- package/dist/services/server/ai/observability/otlpSetup.d.ts +12 -2
- package/dist/services/server/ai/observability/otlpSetup.js +55 -4
- package/dist/telemetry/aiMetrics.d.ts +56 -0
- package/dist/telemetry/aiMetrics.js +229 -0
- package/dist/telemetry/attributes.d.ts +24 -1
- package/dist/telemetry/attributes.js +94 -9
- package/dist/telemetry/chatMessages.d.ts +6 -0
- package/dist/telemetry/chatMessages.js +37 -0
- package/dist/telemetry/index.d.ts +1 -1
- package/dist/telemetry/index.js +1 -1
- package/dist/telemetry/observation.d.ts +5 -5
- package/dist/telemetry/observation.js +128 -39
- package/dist/telemetry/spanSanitizer.d.ts +2 -2
- package/dist/telemetry/spanSanitizer.js +76 -8
- package/dist/telemetry/telemetryStarter.d.ts +16 -0
- package/dist/telemetry/telemetryStarter.js +28 -0
- package/dist/telemetry/traceContext.d.ts +3 -3
- package/dist/telemetry/traceContext.js +3 -3
- package/dist/telemetry/tracers.d.ts +13 -1
- package/dist/telemetry/tracers.js +49 -10
- package/dist/telemetry/withSpan.d.ts +19 -3
- package/dist/telemetry/withSpan.js +149 -20
- package/dist/types/cli.d.ts +2 -0
- package/dist/types/decision.d.ts +117 -10
- package/dist/types/exporter.d.ts +98 -3
- package/dist/types/livekit.d.ts +16 -1
- package/dist/types/loopEngine.d.ts +16 -1
- package/dist/types/observability.d.ts +38 -5
- package/dist/types/providerCatalog.d.ts +12 -1
- package/dist/types/providers.d.ts +2 -0
- package/dist/types/proxy.d.ts +2 -0
- package/dist/types/span.d.ts +26 -0
- package/dist/utils/decisionAnswers.d.ts +7 -1
- package/dist/utils/decisionAnswers.js +43 -2
- package/dist/utils/decisionLimits.d.ts +13 -1
- package/dist/utils/decisionLimits.js +52 -1
- package/dist/utils/decisionMedia.d.ts +4 -2
- package/dist/utils/decisionMedia.js +216 -14
- package/dist/utils/timeout.js +4 -2
- package/dist/voice/livekit/realtimeMcpTools.js +11 -2
- package/dist/voice/livekit/realtimeMemory.d.ts +20 -2
- package/dist/voice/livekit/realtimeMemory.js +139 -46
- package/dist/voice/livekit/realtimeVoiceAgent.js +20 -8
- package/dist/voice/livekit/turnRecorder.js +22 -9
- package/dist/workflow/core/ensembleExecutor.js +2 -0
- package/dist/workflow/core/judgeScorer.js +1 -0
- package/dist/workflow/core/responseConditioner.js +1 -0
- package/dist/workflow/core/workflowRunner.js +6 -1
- package/dist/workflow/index.d.ts +1 -0
- package/dist/workflow/index.js +4 -0
- package/docs-site/static/search-index.json +56 -48
- package/package.json +21 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Span } from "@opentelemetry/api";
|
|
2
2
|
import type { GenerationRecord, ObservationAttributes, ObservationLevel, ObservationType, RawUsageObject, TraceContent } from "../types/index.js";
|
|
3
3
|
/**
|
|
4
|
-
* @description Stamps Langfuse observation attributes on a span, plus a `gen_ai.observation.type` marker so the exporter pipeline recognizes it whatever its tracer scope. Input and output are credential-masked and capped at `SPAN_ATTRIBUTE_MAX_CHARS` before serialization.
|
|
4
|
+
* @description Stamps Langfuse observation attributes on a span, plus a `gen_ai.observation.type` marker so the exporter pipeline recognizes it whatever its tracer scope. Input and output are credential-masked and capped at `SPAN_ATTRIBUTE_MAX_CHARS` before serialization, and the status message is recorded as given; an attribute that `spanContentAttribute` or `spanModelRequestAttribute` already produced is not masked again, and input is left out when `NEUROLINK_RECORD_INPUTS=false`. Nothing is built when the span is not recording.
|
|
5
5
|
* @param span {Span} - Span to describe.
|
|
6
6
|
* @param type {ObservationType} - Observation type Langfuse renders the span as.
|
|
7
7
|
* @param attrs {ObservationAttributes} - Input, output, model, parameters, usage, cost, level and metadata to record.
|
|
@@ -9,7 +9,7 @@ import type { GenerationRecord, ObservationAttributes, ObservationLevel, Observa
|
|
|
9
9
|
*/
|
|
10
10
|
export declare function observe(span: Span, type: ObservationType, attrs: ObservationAttributes): void;
|
|
11
11
|
/**
|
|
12
|
-
* @description Sets the trace-level input, output and metadata on a span, credential-masked and capped like `observe
|
|
12
|
+
* @description Sets the trace-level input, output and metadata on a span, credential-masked and capped like `observe`, with input left out when `NEUROLINK_RECORD_INPUTS=false` and nothing built when the span is not recording. Each metadata entry is written as its own `langfuse.trace.metadata.<key>` attribute, redacted like input and output.
|
|
13
13
|
* @param span {Span} - Span whose trace is described.
|
|
14
14
|
* @param io {TraceContent} - Trace input, output and metadata.
|
|
15
15
|
* @returns {void} Nothing.
|
|
@@ -23,13 +23,13 @@ export declare function observeTrace(span: Span, io: TraceContent): void;
|
|
|
23
23
|
export declare function usageDetails(usage: RawUsageObject | undefined | null): Record<string, number>;
|
|
24
24
|
/**
|
|
25
25
|
* @description Picks the Langfuse level for a model call that finished without throwing.
|
|
26
|
-
* @param finishReason {
|
|
26
|
+
* @param finishReason {unknown} - Provider finish reason: a string in any casing, or an ai@6 `{ unified, raw }` object; any other value is ignored.
|
|
27
27
|
* @param aborted {boolean | undefined} - Whether the caller aborted or closed the call early.
|
|
28
28
|
* @returns {ObservationLevel | undefined} `"WARNING"` for truncated, filtered, aborted or empty output, otherwise `undefined`.
|
|
29
29
|
*/
|
|
30
|
-
export declare function observationLevel(finishReason?:
|
|
30
|
+
export declare function observationLevel(finishReason?: unknown, aborted?: boolean): ObservationLevel | undefined;
|
|
31
31
|
/**
|
|
32
|
-
* @description Records a model request on its span as a Langfuse generation: model, defined parameters, input, output, usage, cost and level. Safe to call once with the request and again with the response; unset fields leave earlier values in place.
|
|
32
|
+
* @description Records a model request on its span as a Langfuse generation: model, defined parameters, input, output, usage, cost and level. Safe to call once with the request and again with the response; unset fields leave earlier values in place. The request is redacted once, input and the arguments of the tool calls in the output are left out when `NEUROLINK_RECORD_INPUTS=false`, and nothing is built when the span is not recording. Cost is priced on the response model when it has a price, otherwise on the requested model. A field that cannot be built is left out; this never throws into the model call.
|
|
33
33
|
* @param span {Span} - Span of one model request.
|
|
34
34
|
* @param call {GenerationRecord} - What was sent and what came back.
|
|
35
35
|
* @returns {void} Nothing.
|
|
@@ -1,47 +1,80 @@
|
|
|
1
1
|
import { createObservationAttributes, createTraceAttributes, LangfuseOtelSpanAttributes, } from "@langfuse/tracing";
|
|
2
2
|
import { WARNING_FINISH_REASONS } from "../constants/telemetry.js";
|
|
3
|
-
import {
|
|
3
|
+
import { logger } from "../utils/logger.js";
|
|
4
|
+
import { calculateCost, hasPricing } from "../utils/pricing.js";
|
|
4
5
|
import { extractTokenUsage } from "../utils/tokenUtils.js";
|
|
5
|
-
import { ATTR, spanContentAttribute, spanModelRequestAttribute, } from "./attributes.js";
|
|
6
|
-
import { assistantMessages } from "./chatMessages.js";
|
|
6
|
+
import { ATTR, isFinishedAttribute, recordsSpanContent, recordsSpanInputs, spanContentAttribute, spanModelRequestAttribute, } from "./attributes.js";
|
|
7
|
+
import { assistantMessages, withoutToolArguments } from "./chatMessages.js";
|
|
8
|
+
import { inTurnlessTree, isNestedOperation } from "./tracers.js";
|
|
9
|
+
// Recording must never fail the call it describes: a field that cannot be
|
|
10
|
+
// built is dropped and logged, and the rest of the record is still written.
|
|
11
|
+
function attempt(what, compute, fallback) {
|
|
12
|
+
try {
|
|
13
|
+
return compute();
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
logger.debug(`[telemetry] Could not record ${what}`, {
|
|
17
|
+
error: error instanceof Error ? error.message : String(error),
|
|
18
|
+
});
|
|
19
|
+
return fallback;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function contentAttribute(value) {
|
|
23
|
+
return typeof value === "string" && isFinishedAttribute(value)
|
|
24
|
+
? value
|
|
25
|
+
: spanContentAttribute(value);
|
|
26
|
+
}
|
|
7
27
|
function redactedIO(attrs) {
|
|
8
28
|
return {
|
|
9
29
|
...attrs,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
30
|
+
input: attrs.input !== undefined && recordsSpanInputs()
|
|
31
|
+
? contentAttribute(attrs.input)
|
|
32
|
+
: undefined,
|
|
33
|
+
output: attrs.output !== undefined && recordsSpanContent()
|
|
34
|
+
? contentAttribute(attrs.output)
|
|
35
|
+
: undefined,
|
|
16
36
|
};
|
|
17
37
|
}
|
|
18
38
|
/**
|
|
19
|
-
* @description Stamps Langfuse observation attributes on a span, plus a `gen_ai.observation.type` marker so the exporter pipeline recognizes it whatever its tracer scope. Input and output are credential-masked and capped at `SPAN_ATTRIBUTE_MAX_CHARS` before serialization.
|
|
39
|
+
* @description Stamps Langfuse observation attributes on a span, plus a `gen_ai.observation.type` marker so the exporter pipeline recognizes it whatever its tracer scope. Input and output are credential-masked and capped at `SPAN_ATTRIBUTE_MAX_CHARS` before serialization, and the status message is recorded as given; an attribute that `spanContentAttribute` or `spanModelRequestAttribute` already produced is not masked again, and input is left out when `NEUROLINK_RECORD_INPUTS=false`. Nothing is built when the span is not recording.
|
|
20
40
|
* @param span {Span} - Span to describe.
|
|
21
41
|
* @param type {ObservationType} - Observation type Langfuse renders the span as.
|
|
22
42
|
* @param attrs {ObservationAttributes} - Input, output, model, parameters, usage, cost, level and metadata to record.
|
|
23
43
|
* @returns {void} Nothing.
|
|
24
44
|
*/
|
|
25
45
|
export function observe(span, type, attrs) {
|
|
26
|
-
span.
|
|
27
|
-
|
|
46
|
+
if (!span.isRecording()) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
attempt("observation", () => span.setAttributes({
|
|
50
|
+
...createObservationAttributes(type, {
|
|
51
|
+
...redactedIO(attrs),
|
|
52
|
+
...(attrs.statusMessage !== undefined && {
|
|
53
|
+
statusMessage: attrs.statusMessage,
|
|
54
|
+
}),
|
|
55
|
+
}),
|
|
28
56
|
[ATTR.GEN_AI_OBSERVATION]: type,
|
|
29
|
-
});
|
|
57
|
+
}), undefined);
|
|
30
58
|
}
|
|
31
59
|
/**
|
|
32
|
-
* @description Sets the trace-level input, output and metadata on a span, credential-masked and capped like `observe
|
|
60
|
+
* @description Sets the trace-level input, output and metadata on a span, credential-masked and capped like `observe`, with input left out when `NEUROLINK_RECORD_INPUTS=false` and nothing built when the span is not recording. Each metadata entry is written as its own `langfuse.trace.metadata.<key>` attribute, redacted like input and output.
|
|
33
61
|
* @param span {Span} - Span whose trace is described.
|
|
34
62
|
* @param io {TraceContent} - Trace input, output and metadata.
|
|
35
63
|
* @returns {void} Nothing.
|
|
36
64
|
*/
|
|
37
65
|
export function observeTrace(span, io) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
for (const [key, value] of Object.entries(metadata ?? {})) {
|
|
41
|
-
if (value !== undefined) {
|
|
42
|
-
span.setAttribute(`${LangfuseOtelSpanAttributes.TRACE_METADATA}.${key}`, spanContentAttribute(value));
|
|
43
|
-
}
|
|
66
|
+
if (!span.isRecording()) {
|
|
67
|
+
return;
|
|
44
68
|
}
|
|
69
|
+
attempt("trace", () => {
|
|
70
|
+
const { metadata, ...content } = io;
|
|
71
|
+
span.setAttributes(createTraceAttributes(redactedIO(content)));
|
|
72
|
+
for (const [key, value] of Object.entries(metadata ?? {})) {
|
|
73
|
+
if (value !== undefined) {
|
|
74
|
+
span.setAttribute(`${LangfuseOtelSpanAttributes.TRACE_METADATA}.${key}`, spanContentAttribute(value));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}, undefined);
|
|
45
78
|
}
|
|
46
79
|
/**
|
|
47
80
|
* @description Maps any provider's token usage onto Langfuse usage-detail keys.
|
|
@@ -66,47 +99,103 @@ export function usageDetails(usage) {
|
|
|
66
99
|
}
|
|
67
100
|
return details;
|
|
68
101
|
}
|
|
102
|
+
// ai@6 reports `{ unified, raw }` where other providers report a string; any
|
|
103
|
+
// other shape carries no reason worth recording.
|
|
104
|
+
function finishReasonText(finishReason) {
|
|
105
|
+
if (typeof finishReason === "string") {
|
|
106
|
+
return finishReason;
|
|
107
|
+
}
|
|
108
|
+
if (typeof finishReason !== "object" || finishReason === null) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
if ("unified" in finishReason && typeof finishReason.unified === "string") {
|
|
112
|
+
return finishReason.unified;
|
|
113
|
+
}
|
|
114
|
+
return "raw" in finishReason && typeof finishReason.raw === "string"
|
|
115
|
+
? finishReason.raw
|
|
116
|
+
: undefined;
|
|
117
|
+
}
|
|
69
118
|
/**
|
|
70
119
|
* @description Picks the Langfuse level for a model call that finished without throwing.
|
|
71
|
-
* @param finishReason {
|
|
120
|
+
* @param finishReason {unknown} - Provider finish reason: a string in any casing, or an ai@6 `{ unified, raw }` object; any other value is ignored.
|
|
72
121
|
* @param aborted {boolean | undefined} - Whether the caller aborted or closed the call early.
|
|
73
122
|
* @returns {ObservationLevel | undefined} `"WARNING"` for truncated, filtered, aborted or empty output, otherwise `undefined`.
|
|
74
123
|
*/
|
|
75
124
|
export function observationLevel(finishReason, aborted) {
|
|
76
125
|
if (aborted ||
|
|
77
|
-
WARNING_FINISH_REASONS.has(finishReason?.toLowerCase() ?? "")) {
|
|
126
|
+
WARNING_FINISH_REASONS.has(finishReasonText(finishReason)?.toLowerCase() ?? "")) {
|
|
78
127
|
return "WARNING";
|
|
79
128
|
}
|
|
80
129
|
return undefined;
|
|
81
130
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
131
|
+
// The model that answered sets the price. A served model without one (an
|
|
132
|
+
// alias, a router's own name) falls back to the model that was requested.
|
|
133
|
+
function pricedModel(call) {
|
|
134
|
+
const { provider, model, responseModel } = call;
|
|
135
|
+
return provider && responseModel && hasPricing(provider, responseModel)
|
|
136
|
+
? responseModel
|
|
137
|
+
: (model ?? responseModel);
|
|
138
|
+
}
|
|
139
|
+
function generationCost(call) {
|
|
140
|
+
const model = pricedModel(call);
|
|
141
|
+
return call.usage && call.provider && model
|
|
142
|
+
? calculateCost(call.provider, model, extractTokenUsage(call.usage, { calculateCacheSavings: false }))
|
|
143
|
+
: 0;
|
|
144
|
+
}
|
|
145
|
+
function recordGeneration(span, call) {
|
|
89
146
|
const parameters = {};
|
|
90
147
|
for (const [key, value] of Object.entries(call.modelParameters ?? {})) {
|
|
91
148
|
if (value !== undefined) {
|
|
92
149
|
parameters[key] = value;
|
|
93
150
|
}
|
|
94
151
|
}
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
152
|
+
const cost = attempt("generation cost", () => generationCost(call), 0);
|
|
153
|
+
const request = call.input;
|
|
154
|
+
const input = request === undefined || !recordsSpanInputs()
|
|
155
|
+
? undefined
|
|
156
|
+
: attempt("generation input", () => spanModelRequestAttribute(request), undefined);
|
|
157
|
+
const output = attempt("generation output", () => {
|
|
158
|
+
const messages = assistantMessages(call.output);
|
|
159
|
+
if (messages === undefined) {
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
// A tool call's arguments are the tool's input, so they follow the
|
|
163
|
+
// inputs setting even though they arrive in the model's output.
|
|
164
|
+
return spanContentAttribute(recordsSpanInputs() ? messages : withoutToolArguments(messages));
|
|
165
|
+
}, undefined);
|
|
166
|
+
const finishReason = finishReasonText(call.finishReason);
|
|
99
167
|
observe(span, "generation", {
|
|
100
168
|
model: call.responseModel ?? call.model,
|
|
101
169
|
modelParameters: Object.keys(parameters).length > 0 ? parameters : undefined,
|
|
102
|
-
input
|
|
103
|
-
|
|
104
|
-
: spanModelRequestAttribute(call.input),
|
|
105
|
-
output: assistantMessages(call.output),
|
|
170
|
+
input,
|
|
171
|
+
output,
|
|
106
172
|
usageDetails: call.usage ? usageDetails(call.usage) : undefined,
|
|
107
173
|
costDetails: cost > 0 ? { total: cost } : undefined,
|
|
108
174
|
completionStartTime: call.completionStartTime,
|
|
109
|
-
level: observationLevel(
|
|
110
|
-
metadata: call.provider
|
|
175
|
+
level: observationLevel(finishReason, call.aborted),
|
|
176
|
+
metadata: call.provider || finishReason
|
|
177
|
+
? {
|
|
178
|
+
...(call.provider && { provider: call.provider }),
|
|
179
|
+
...(finishReason && { finishReason }),
|
|
180
|
+
}
|
|
181
|
+
: undefined,
|
|
111
182
|
});
|
|
183
|
+
// Inside a NeuroLink turn the turn owns the trace's input and output, and
|
|
184
|
+
// under a host span the host does. Only a direct loop that opened its own
|
|
185
|
+
// root has nothing above it, so only its generations write them.
|
|
186
|
+
if (!isNestedOperation(span) && inTurnlessTree(span)) {
|
|
187
|
+
observeTrace(span, { input, output });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* @description Records a model request on its span as a Langfuse generation: model, defined parameters, input, output, usage, cost and level. Safe to call once with the request and again with the response; unset fields leave earlier values in place. The request is redacted once, input and the arguments of the tool calls in the output are left out when `NEUROLINK_RECORD_INPUTS=false`, and nothing is built when the span is not recording. Cost is priced on the response model when it has a price, otherwise on the requested model. A field that cannot be built is left out; this never throws into the model call.
|
|
192
|
+
* @param span {Span} - Span of one model request.
|
|
193
|
+
* @param call {GenerationRecord} - What was sent and what came back.
|
|
194
|
+
* @returns {void} Nothing.
|
|
195
|
+
*/
|
|
196
|
+
export function observeGeneration(span, call) {
|
|
197
|
+
if (!span.isRecording()) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
attempt("generation", () => recordGeneration(span, call), undefined);
|
|
112
201
|
}
|
|
@@ -7,7 +7,7 @@ import type { JsonValue } from "../types/index.js";
|
|
|
7
7
|
*/
|
|
8
8
|
export declare function redactSensitiveEntry(key: string, entry: JsonValue): JsonValue;
|
|
9
9
|
/**
|
|
10
|
-
* @description Masks credentials written as `name=value`, `name: value` or as a recognizable bare key (OpenAI, GitHub, AWS, Slack). Purely numeric values after a credential-looking name are left alone.
|
|
10
|
+
* @description Masks credentials written as `name=value`, `name: value` or as a recognizable bare key (OpenAI, GitHub, AWS, Slack). Purely numeric values after a credential-looking name are left alone. Runs in time linear in the length of the text.
|
|
11
11
|
* @param text {string} - Text to scan.
|
|
12
12
|
* @returns {string} The text with credential values replaced.
|
|
13
13
|
*/
|
|
@@ -32,7 +32,7 @@ export declare function isBinaryKey(key: string): boolean;
|
|
|
32
32
|
export declare function declaredMimeType(node: object): string | undefined;
|
|
33
33
|
/**
|
|
34
34
|
* @description Describes a binary payload by size instead of content.
|
|
35
|
-
* @param value {ArrayBuffer | ArrayBufferView | string} - Raw bytes, or a base64 string long enough to be treated as bytes.
|
|
35
|
+
* @param value {ArrayBuffer | ArrayBufferView | string} - Raw bytes, or a base64 string long enough to be treated as bytes. A string made only of base64 characters and `=` counts as base64; one with whitespace inside it counts only when it is wrapped at a fixed width (or PEM-armored), so prose, word lists and name lists keep their content.
|
|
36
36
|
* @param mimeType {string | undefined} - MIME type to include in the placeholder.
|
|
37
37
|
* @returns {string | undefined} `[N bytes mime/type]`, or `undefined` when the value is not binary.
|
|
38
38
|
*/
|
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
import { BINARY_KEYS, BINARY_PAYLOAD_MIN_CHARS, REDACTED_VALUE, } from "../constants/telemetry.js";
|
|
2
2
|
const TOKEN_KEY_PATTERN = /tokens?$/;
|
|
3
|
-
|
|
3
|
+
// A run of base64 characters and `=`, with no whitespace in it.
|
|
4
|
+
const BASE64_RUN_PATTERN = /^[A-Za-z0-9+/_\-=]+$/;
|
|
5
|
+
// What every string was held to before whitespace was told apart.
|
|
6
|
+
const BASE64_OR_SPACE_PATTERN = /^[A-Za-z0-9+/_\-=\s]+$/;
|
|
7
|
+
const WHITESPACE_PATTERN = /\s/;
|
|
8
|
+
const SEGMENT_PATTERN = /\S+/g;
|
|
9
|
+
const PEM_ARMOR_PATTERN = /^-----BEGIN [A-Z0-9 ]+-----/;
|
|
10
|
+
// Encoders wrap at 16 columns or more; a narrower repeat is a list of short
|
|
11
|
+
// words or codes.
|
|
12
|
+
const MIN_WRAPPED_WIDTH = 16;
|
|
4
13
|
const MIME_TYPE_KEYS = ["mimeType", "mediaType", "media_type", "mime_type"];
|
|
5
14
|
const DATA_URL_PATTERN = new RegExp(`data:([\\w.+-]+/[\\w.+-]+)?(?:;[\\w=.+-]+)*;base64,[A-Za-z0-9+/_\\-=]{${BINARY_PAYLOAD_MIN_CHARS},}`, "g");
|
|
6
|
-
|
|
15
|
+
// A name made of word characters, dots and dashes that is followed by `=` or
|
|
16
|
+
// `:` and a value that is not a bare number. Starting only where a run begins
|
|
17
|
+
// keeps the scan linear: the run is read once, and whether it names a
|
|
18
|
+
// credential is decided afterwards on the matched run alone.
|
|
19
|
+
const ASSIGNMENT_NAME_PATTERN = /(?<![\w.-])[\w.-]+(?=\s*[=:]\s*(?!\d+\b)[^\s&"',;])/g;
|
|
20
|
+
const ASSIGNMENT_VALUE_PATTERN = /(\s*[=:]\s*)(?!\d+\b)[^\s&"',;]+/y;
|
|
21
|
+
const SECRET_NAME_PATTERN = /api[_-]?key|secret|password|passwd|token|credential|private[_-]?key|signature/i;
|
|
7
22
|
const BARE_SECRET_PATTERN = /\b(?:sk-[A-Za-z0-9_-]{16,}|gh[pousr]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,}|AKIA[0-9A-Z]{16}|xox[baprs]-[A-Za-z0-9-]{10,})/g;
|
|
8
23
|
function normalizeKey(key) {
|
|
9
24
|
return key.replace(/[_-]/g, "").toLowerCase();
|
|
@@ -33,14 +48,30 @@ export function redactSensitiveEntry(key, entry) {
|
|
|
33
48
|
: REDACTED_VALUE;
|
|
34
49
|
}
|
|
35
50
|
/**
|
|
36
|
-
* @description Masks credentials written as `name=value`, `name: value` or as a recognizable bare key (OpenAI, GitHub, AWS, Slack). Purely numeric values after a credential-looking name are left alone.
|
|
51
|
+
* @description Masks credentials written as `name=value`, `name: value` or as a recognizable bare key (OpenAI, GitHub, AWS, Slack). Purely numeric values after a credential-looking name are left alone. Runs in time linear in the length of the text.
|
|
37
52
|
* @param text {string} - Text to scan.
|
|
38
53
|
* @returns {string} The text with credential values replaced.
|
|
39
54
|
*/
|
|
40
55
|
export function redactSecretAssignments(text) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
56
|
+
const names = new RegExp(ASSIGNMENT_NAME_PATTERN);
|
|
57
|
+
const value = new RegExp(ASSIGNMENT_VALUE_PATTERN);
|
|
58
|
+
let redacted = "";
|
|
59
|
+
let copied = 0;
|
|
60
|
+
for (let name = names.exec(text); name; name = names.exec(text)) {
|
|
61
|
+
if (!SECRET_NAME_PATTERN.test(name[0])) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const nameEnd = name.index + name[0].length;
|
|
65
|
+
value.lastIndex = nameEnd;
|
|
66
|
+
const assignment = value.exec(text);
|
|
67
|
+
if (!assignment) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
redacted += `${text.slice(copied, nameEnd)}${assignment[1]}${REDACTED_VALUE}`;
|
|
71
|
+
copied = nameEnd + assignment[0].length;
|
|
72
|
+
names.lastIndex = copied;
|
|
73
|
+
}
|
|
74
|
+
return `${redacted}${text.slice(copied)}`.replace(BARE_SECRET_PATTERN, REDACTED_VALUE);
|
|
44
75
|
}
|
|
45
76
|
/**
|
|
46
77
|
* @description Replaces inline base64 data URLs with a size and MIME-type placeholder.
|
|
@@ -72,9 +103,46 @@ export function declaredMimeType(node) {
|
|
|
72
103
|
}
|
|
73
104
|
return undefined;
|
|
74
105
|
}
|
|
106
|
+
// Encoded text with whitespace in it is wrapped: every segment but the last has
|
|
107
|
+
// the same width, as MIME, PEM and `base64 -w` produce. Prose and lists of names
|
|
108
|
+
// break wherever they like, so their segments differ in length.
|
|
109
|
+
function isWrappedBase64(text) {
|
|
110
|
+
if (PEM_ARMOR_PATTERN.test(text)) {
|
|
111
|
+
return BASE64_OR_SPACE_PATTERN.test(text);
|
|
112
|
+
}
|
|
113
|
+
let width;
|
|
114
|
+
let sawShorter = false;
|
|
115
|
+
for (const [segment] of text.matchAll(SEGMENT_PATTERN)) {
|
|
116
|
+
if (!BASE64_RUN_PATTERN.test(segment)) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
if (width === undefined) {
|
|
120
|
+
width = segment.length;
|
|
121
|
+
if (width < MIN_WRAPPED_WIDTH) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else if (sawShorter || segment.length > width) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
else if (segment.length < width) {
|
|
129
|
+
sawShorter = true;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
// A string without whitespace is held to the rule it always was; one with
|
|
135
|
+
// whitespace inside it must also read as wrapped base64, which keeps prose and
|
|
136
|
+
// word lists readable. Whitespace around the ends does not count.
|
|
137
|
+
function isBase64Payload(value) {
|
|
138
|
+
const text = value.trim();
|
|
139
|
+
return WHITESPACE_PATTERN.test(text)
|
|
140
|
+
? isWrappedBase64(text)
|
|
141
|
+
: BASE64_RUN_PATTERN.test(text);
|
|
142
|
+
}
|
|
75
143
|
/**
|
|
76
144
|
* @description Describes a binary payload by size instead of content.
|
|
77
|
-
* @param value {ArrayBuffer | ArrayBufferView | string} - Raw bytes, or a base64 string long enough to be treated as bytes.
|
|
145
|
+
* @param value {ArrayBuffer | ArrayBufferView | string} - Raw bytes, or a base64 string long enough to be treated as bytes. A string made only of base64 characters and `=` counts as base64; one with whitespace inside it counts only when it is wrapped at a fixed width (or PEM-armored), so prose, word lists and name lists keep their content.
|
|
78
146
|
* @param mimeType {string | undefined} - MIME type to include in the placeholder.
|
|
79
147
|
* @returns {string | undefined} `[N bytes mime/type]`, or `undefined` when the value is not binary.
|
|
80
148
|
*/
|
|
@@ -85,7 +153,7 @@ export function binaryPlaceholder(value, mimeType) {
|
|
|
85
153
|
? `[${value.byteLength} bytes${suffix}]`
|
|
86
154
|
: undefined;
|
|
87
155
|
}
|
|
88
|
-
if (value.length > BINARY_PAYLOAD_MIN_CHARS &&
|
|
156
|
+
if (value.length > BINARY_PAYLOAD_MIN_CHARS && isBase64Payload(value)) {
|
|
89
157
|
return `[${Math.floor((value.length * 3) / 4)} bytes${suffix}]`;
|
|
90
158
|
}
|
|
91
159
|
return undefined;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description Registers the function that starts NeuroLink's tracing. The shared tracers call it through `startTelemetry`, so they depend on this module and on `@opentelemetry/api` only, and the OpenTelemetry SDK loads with the entries that register a starter.
|
|
3
|
+
* @param start {() => void} - Starts tracing once per process; safe to call repeatedly.
|
|
4
|
+
* @returns {void} Nothing.
|
|
5
|
+
*/
|
|
6
|
+
export declare function registerTelemetryStarter(start: () => void): void;
|
|
7
|
+
/**
|
|
8
|
+
* @description Starts NeuroLink's tracing through the registered starter. Without one nothing starts and spans go to whatever provider the host registered.
|
|
9
|
+
* @returns {void} Nothing.
|
|
10
|
+
*/
|
|
11
|
+
export declare function startTelemetry(): void;
|
|
12
|
+
/**
|
|
13
|
+
* @description Reports whether a TracerProvider other than the API's placeholder is registered globally, whoever registered it.
|
|
14
|
+
* @returns {boolean} `true` when spans reach a real provider.
|
|
15
|
+
*/
|
|
16
|
+
export declare function hasHostTracerProvider(): boolean;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ProxyTracerProvider, trace } from "@opentelemetry/api";
|
|
2
|
+
let starter;
|
|
3
|
+
/**
|
|
4
|
+
* @description Registers the function that starts NeuroLink's tracing. The shared tracers call it through `startTelemetry`, so they depend on this module and on `@opentelemetry/api` only, and the OpenTelemetry SDK loads with the entries that register a starter.
|
|
5
|
+
* @param start {() => void} - Starts tracing once per process; safe to call repeatedly.
|
|
6
|
+
* @returns {void} Nothing.
|
|
7
|
+
*/
|
|
8
|
+
export function registerTelemetryStarter(start) {
|
|
9
|
+
starter = start;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* @description Starts NeuroLink's tracing through the registered starter. Without one nothing starts and spans go to whatever provider the host registered.
|
|
13
|
+
* @returns {void} Nothing.
|
|
14
|
+
*/
|
|
15
|
+
export function startTelemetry() {
|
|
16
|
+
starter?.();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* @description Reports whether a TracerProvider other than the API's placeholder is registered globally, whoever registered it.
|
|
20
|
+
* @returns {boolean} `true` when spans reach a real provider.
|
|
21
|
+
*/
|
|
22
|
+
export function hasHostTracerProvider() {
|
|
23
|
+
const global = trace.getTracerProvider();
|
|
24
|
+
if (!(global instanceof ProxyTracerProvider)) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
return global.getDelegateTracer("ai") !== undefined;
|
|
28
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Extract the current OTel trace context for
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Extract the current OTel trace context for the in-process span records
|
|
3
|
+
* (`SpanSerializer`), so they carry the traceId of the exported OTEL trace.
|
|
4
|
+
* Returns undefined values when no OTel context is active.
|
|
5
5
|
*/
|
|
6
6
|
export declare function getActiveTraceContext(): {
|
|
7
7
|
traceId?: string;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { trace, context } from "@opentelemetry/api";
|
|
2
2
|
/**
|
|
3
|
-
* Extract the current OTel trace context for
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* Extract the current OTel trace context for the in-process span records
|
|
4
|
+
* (`SpanSerializer`), so they carry the traceId of the exported OTEL trace.
|
|
5
|
+
* Returns undefined values when no OTel context is active.
|
|
6
6
|
*/
|
|
7
7
|
export function getActiveTraceContext() {
|
|
8
8
|
const activeSpan = trace.getSpan(context.active());
|
|
@@ -12,7 +12,19 @@ export declare function activeOperation(): GenerationOperation | undefined;
|
|
|
12
12
|
*/
|
|
13
13
|
export declare function isNestedOperation(span: Span): boolean;
|
|
14
14
|
/**
|
|
15
|
-
* @description
|
|
15
|
+
* @description Reports whether a span belongs to the tree of a root a direct provider loop opened for itself, so neither a host span nor a NeuroLink turn sits above it.
|
|
16
|
+
* @param span {Span} - Span returned by a scrubbed tracer.
|
|
17
|
+
* @returns {boolean} `true` for that root and the spans started under it, apart from a NeuroLink turn's own subtree; `false` for everything under a host span or a turn.
|
|
18
|
+
*/
|
|
19
|
+
export declare function inTurnlessTree(span: Span): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* @description Marks the root a direct provider loop opened for itself. It names the operation its steps belong to, but no NeuroLink turn sits above it and it records no input or output, so a generation under it is still the top owner of the trace's input and output and `isNestedOperation` stays `false` for it. A NeuroLink `generate` or `stream` turn started inside it, such as a sub-agent run by a tool, is not marked: that turn and its generations stay nested.
|
|
22
|
+
* @param span {Span} - Root span returned by a scrubbed tracer, marked before any child is started.
|
|
23
|
+
* @returns {Span} The same span.
|
|
24
|
+
*/
|
|
25
|
+
export declare function markTurnlessRoot(span: Span): Span;
|
|
26
|
+
/**
|
|
27
|
+
* @description Resolves a tracer whose spans record exceptions as type and message only, without stack traces or product-name references that would reach trace backends as event metadata. Exception and status messages have credentials masked, like recorded input and output.
|
|
16
28
|
* @param name {string} - Instrumentation scope name.
|
|
17
29
|
* @returns {Tracer} A tracer delegating to the globally registered provider.
|
|
18
30
|
*/
|
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
import { context, trace, } from "@opentelemetry/api";
|
|
2
|
-
import {
|
|
2
|
+
import { startTelemetry } from "./telemetryStarter.js";
|
|
3
3
|
const PRODUCT_NAME = /neurolink/gi;
|
|
4
4
|
const OPERATION_ROOT = /^(?:provider\.)?(generate|stream)(?:\.|$)/;
|
|
5
|
+
const TURN_NAME = /^(?:generate|stream)$/;
|
|
5
6
|
const operations = new WeakMap();
|
|
6
7
|
const nestedOperations = new WeakSet();
|
|
7
|
-
|
|
8
|
+
const turnlessTrees = new WeakSet();
|
|
9
|
+
function trackOperation(span, name, scope, parent) {
|
|
8
10
|
const root = OPERATION_ROOT.exec(name)?.[1];
|
|
9
11
|
const parentOperation = parent && operations.get(parent);
|
|
10
|
-
|
|
12
|
+
// A NeuroLink turn (a sub-agent a direct loop's tool runs) stops the loop's
|
|
13
|
+
// turnless mark, so it and its generations stay nested and leave the
|
|
14
|
+
// trace's input and output to the loop.
|
|
15
|
+
const turnless = parent !== undefined &&
|
|
16
|
+
turnlessTrees.has(parent) &&
|
|
17
|
+
!(scope === "sdk" && TURN_NAME.test(name));
|
|
18
|
+
if (root && parentOperation && !turnless) {
|
|
11
19
|
nestedOperations.add(span);
|
|
12
20
|
}
|
|
21
|
+
if (turnless) {
|
|
22
|
+
turnlessTrees.add(span);
|
|
23
|
+
}
|
|
13
24
|
const operation = root ?? parentOperation;
|
|
14
25
|
if (operation) {
|
|
15
26
|
operations.set(span, operation);
|
|
@@ -32,6 +43,34 @@ export function activeOperation() {
|
|
|
32
43
|
export function isNestedOperation(span) {
|
|
33
44
|
return nestedOperations.has(span);
|
|
34
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* @description Reports whether a span belongs to the tree of a root a direct provider loop opened for itself, so neither a host span nor a NeuroLink turn sits above it.
|
|
48
|
+
* @param span {Span} - Span returned by a scrubbed tracer.
|
|
49
|
+
* @returns {boolean} `true` for that root and the spans started under it, apart from a NeuroLink turn's own subtree; `false` for everything under a host span or a turn.
|
|
50
|
+
*/
|
|
51
|
+
export function inTurnlessTree(span) {
|
|
52
|
+
return turnlessTrees.has(span);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* @description Marks the root a direct provider loop opened for itself. It names the operation its steps belong to, but no NeuroLink turn sits above it and it records no input or output, so a generation under it is still the top owner of the trace's input and output and `isNestedOperation` stays `false` for it. A NeuroLink `generate` or `stream` turn started inside it, such as a sub-agent run by a tool, is not marked: that turn and its generations stay nested.
|
|
56
|
+
* @param span {Span} - Root span returned by a scrubbed tracer, marked before any child is started.
|
|
57
|
+
* @returns {Span} The same span.
|
|
58
|
+
*/
|
|
59
|
+
export function markTurnlessRoot(span) {
|
|
60
|
+
turnlessTrees.add(span);
|
|
61
|
+
return span;
|
|
62
|
+
}
|
|
63
|
+
// An Error's name or message may be missing, not a string, or a getter that
|
|
64
|
+
// throws; recording the exception must not throw in its place.
|
|
65
|
+
function exceptionText(read, fallback) {
|
|
66
|
+
try {
|
|
67
|
+
const value = read();
|
|
68
|
+
return value === undefined || value === null ? fallback : String(value);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return fallback;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
35
74
|
function scrubException(exception) {
|
|
36
75
|
if (!(exception instanceof Error)) {
|
|
37
76
|
return typeof exception === "string"
|
|
@@ -39,8 +78,8 @@ function scrubException(exception) {
|
|
|
39
78
|
: exception;
|
|
40
79
|
}
|
|
41
80
|
return {
|
|
42
|
-
name: exception.name.replace(PRODUCT_NAME, "Ai"),
|
|
43
|
-
message: exception.message.replace(PRODUCT_NAME, "ai"),
|
|
81
|
+
name: exceptionText(() => exception.name, "Error").replace(PRODUCT_NAME, "Ai"),
|
|
82
|
+
message: exceptionText(() => exception.message, "").replace(PRODUCT_NAME, "ai"),
|
|
44
83
|
};
|
|
45
84
|
}
|
|
46
85
|
function withScrubbedExceptions(span) {
|
|
@@ -49,7 +88,7 @@ function withScrubbedExceptions(span) {
|
|
|
49
88
|
return span;
|
|
50
89
|
}
|
|
51
90
|
/**
|
|
52
|
-
* @description Resolves a tracer whose spans record exceptions as type and message only, without stack traces or product-name references that would reach trace backends as event metadata.
|
|
91
|
+
* @description Resolves a tracer whose spans record exceptions as type and message only, without stack traces or product-name references that would reach trace backends as event metadata. Exception and status messages have credentials masked, like recorded input and output.
|
|
53
92
|
* @param name {string} - Instrumentation scope name.
|
|
54
93
|
* @returns {Tracer} A tracer delegating to the globally registered provider.
|
|
55
94
|
*/
|
|
@@ -57,17 +96,17 @@ export function scrubbedTracer(name) {
|
|
|
57
96
|
const inner = trace.getTracer(name);
|
|
58
97
|
return {
|
|
59
98
|
startSpan: (spanName, options, ctx) => {
|
|
60
|
-
|
|
99
|
+
startTelemetry();
|
|
61
100
|
const span = withScrubbedExceptions(inner.startSpan(spanName, options, ctx));
|
|
62
|
-
return trackOperation(span, spanName, trace.getSpan(ctx ?? context.active()));
|
|
101
|
+
return trackOperation(span, spanName, name, trace.getSpan(ctx ?? context.active()));
|
|
63
102
|
},
|
|
64
103
|
startActiveSpan(spanName, arg2, arg3, arg4) {
|
|
65
|
-
|
|
104
|
+
startTelemetry();
|
|
66
105
|
const fn = [arg4, arg3, arg2].find((arg) => typeof arg === "function");
|
|
67
106
|
const options = typeof arg2 === "function" ? undefined : arg2;
|
|
68
107
|
const ctx = typeof arg3 === "function" ? undefined : arg3;
|
|
69
108
|
const parent = trace.getSpan(ctx ?? context.active());
|
|
70
|
-
const wrapped = ((span) => fn(trackOperation(withScrubbedExceptions(span), spanName, parent)));
|
|
109
|
+
const wrapped = ((span) => fn(trackOperation(withScrubbedExceptions(span), spanName, name, parent)));
|
|
71
110
|
if (ctx) {
|
|
72
111
|
return inner.startActiveSpan(spanName, options ?? {}, ctx, wrapped);
|
|
73
112
|
}
|