@juspay/neurolink 13.5.0 → 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.
Files changed (118) hide show
  1. package/CHANGELOG.md +3 -3
  2. package/dist/adapters/video/videoAnalyzer.d.ts +1 -0
  3. package/dist/adapters/video/videoAnalyzer.js +4 -0
  4. package/dist/autoresearch/index.d.ts +1 -0
  5. package/dist/autoresearch/index.js +4 -0
  6. package/dist/autoresearch/worker.js +4 -0
  7. package/dist/browser/neurolink.min.js +467 -467
  8. package/dist/cli/commands/observability.js +11 -4
  9. package/dist/cli/commands/telemetry.js +36 -6
  10. package/dist/cli/utils/formatters.d.ts +17 -0
  11. package/dist/cli/utils/formatters.js +39 -0
  12. package/dist/constants/langfuse.d.ts +17 -1
  13. package/dist/constants/langfuse.js +23 -1
  14. package/dist/constants/telemetry.d.ts +6 -0
  15. package/dist/constants/telemetry.js +6 -0
  16. package/dist/core/baseProvider.js +2 -0
  17. package/dist/core/geminiLoopAdapter.js +19 -0
  18. package/dist/core/loopEngine.js +10 -7
  19. package/dist/core/modules/StreamHandler.js +5 -4
  20. package/dist/core/modules/TelemetryHandler.d.ts +5 -1
  21. package/dist/core/modules/TelemetryHandler.js +16 -0
  22. package/dist/core/modules/ToolsManager.js +3 -0
  23. package/dist/core/nativeGenerateLoop.js +1 -1
  24. package/dist/evaluation/index.d.ts +1 -0
  25. package/dist/evaluation/index.js +4 -0
  26. package/dist/evaluation/ragasEvaluator.js +1 -0
  27. package/dist/mcp/externalServerManager.js +6 -2
  28. package/dist/mcp/toolRegistry.js +3 -0
  29. package/dist/neurolink.d.ts +16 -8
  30. package/dist/neurolink.js +115 -61
  31. package/dist/observability/FEATURE-STATUS.md +0 -4
  32. package/dist/observability/exporterRegistry.d.ts +6 -1
  33. package/dist/observability/exporterRegistry.js +18 -0
  34. package/dist/observability/exporterSpanProcessor.d.ts +20 -6
  35. package/dist/observability/exporterSpanProcessor.js +166 -34
  36. package/dist/observability/exporters/baseExporter.d.ts +22 -3
  37. package/dist/observability/exporters/baseExporter.js +87 -5
  38. package/dist/observability/exporters/langfuseExporter.d.ts +29 -3
  39. package/dist/observability/exporters/langfuseExporter.js +251 -41
  40. package/dist/observability/langfuseMasking.d.ts +1 -1
  41. package/dist/observability/langfuseMasking.js +1 -1
  42. package/dist/observability/otlpEncoding.d.ts +18 -5
  43. package/dist/observability/otlpEncoding.js +69 -12
  44. package/dist/observability/utils/spanSerializer.d.ts +3 -2
  45. package/dist/observability/utils/spanSerializer.js +5 -3
  46. package/dist/processors/base/BaseFileProcessor.d.ts +1 -0
  47. package/dist/processors/base/BaseFileProcessor.js +4 -0
  48. package/dist/processors/index.d.ts +1 -0
  49. package/dist/processors/index.js +4 -0
  50. package/dist/processors/registry/ProcessorRegistry.d.ts +1 -0
  51. package/dist/processors/registry/ProcessorRegistry.js +5 -0
  52. package/dist/providers/amazonBedrock/client.js +7 -5
  53. package/dist/providers/amazonSagemaker.js +32 -12
  54. package/dist/providers/anthropic/client.js +1 -0
  55. package/dist/providers/anthropic/loopAdapter.js +7 -3
  56. package/dist/providers/googleAiStudio/client.js +2 -0
  57. package/dist/providers/googleVertex/client.d.ts +6 -5
  58. package/dist/providers/googleVertex/client.js +46 -19
  59. package/dist/providers/openaiChatCompletionsBase.js +4 -4
  60. package/dist/proxy/codexUsage.js +59 -2
  61. package/dist/proxy/proxyTracer.d.ts +25 -1
  62. package/dist/proxy/proxyTracer.js +146 -22
  63. package/dist/proxy/proxyTranslationEngine.js +4 -2
  64. package/dist/proxy/sseInterceptor.js +6 -4
  65. package/dist/rag/chunkers/BaseChunker.js +1 -0
  66. package/dist/rag/index.d.ts +1 -0
  67. package/dist/rag/index.js +4 -0
  68. package/dist/rag/ragIntegration.js +1 -0
  69. package/dist/rag/reranker/reranker.js +2 -0
  70. package/dist/rag/retrieval/vectorQueryTool.js +1 -0
  71. package/dist/server/routes/codexProxyRoutes.js +3 -1
  72. package/dist/server/utils/validation.d.ts +3 -3
  73. package/dist/services/server/ai/observability/instrumentation.d.ts +8 -7
  74. package/dist/services/server/ai/observability/instrumentation.js +217 -47
  75. package/dist/services/server/ai/observability/langfuseSetup.d.ts +19 -1
  76. package/dist/services/server/ai/observability/langfuseSetup.js +62 -4
  77. package/dist/services/server/ai/observability/otlpSetup.d.ts +12 -2
  78. package/dist/services/server/ai/observability/otlpSetup.js +55 -4
  79. package/dist/telemetry/aiMetrics.d.ts +56 -0
  80. package/dist/telemetry/aiMetrics.js +229 -0
  81. package/dist/telemetry/attributes.d.ts +24 -1
  82. package/dist/telemetry/attributes.js +94 -9
  83. package/dist/telemetry/chatMessages.d.ts +6 -0
  84. package/dist/telemetry/chatMessages.js +37 -0
  85. package/dist/telemetry/index.d.ts +1 -1
  86. package/dist/telemetry/index.js +1 -1
  87. package/dist/telemetry/observation.d.ts +5 -5
  88. package/dist/telemetry/observation.js +128 -39
  89. package/dist/telemetry/spanSanitizer.d.ts +2 -2
  90. package/dist/telemetry/spanSanitizer.js +76 -8
  91. package/dist/telemetry/telemetryStarter.d.ts +16 -0
  92. package/dist/telemetry/telemetryStarter.js +28 -0
  93. package/dist/telemetry/traceContext.d.ts +3 -3
  94. package/dist/telemetry/traceContext.js +3 -3
  95. package/dist/telemetry/tracers.d.ts +13 -1
  96. package/dist/telemetry/tracers.js +49 -10
  97. package/dist/telemetry/withSpan.d.ts +19 -3
  98. package/dist/telemetry/withSpan.js +149 -20
  99. package/dist/types/exporter.d.ts +98 -3
  100. package/dist/types/livekit.d.ts +16 -1
  101. package/dist/types/loopEngine.d.ts +16 -1
  102. package/dist/types/observability.d.ts +38 -5
  103. package/dist/types/proxy.d.ts +2 -0
  104. package/dist/types/span.d.ts +26 -0
  105. package/dist/utils/timeout.js +4 -2
  106. package/dist/voice/livekit/realtimeMcpTools.js +11 -2
  107. package/dist/voice/livekit/realtimeMemory.d.ts +20 -2
  108. package/dist/voice/livekit/realtimeMemory.js +139 -46
  109. package/dist/voice/livekit/realtimeVoiceAgent.js +20 -8
  110. package/dist/voice/livekit/turnRecorder.js +22 -9
  111. package/dist/workflow/core/ensembleExecutor.js +2 -0
  112. package/dist/workflow/core/judgeScorer.js +1 -0
  113. package/dist/workflow/core/responseConditioner.js +1 -0
  114. package/dist/workflow/core/workflowRunner.js +6 -1
  115. package/dist/workflow/index.d.ts +1 -0
  116. package/dist/workflow/index.js +4 -0
  117. package/docs-site/static/search-index.json +32 -27
  118. package/package.json +21 -2
@@ -1,5 +1,13 @@
1
1
  import { type Span } from "@opentelemetry/api";
2
- import type { GenerationRecord, SpanOptions } from "../types/index.js";
2
+ import type { GenerationOperation, GenerationRecord, SpanOptions } from "../types/index.js";
3
+ export declare function markFailure(span: Span, error: unknown, expected?: boolean): void;
4
+ /**
5
+ * @description Ends `span` at WARNING once `stream` is garbage collected while the span is still open, for a stream result whose caller dropped it without reading it to the end or closing it. Best effort: it runs only if and when the collector reclaims `stream`, and a span that already ended is left alone.
6
+ * @param stream {object} - The iterable handed to the caller; `span` must not keep it reachable.
7
+ * @param span {Span} - Span that reading or closing `stream` would have ended.
8
+ * @returns {void} Nothing.
9
+ */
10
+ export declare function endSpanWhenDropped(stream: object, span: Span): void;
3
11
  export declare function withSpan<T>(options: SpanOptions, fn: (span: Span) => Promise<T>): Promise<T>;
4
12
  export declare function withClientSpan<T>(options: Omit<SpanOptions, "kind">, fn: (span: Span) => Promise<T>): Promise<T>;
5
13
  /**
@@ -40,9 +48,15 @@ export declare function withStepSpan<T>(stepNumber: number, fn: () => Promise<T>
40
48
  * @description Groups the observations of each agentic loop step under one `step` span. Call `begin` at the top of a step, run the step's model call and tool dispatch through `run`, and `end` when the loop is over; `begin` closes the previous step.
41
49
  */
42
50
  export declare class StepSpans {
51
+ private readonly operation?;
43
52
  private current;
53
+ private root;
54
+ /**
55
+ * @param operation {GenerationOperation} - Public API the loop serves, naming the root opened when no span is active; inferred from the enclosing span when unset, `generate` when there is none.
56
+ */
57
+ constructor(operation?: GenerationOperation | undefined);
44
58
  /**
45
- * @description Closes the previous step and opens `step` under the active span.
59
+ * @description Closes the previous step and opens `step` under the active span. When no span is active, the first step also opens a `generate` or `stream` root, so a loop run outside NeuroLink and outside any host span stays in one trace instead of starting a trace per step.
46
60
  * @param stepNumber {number} - One-based step number within the turn.
47
61
  * @returns {void} Nothing.
48
62
  */
@@ -54,8 +68,10 @@ export declare class StepSpans {
54
68
  */
55
69
  run<T>(fn: () => T): T;
56
70
  /**
57
- * @description Closes the open step, if any.
71
+ * @description Closes the open step, and the root when this loop opened one.
58
72
  * @returns {void} Nothing.
59
73
  */
60
74
  end(): void;
75
+ private endStep;
76
+ private startOrphanedRoot;
61
77
  }
@@ -1,12 +1,40 @@
1
1
  import { LangfuseOtelSpanAttributes } from "@langfuse/tracing";
2
- import { context, SpanKind, SpanStatusCode, trace, } from "@opentelemetry/api";
2
+ import { context, SpanKind, SpanStatusCode, isSpanContextValid, trace, } from "@opentelemetry/api";
3
3
  import { isAbortError } from "../utils/errorHandling.js";
4
4
  import { observe, observeGeneration } from "./observation.js";
5
5
  import { generationName, stepSpanName } from "./observationNames.js";
6
- import { tracers } from "./tracers.js";
7
- function markFailure(span, error, expected = false) {
8
- const message = error instanceof Error ? error.message : String(error);
9
- if (isAbortError(error)) {
6
+ import { markTurnlessRoot, tracers } from "./tracers.js";
7
+ // The thrown value is the caller's, and reading it can throw as well (a
8
+ // Proxy, a getter or toString that throws). Failure recording reads it through
9
+ // these, and whatever it still cannot record is skipped, so the caller always
10
+ // rethrows its own error.
11
+ function failureText(error) {
12
+ try {
13
+ return String(error instanceof Error ? error.message : error);
14
+ }
15
+ catch {
16
+ return "unreadable error";
17
+ }
18
+ }
19
+ function readsAsAbort(error) {
20
+ try {
21
+ return isAbortError(error);
22
+ }
23
+ catch {
24
+ return false;
25
+ }
26
+ }
27
+ export function markFailure(span, error, expected = false) {
28
+ try {
29
+ recordFailure(span, error, expected);
30
+ }
31
+ catch {
32
+ // The span keeps whatever was recorded before the value could not be read.
33
+ }
34
+ }
35
+ function recordFailure(span, error, expected) {
36
+ const message = failureText(error);
37
+ if (readsAsAbort(error)) {
10
38
  span.setAttributes({
11
39
  [LangfuseOtelSpanAttributes.OBSERVATION_LEVEL]: "WARNING",
12
40
  [LangfuseOtelSpanAttributes.OBSERVATION_STATUS_MESSAGE]: message,
@@ -24,9 +52,50 @@ function markFailure(span, error, expected = false) {
24
52
  span.recordException(error);
25
53
  }
26
54
  }
55
+ // A stream result its caller drops without reading it to the end or closing
56
+ // it can no longer end its span, so the span is ended here once the result is
57
+ // garbage collected. Best effort: collection may come late or never.
58
+ const droppedStreams = new FinalizationRegistry((span) => {
59
+ try {
60
+ if (span.isRecording()) {
61
+ span.setAttributes({
62
+ [LangfuseOtelSpanAttributes.OBSERVATION_LEVEL]: "WARNING",
63
+ [LangfuseOtelSpanAttributes.OBSERVATION_STATUS_MESSAGE]: "stream dropped before it was read to the end",
64
+ });
65
+ span.end();
66
+ }
67
+ }
68
+ catch {
69
+ // A finalizer has no caller to report to.
70
+ }
71
+ });
72
+ /**
73
+ * @description Ends `span` at WARNING once `stream` is garbage collected while the span is still open, for a stream result whose caller dropped it without reading it to the end or closing it. Best effort: it runs only if and when the collector reclaims `stream`, and a span that already ended is left alone.
74
+ * @param stream {object} - The iterable handed to the caller; `span` must not keep it reachable.
75
+ * @param span {Span} - Span that reading or closing `stream` would have ended.
76
+ * @returns {void} Nothing.
77
+ */
78
+ export function endSpanWhenDropped(stream, span) {
79
+ droppedStreams.register(stream, span);
80
+ }
81
+ function startsOrphanedRoot(options) {
82
+ if (!options.detached || !options.rootWhenOrphaned) {
83
+ return false;
84
+ }
85
+ const active = trace.getSpan(context.active());
86
+ return !active || !isSpanContextValid(active.spanContext());
87
+ }
27
88
  export async function withSpan(options, fn) {
28
- const { name, tracer, kind = SpanKind.INTERNAL, attributes, detached, } = options;
89
+ const { name, tracer, kind = SpanKind.INTERNAL, attributes, detached, observationType, } = options;
90
+ const asRoot = startsOrphanedRoot(options);
29
91
  const run = async (span) => {
92
+ if (asRoot) {
93
+ markTurnlessRoot(span);
94
+ observe(span, "agent", {});
95
+ }
96
+ else if (observationType) {
97
+ observe(span, observationType, {});
98
+ }
30
99
  if (attributes) {
31
100
  for (const [key, value] of Object.entries(attributes)) {
32
101
  if (value !== undefined) {
@@ -47,7 +116,7 @@ export async function withSpan(options, fn) {
47
116
  span.end();
48
117
  }
49
118
  };
50
- return detached
119
+ return detached && !asRoot
51
120
  ? run(tracer.startSpan(name, { kind }))
52
121
  : tracer.startActiveSpan(name, { kind }, run);
53
122
  }
@@ -72,8 +141,16 @@ export async function withClientSpan(options, fn) {
72
141
  * ```
73
142
  */
74
143
  export async function withStreamSpan(options, fn, getIterable, setIterable) {
75
- const { name, tracer, kind = SpanKind.CLIENT, attributes, detached, } = options;
144
+ const { name, tracer, kind = SpanKind.CLIENT, attributes, detached, observationType, } = options;
145
+ const asRoot = startsOrphanedRoot(options);
76
146
  const span = tracer.startSpan(name, { kind });
147
+ if (asRoot) {
148
+ markTurnlessRoot(span);
149
+ observe(span, "agent", {});
150
+ }
151
+ else if (observationType) {
152
+ observe(span, observationType, {});
153
+ }
77
154
  if (attributes) {
78
155
  for (const [key, value] of Object.entries(attributes)) {
79
156
  if (value !== undefined) {
@@ -81,7 +158,7 @@ export async function withStreamSpan(options, fn, getIterable, setIterable) {
81
158
  }
82
159
  }
83
160
  }
84
- const spanContext = detached
161
+ const spanContext = detached && !asRoot
85
162
  ? context.active()
86
163
  : trace.setSpan(context.active(), span);
87
164
  let result;
@@ -93,7 +170,15 @@ export async function withStreamSpan(options, fn, getIterable, setIterable) {
93
170
  span.end();
94
171
  throw error;
95
172
  }
96
- const iterator = getIterable(result)[Symbol.asyncIterator]();
173
+ let iterator;
174
+ try {
175
+ iterator = getIterable(result)[Symbol.asyncIterator]();
176
+ }
177
+ catch (error) {
178
+ markFailure(span, error);
179
+ span.end();
180
+ throw error;
181
+ }
97
182
  let ended = false;
98
183
  let started = false;
99
184
  function end(status) {
@@ -134,8 +219,11 @@ export async function withStreamSpan(options, fn, getIterable, setIterable) {
134
219
  return step;
135
220
  }
136
221
  catch (err) {
137
- markFailure(span, err);
138
- end();
222
+ // A return() that raced this pull has already ended the span.
223
+ if (!ended) {
224
+ markFailure(span, err);
225
+ end();
226
+ }
139
227
  throw err;
140
228
  }
141
229
  },
@@ -144,13 +232,31 @@ export async function withStreamSpan(options, fn, getIterable, setIterable) {
144
232
  return { done: true, value: undefined };
145
233
  },
146
234
  async throw(error) {
147
- await close();
148
- throw error;
235
+ const forward = iterator.throw;
236
+ if (ended || forward === undefined) {
237
+ await close();
238
+ throw error;
239
+ }
240
+ try {
241
+ const step = await context.with(spanContext, () => forward.call(iterator, error));
242
+ if (step.done) {
243
+ end({ code: SpanStatusCode.OK });
244
+ }
245
+ return step;
246
+ }
247
+ catch (err) {
248
+ if (!ended) {
249
+ markFailure(span, err);
250
+ end();
251
+ }
252
+ throw err;
253
+ }
149
254
  },
150
255
  [Symbol.asyncIterator]() {
151
256
  return wrapped;
152
257
  },
153
258
  };
259
+ endSpanWhenDropped(wrapped, span);
154
260
  return setIterable(result, wrapped);
155
261
  }
156
262
  /**
@@ -189,17 +295,24 @@ export async function withStepSpan(stepNumber, fn) {
189
295
  * @description Groups the observations of each agentic loop step under one `step` span. Call `begin` at the top of a step, run the step's model call and tool dispatch through `run`, and `end` when the loop is over; `begin` closes the previous step.
190
296
  */
191
297
  export class StepSpans {
298
+ operation;
192
299
  current;
300
+ root;
301
+ /**
302
+ * @param operation {GenerationOperation} - Public API the loop serves, naming the root opened when no span is active; inferred from the enclosing span when unset, `generate` when there is none.
303
+ */
304
+ constructor(operation) {
305
+ this.operation = operation;
306
+ }
193
307
  /**
194
- * @description Closes the previous step and opens `step` under the active span.
308
+ * @description Closes the previous step and opens `step` under the active span. When no span is active, the first step also opens a `generate` or `stream` root, so a loop run outside NeuroLink and outside any host span stays in one trace instead of starting a trace per step.
195
309
  * @param stepNumber {number} - One-based step number within the turn.
196
310
  * @returns {void} Nothing.
197
311
  */
198
312
  begin(stepNumber) {
199
- this.end();
200
- this.current = tracers.sdk.startSpan(stepSpanName(), {
201
- kind: SpanKind.INTERNAL,
202
- });
313
+ this.endStep();
314
+ this.root ??= this.startOrphanedRoot();
315
+ this.current = tracers.sdk.startSpan(stepSpanName(), { kind: SpanKind.INTERNAL }, this.root ? trace.setSpan(context.active(), this.root) : undefined);
203
316
  observe(this.current, "span", { metadata: { step_index: stepNumber } });
204
317
  }
205
318
  /**
@@ -214,11 +327,27 @@ export class StepSpans {
214
327
  : fn();
215
328
  }
216
329
  /**
217
- * @description Closes the open step, if any.
330
+ * @description Closes the open step, and the root when this loop opened one.
218
331
  * @returns {void} Nothing.
219
332
  */
220
333
  end() {
334
+ this.endStep();
335
+ this.root?.end();
336
+ this.root = undefined;
337
+ }
338
+ endStep() {
221
339
  this.current?.end();
222
340
  this.current = undefined;
223
341
  }
342
+ startOrphanedRoot() {
343
+ const active = trace.getSpan(context.active());
344
+ if (active && isSpanContextValid(active.spanContext())) {
345
+ return undefined;
346
+ }
347
+ const root = markTurnlessRoot(tracers.sdk.startSpan(generationName(this.operation), {
348
+ kind: SpanKind.INTERNAL,
349
+ }));
350
+ observe(root, "agent", {});
351
+ return root;
352
+ }
224
353
  }
@@ -1,3 +1,5 @@
1
+ import type { Agent as HttpAgent } from "node:http";
2
+ import type { Agent as HttpsAgent } from "node:https";
1
3
  import type { JsonValue } from "./common.js";
2
4
  import type { SpanAttributes } from "./span.js";
3
5
  /**
@@ -54,6 +56,32 @@ export type ExporterHealthStatus = {
54
56
  pendingSpans: number;
55
57
  errors?: string[];
56
58
  };
59
+ /**
60
+ * Point-in-time state of one registered exporter, as `getTelemetryStatus()` and
61
+ * the CLI status commands report it. `pendingSpans` counts spans queued and not
62
+ * yet handed to the network; `deliveredSpans` and `failedSpans` count what the
63
+ * backend accepted and what was dropped since the process started.
64
+ */
65
+ export type ExporterStatus = {
66
+ name: string;
67
+ enabled: boolean;
68
+ healthy: boolean;
69
+ pendingSpans: number;
70
+ deliveredSpans: number;
71
+ failedSpans: number;
72
+ /** ISO timestamp of the last export that delivered spans */
73
+ lastExportTime?: string;
74
+ errors?: string[];
75
+ };
76
+ /**
77
+ * What an exporter has delivered and dropped, and why its latest delivery
78
+ * failed, as `getStatuses()` reports it.
79
+ */
80
+ export type ExporterDeliveryCounts = {
81
+ delivered: number;
82
+ failed: number;
83
+ lastError: string | undefined;
84
+ };
57
85
  /**
58
86
  * Langfuse exporter configuration
59
87
  */
@@ -70,16 +98,24 @@ export type LangfuseExporterConfig = ExporterConfig & {
70
98
  * When true, `input` and `output` fields are omitted from exported spans and
71
99
  * generations. Enable in compliance-sensitive deployments where prompt/response
72
100
  * content is considered PII or subject to data-minimisation requirements.
73
- * Defaults to false (input/output are exported).
101
+ * Defaults to false (input/output are exported). It acts on what this
102
+ * exporter sends to Langfuse and nowhere else.
74
103
  */
75
104
  redactIO?: boolean;
76
105
  /**
77
- * Applied to input, output and metadata before they are sent. When it
78
- * throws, the value is replaced by `<fully masked>`.
106
+ * Applied to input, output and metadata before they are sent to Langfuse.
107
+ * When it throws, the value is replaced by `<fully masked>`. It acts on what
108
+ * this exporter sends and nowhere else: not on other exporters, the OTLP
109
+ * export, `getSpans()` or error text.
79
110
  */
80
111
  mask?: (params: {
81
112
  data: JsonValue;
82
113
  }) => JsonValue;
114
+ /**
115
+ * Milliseconds `flush()` and `shutdown()` wait for delivery before they
116
+ * give up on what is still in flight and drop it. Defaults to 3000.
117
+ */
118
+ flushBudgetMs?: number;
83
119
  };
84
120
  /**
85
121
  * LangSmith exporter configuration
@@ -285,6 +321,17 @@ export type OtlpKeyValue = {
285
321
  key: string;
286
322
  value: OtlpAnyValue;
287
323
  };
324
+ /** @description How `createExporterSpanProcessor` treats the exporters of its registry. */
325
+ export type ExporterSpanProcessorOptions = {
326
+ /** Whether the processor's `shutdown()` also shuts down the registry's exporters. Defaults to `true`; `false` flushes them and leaves them running. */
327
+ shutdownExporters?: boolean;
328
+ };
329
+ /** @description An OTLP/JSON link from one span to another. */
330
+ export type OtlpLink = {
331
+ traceId: string;
332
+ spanId: string;
333
+ attributes: OtlpKeyValue[];
334
+ };
288
335
  /** @description An OTLP/JSON span as Langfuse's ingestion endpoint reads it. */
289
336
  export type OtlpSpan = {
290
337
  traceId: string;
@@ -300,12 +347,60 @@ export type OtlpSpan = {
300
347
  timeUnixNano: string;
301
348
  attributes: OtlpKeyValue[];
302
349
  }[];
350
+ links?: OtlpLink[];
303
351
  status: {
304
352
  code: number;
305
353
  message?: string;
306
354
  };
307
355
  };
356
+ /** @description An encoded span together with the resource and instrumentation scope it was produced under. */
357
+ export type OtlpEncodedSpan = {
358
+ span: OtlpSpan;
359
+ resource: OtlpKeyValue[];
360
+ scope: {
361
+ name: string;
362
+ version?: string;
363
+ };
364
+ };
365
+ /** @description The spans one instrumentation scope produced, as an OTLP/JSON request groups them. */
366
+ export type OtlpScopeSpans = {
367
+ scope: {
368
+ name: string;
369
+ version?: string;
370
+ };
371
+ spans: OtlpSpan[];
372
+ };
373
+ /** @description An OTLP/JSON trace export request body. */
374
+ export type OtlpTraceRequest = {
375
+ resourceSpans: {
376
+ resource: {
377
+ attributes: OtlpKeyValue[];
378
+ };
379
+ scopeSpans: OtlpScopeSpans[];
380
+ }[];
381
+ };
382
+ /** @description What one delivery attempt of a batch to Langfuse achieved. */
383
+ export type LangfuseDeliveryReport = {
384
+ delivered: number;
385
+ rejected: number;
386
+ droppedIds: string[];
387
+ error?: string;
388
+ };
389
+ /** @description How Langfuse answered one export request: it took the spans (some perhaps rejected) or it did not take them. */
390
+ export type LangfusePostOutcome = {
391
+ accepted: true;
392
+ rejected: number;
393
+ } | {
394
+ accepted: false;
395
+ };
308
396
  /** @description What the Langfuse exporter adds to, masks in and removes from span attributes before sending. */
309
397
  export type LangfuseAttributePolicy = Pick<LangfuseExporterConfig, "environment" | "release" | "mask"> & {
310
398
  redactIO: boolean;
311
399
  };
400
+ /** @description The connections NeuroLink's OTLP exporters open, which a flush that has run out of its budget can close. */
401
+ export type OtlpConnections = {
402
+ /** Hands an exporter the agent it opens its connections with. */
403
+ agentFor: (protocol: string) => HttpAgent | HttpsAgent;
404
+ /** Closes every connection now, and again once a retry that was asleep at that moment has sent. */
405
+ cut: () => void;
406
+ };
@@ -613,16 +613,18 @@ export type VoiceTurnRecorder = {
613
613
  * Call it once, when the agent is about to speak its greeting.
614
614
  */
615
615
  expectGreeting: () => void;
616
- /** A tool call started in the current exchange. */
616
+ /** A tool call started in the current exchange. `callId` pairs it with its end when calls overlap. */
617
617
  toolStart: (call: {
618
618
  name: string;
619
619
  args?: unknown;
620
+ callId?: string;
620
621
  }) => void;
621
622
  /** A tool call finished in the current exchange. */
622
623
  toolEnd: (call: {
623
624
  name: string;
624
625
  result?: unknown;
625
626
  error?: string;
627
+ callId?: string;
626
628
  }) => void;
627
629
  /**
628
630
  * Shutdown: close what is open, then wait for pending writes — but no longer
@@ -632,6 +634,15 @@ export type VoiceTurnRecorder = {
632
634
  /** Counters, for logs and tests. */
633
635
  stats: () => VoiceTurnRecorderStats;
634
636
  };
637
+ /** Who and what a realtime turn is traced as; `userId` is set only for a verified binding. */
638
+ export type RealtimeTracedCall = {
639
+ sessionId?: string;
640
+ userId?: string;
641
+ roomName?: string;
642
+ model?: string;
643
+ /** The provider of the realtime backend (`vertex` for Gemini Live, `xai` for Grok Voice); `vertex` when absent. */
644
+ provider?: string;
645
+ };
635
646
  /** What a realtime call needs from its memory once its binding verified. */
636
647
  export type RealtimeCallMemory = {
637
648
  recorder: VoiceTurnRecorder;
@@ -651,6 +662,8 @@ export type VoiceTurnRecorderStats = {
651
662
  export type VoiceTurnRecorderOptions = {
652
663
  /** Stores one exchange. Throwing means "not stored"; the recorder retries. */
653
664
  write: (record: VoiceTurnRecord) => Promise<void>;
665
+ /** An exchange closed: called once per exchange, in order, before its write is queued, so it never waits behind earlier writes. */
666
+ onClosed?: (record: VoiceTurnRecord) => void;
654
667
  /** A write succeeded. */
655
668
  onRecorded?: (record: VoiceTurnRecord) => void;
656
669
  /** A write failed after every retry. The call continues. */
@@ -771,11 +784,13 @@ export type BuildRealtimeMcpToolsParams = {
771
784
  onToolStart?: (call: {
772
785
  name: string;
773
786
  args: Record<string, unknown>;
787
+ callId?: string;
774
788
  }) => void;
775
789
  /** Observes a tool call as it ends, with its result as text or its error. */
776
790
  onToolEnd?: (call: {
777
791
  name: string;
778
792
  result?: string;
779
793
  error?: string;
794
+ callId?: string;
780
795
  }) => void;
781
796
  };
@@ -3,7 +3,7 @@ import type { Span } from "@opentelemetry/api";
3
3
  import type { Tool } from "./tools.js";
4
4
  import type { GenerateStopReason } from "./generate.js";
5
5
  import type { UnknownRecord } from "./common.js";
6
- import type { GenerationRecord } from "./observability.js";
6
+ import type { GenerationOperation, GenerationRecord } from "./observability.js";
7
7
  import type { CollectedChunkResult, NativeFunctionCall, NativeToolDeclarationsResult } from "./providers.js";
8
8
  import type { ExecutionControlDecision, ExecutionControlStepContext } from "./stream.js";
9
9
  /**
@@ -213,6 +213,15 @@ export type AgenticLoopAdapter<TConversation = unknown, TRaw = unknown> = {
213
213
  */
214
214
  buildToolResultMessages(conversation: TConversation, stepResult: AgenticLoopStepResult<TRaw>, toolResults: AgenticLoopToolCallResult[], step: number): TConversation;
215
215
  mapFinishReason(rawStopReason: string | undefined, hadToolCalls: boolean): string;
216
+ /**
217
+ * Optional: maps a step's usage onto what its generation observation
218
+ * prices, where `inputTokens` excludes cache reads and `outputTokens`
219
+ * includes reasoning. Only for providers whose API reports otherwise
220
+ * (Gemini counts cached tokens inside the prompt count and thinking tokens
221
+ * outside the candidate count). The turn totals the engine returns are not
222
+ * touched: callers already reconcile those once.
223
+ */
224
+ observedUsage?(usage: AgenticLoopUsage): AgenticLoopUsage;
216
225
  /** Optional: in-turn context-budget reclaim, called once per step before buildStepRequest. */
217
226
  planReclaim?(conversation: TConversation, step: number): AgenticLoopReclaimResult<TConversation> | undefined;
218
227
  /** Optional: Vertex+Gemini-only single-retry-on-malformed-call. */
@@ -528,6 +537,12 @@ export type AgenticLoopOptions = {
528
537
  execute?: (args: Record<string, unknown>, opts: unknown) => Promise<unknown>;
529
538
  }>;
530
539
  abortSignal?: AbortSignal;
540
+ /**
541
+ * Public API the turn serves, used only to name the root observation the
542
+ * loop opens when it runs with no active span. Inferred from the enclosing
543
+ * span when omitted, and `generate` when there is none.
544
+ */
545
+ operation?: GenerationOperation;
531
546
  /**
532
547
  * Span the per-step provider retry annotates, via
533
548
  * `withProviderRetry(..., span, ...)` — it records
@@ -3,7 +3,7 @@
3
3
  * These configs are passed from the parent application (e.g., Lighthouse)
4
4
  * to enable telemetry and observability features in Neurolink SDK
5
5
  */
6
- import type { AttributeValue } from "@opentelemetry/api";
6
+ import type { AttributeValue, Counter, Histogram, Meter } from "@opentelemetry/api";
7
7
  import type { JsonValue, RawUsageObject, Unknown } from "./common.js";
8
8
  import type { SpanData } from "./span.js";
9
9
  /**
@@ -37,20 +37,26 @@ export type LangfuseSpanAttributes = {
37
37
  * @description Langfuse exporter options. Every field falls back to its `LANGFUSE_*` environment variable when omitted.
38
38
  */
39
39
  export type NeurolinkLangfuseConfig = {
40
+ /** `false` keeps Langfuse export off although keys are present; any other value, or leaving it out, leaves it on. */
41
+ enabled?: boolean;
40
42
  /** Project public key; defaults to `LANGFUSE_PUBLIC_KEY`. */
41
43
  publicKey?: string;
42
44
  /** Project secret key; defaults to `LANGFUSE_SECRET_KEY`. */
43
45
  secretKey?: string;
44
46
  /** Langfuse host; defaults to `LANGFUSE_BASE_URL`, then Langfuse Cloud. */
45
47
  baseUrl?: string;
46
- /** Environment name; defaults to `LANGFUSE_TRACING_ENVIRONMENT`. */
48
+ /** Environment name on Langfuse traces; defaults to `LANGFUSE_TRACING_ENVIRONMENT`. The OTLP resource's `deployment.environment` also falls back to `LANGFUSE_ENVIRONMENT`, then `PUBLIC_APP_ENVIRONMENT`. */
47
49
  environment?: string;
48
- /** Release identifier; defaults to `LANGFUSE_RELEASE`. */
50
+ /** Release identifier on Langfuse traces; defaults to `LANGFUSE_RELEASE`. The OTLP resource's `service.version` also falls back to `PUBLIC_APP_VERSION`. */
49
51
  release?: string;
50
- /** Applied to input, output and metadata before they leave the process. */
52
+ /** Applied to input, output and metadata before they are sent to Langfuse. It does not reach other exporters, the OTLP export, `getSpans()` or error text. */
51
53
  mask?: (params: {
52
54
  data: JsonValue;
53
55
  }) => JsonValue;
56
+ /** Leaves input and output out of what is sent to Langfuse. It does not reach other exporters, the OTLP export, `getSpans()` or error text. */
57
+ redactIO?: boolean;
58
+ /** Milliseconds a flush or shutdown waits for the Langfuse exporter before it drops what is still in flight; defaults to 3000. */
59
+ flushBudgetMs?: number;
54
60
  /** `"immediate"` sends every span as it ends; `"batched"` (default) queues them. */
55
61
  exportMode?: "batched" | "immediate";
56
62
  /** Spans queued before a batch is sent; defaults to 15. */
@@ -59,7 +65,26 @@ export type NeurolinkLangfuseConfig = {
59
65
  flushInterval?: number;
60
66
  };
61
67
  /**
62
- * @description Observability configuration for the NeuroLink SDK. Langfuse turns on automatically when `LANGFUSE_PUBLIC_KEY` and `LANGFUSE_SECRET_KEY` are set; `false` turns it off.
68
+ * @description Kind of connection counted by the `connections_total` metric.
69
+ */
70
+ export type MetricConnectionType = "websocket" | "sse" | "http";
71
+ /**
72
+ * @description The OpenTelemetry instruments behind NeuroLink's `ai_*`, `mcp_*`, `connections_total`, `response_time_ms` and `custom_*` metrics, created on one meter.
73
+ */
74
+ export type AiMetricInstruments = {
75
+ meter: Meter;
76
+ requests: Counter;
77
+ duration: Histogram;
78
+ tokens: Counter;
79
+ cost: Counter;
80
+ providerErrors: Counter;
81
+ mcpToolCalls: Counter;
82
+ connections: Counter;
83
+ responseTime: Histogram;
84
+ custom: Map<string, Counter | Histogram>;
85
+ };
86
+ /**
87
+ * @description Observability configuration for the NeuroLink SDK. Langfuse turns on automatically when `LANGFUSE_PUBLIC_KEY` and `LANGFUSE_SECRET_KEY` are set and `LANGFUSE_ENABLED` is not `false`, `0`, `off` or `no`; `false` turns it off.
63
88
  */
64
89
  export type ObservabilityConfig = {
65
90
  /** `false` disables Langfuse, `true` or an options object enables it. */
@@ -387,12 +412,20 @@ export type HealthMetrics = {
387
412
  export type SpanOptions = {
388
413
  name: string;
389
414
  tracer: import("@opentelemetry/api").Tracer;
415
+ /** Marks the span as an observation of this type when it starts, which is what sends it to the exporters. Without it a span is exported only if its own code marks it with `observe`. */
416
+ observationType?: ObservationType;
390
417
  kind?: import("@opentelemetry/api").SpanKind;
391
418
  attributes?: Record<string, string | number | boolean | undefined>;
392
419
  /** Marks a thrown error as an expected skip whose Langfuse level the span set itself, so `withSpan` keeps it instead of raising ERROR. */
393
420
  expectedFailure?: (error: unknown) => boolean;
394
421
  /** Keeps the span out of the active context, so spans started inside `fn` parent to the nearest ancestor that is exported. */
395
422
  detached?: boolean;
423
+ /**
424
+ * With `detached`, lets the span be the active, exported root of its trace
425
+ * when no span is active, so what it wraps stays in one trace instead of
426
+ * each child starting its own. With an active ancestor it stays detached.
427
+ */
428
+ rootWhenOrphaned?: boolean;
396
429
  };
397
430
  /** Trace + parent span IDs used to correlate metric records with spans. */
398
431
  export type MetricsTraceContext = {
@@ -2478,6 +2478,8 @@ export type CodexStreamEvidence = {
2478
2478
  terminalBytes: number;
2479
2479
  /** Malformed, oversized or undispatched frames prevent proving output absence. */
2480
2480
  observationIncomplete?: boolean;
2481
+ /** Output items past the recorded-size cap were left out of `response.output`. */
2482
+ outputItemsOmitted?: boolean;
2481
2483
  firstUsefulOutputAt?: number;
2482
2484
  firstUsefulOutputEvent?: string;
2483
2485
  errorType?: string;