@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.
Files changed (145) hide show
  1. package/CHANGELOG.md +2 -3
  2. package/README.md +3 -2
  3. package/dist/adapters/video/videoAnalyzer.d.ts +1 -0
  4. package/dist/adapters/video/videoAnalyzer.js +4 -0
  5. package/dist/autoresearch/index.d.ts +1 -0
  6. package/dist/autoresearch/index.js +4 -0
  7. package/dist/autoresearch/worker.js +4 -0
  8. package/dist/browser/neurolink.min.js +466 -466
  9. package/dist/cli/commands/decide.js +25 -5
  10. package/dist/cli/commands/observability.js +11 -4
  11. package/dist/cli/commands/telemetry.js +36 -6
  12. package/dist/cli/utils/formatters.d.ts +17 -0
  13. package/dist/cli/utils/formatters.js +39 -0
  14. package/dist/constants/langfuse.d.ts +17 -1
  15. package/dist/constants/langfuse.js +23 -1
  16. package/dist/constants/telemetry.d.ts +6 -0
  17. package/dist/constants/telemetry.js +6 -0
  18. package/dist/core/baseProvider.js +2 -0
  19. package/dist/core/geminiLoopAdapter.js +19 -0
  20. package/dist/core/loopEngine.js +10 -7
  21. package/dist/core/modules/StreamHandler.js +5 -4
  22. package/dist/core/modules/TelemetryHandler.d.ts +5 -1
  23. package/dist/core/modules/TelemetryHandler.js +16 -0
  24. package/dist/core/modules/ToolsManager.js +3 -0
  25. package/dist/core/nativeGenerateLoop.js +1 -1
  26. package/dist/evaluation/index.d.ts +1 -0
  27. package/dist/evaluation/index.js +4 -0
  28. package/dist/evaluation/ragasEvaluator.js +1 -0
  29. package/dist/factories/providerDescriptors.d.ts +4 -3
  30. package/dist/factories/providerDescriptors.js +6 -4
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.js +1 -1
  33. package/dist/mcp/externalServerManager.js +6 -2
  34. package/dist/mcp/toolRegistry.js +3 -0
  35. package/dist/neurolink.d.ts +20 -10
  36. package/dist/neurolink.js +134 -65
  37. package/dist/observability/FEATURE-STATUS.md +0 -4
  38. package/dist/observability/exporterRegistry.d.ts +6 -1
  39. package/dist/observability/exporterRegistry.js +18 -0
  40. package/dist/observability/exporterSpanProcessor.d.ts +20 -6
  41. package/dist/observability/exporterSpanProcessor.js +166 -34
  42. package/dist/observability/exporters/baseExporter.d.ts +22 -3
  43. package/dist/observability/exporters/baseExporter.js +87 -5
  44. package/dist/observability/exporters/langfuseExporter.d.ts +29 -3
  45. package/dist/observability/exporters/langfuseExporter.js +251 -41
  46. package/dist/observability/langfuseMasking.d.ts +1 -1
  47. package/dist/observability/langfuseMasking.js +1 -1
  48. package/dist/observability/otlpEncoding.d.ts +18 -5
  49. package/dist/observability/otlpEncoding.js +69 -12
  50. package/dist/observability/utils/spanSerializer.d.ts +3 -2
  51. package/dist/observability/utils/spanSerializer.js +5 -3
  52. package/dist/processors/base/BaseFileProcessor.d.ts +1 -0
  53. package/dist/processors/base/BaseFileProcessor.js +4 -0
  54. package/dist/processors/index.d.ts +1 -0
  55. package/dist/processors/index.js +4 -0
  56. package/dist/processors/registry/ProcessorRegistry.d.ts +1 -0
  57. package/dist/processors/registry/ProcessorRegistry.js +5 -0
  58. package/dist/providers/amazonBedrock/client.js +7 -5
  59. package/dist/providers/amazonSagemaker.js +32 -12
  60. package/dist/providers/anthropic/client.js +1 -0
  61. package/dist/providers/anthropic/loopAdapter.js +7 -3
  62. package/dist/providers/catalog/provider-catalog.schema.json +64 -3
  63. package/dist/providers/catalog/schema.js +81 -1
  64. package/dist/providers/catalog/xor.json +30 -10
  65. package/dist/providers/configuredDecision.d.ts +8 -1
  66. package/dist/providers/configuredDecision.js +22 -0
  67. package/dist/providers/decide/dialects/openaiDecisions.js +8 -0
  68. package/dist/providers/decide/dialects/systemOne.js +28 -2
  69. package/dist/providers/decide/guards.d.ts +6 -0
  70. package/dist/providers/decide/guards.js +22 -0
  71. package/dist/providers/googleAiStudio/client.js +2 -0
  72. package/dist/providers/googleVertex/client.d.ts +6 -5
  73. package/dist/providers/googleVertex/client.js +46 -19
  74. package/dist/providers/openaiChatCompletionsBase.js +4 -4
  75. package/dist/providers/systemOneDecision.d.ts +30 -7
  76. package/dist/providers/systemOneDecision.js +71 -14
  77. package/dist/proxy/codexUsage.js +59 -2
  78. package/dist/proxy/proxyTracer.d.ts +25 -1
  79. package/dist/proxy/proxyTracer.js +146 -22
  80. package/dist/proxy/proxyTranslationEngine.js +4 -2
  81. package/dist/proxy/sseInterceptor.js +6 -4
  82. package/dist/rag/chunkers/BaseChunker.js +1 -0
  83. package/dist/rag/index.d.ts +1 -0
  84. package/dist/rag/index.js +4 -0
  85. package/dist/rag/ragIntegration.js +1 -0
  86. package/dist/rag/reranker/reranker.js +2 -0
  87. package/dist/rag/retrieval/vectorQueryTool.js +1 -0
  88. package/dist/server/routes/codexProxyRoutes.js +3 -1
  89. package/dist/server/utils/validation.d.ts +3 -3
  90. package/dist/services/server/ai/observability/instrumentation.d.ts +8 -7
  91. package/dist/services/server/ai/observability/instrumentation.js +217 -47
  92. package/dist/services/server/ai/observability/langfuseSetup.d.ts +19 -1
  93. package/dist/services/server/ai/observability/langfuseSetup.js +62 -4
  94. package/dist/services/server/ai/observability/otlpSetup.d.ts +12 -2
  95. package/dist/services/server/ai/observability/otlpSetup.js +55 -4
  96. package/dist/telemetry/aiMetrics.d.ts +56 -0
  97. package/dist/telemetry/aiMetrics.js +229 -0
  98. package/dist/telemetry/attributes.d.ts +24 -1
  99. package/dist/telemetry/attributes.js +94 -9
  100. package/dist/telemetry/chatMessages.d.ts +6 -0
  101. package/dist/telemetry/chatMessages.js +37 -0
  102. package/dist/telemetry/index.d.ts +1 -1
  103. package/dist/telemetry/index.js +1 -1
  104. package/dist/telemetry/observation.d.ts +5 -5
  105. package/dist/telemetry/observation.js +128 -39
  106. package/dist/telemetry/spanSanitizer.d.ts +2 -2
  107. package/dist/telemetry/spanSanitizer.js +76 -8
  108. package/dist/telemetry/telemetryStarter.d.ts +16 -0
  109. package/dist/telemetry/telemetryStarter.js +28 -0
  110. package/dist/telemetry/traceContext.d.ts +3 -3
  111. package/dist/telemetry/traceContext.js +3 -3
  112. package/dist/telemetry/tracers.d.ts +13 -1
  113. package/dist/telemetry/tracers.js +49 -10
  114. package/dist/telemetry/withSpan.d.ts +19 -3
  115. package/dist/telemetry/withSpan.js +149 -20
  116. package/dist/types/cli.d.ts +2 -0
  117. package/dist/types/decision.d.ts +117 -10
  118. package/dist/types/exporter.d.ts +98 -3
  119. package/dist/types/livekit.d.ts +16 -1
  120. package/dist/types/loopEngine.d.ts +16 -1
  121. package/dist/types/observability.d.ts +38 -5
  122. package/dist/types/providerCatalog.d.ts +12 -1
  123. package/dist/types/providers.d.ts +2 -0
  124. package/dist/types/proxy.d.ts +2 -0
  125. package/dist/types/span.d.ts +26 -0
  126. package/dist/utils/decisionAnswers.d.ts +7 -1
  127. package/dist/utils/decisionAnswers.js +43 -2
  128. package/dist/utils/decisionLimits.d.ts +13 -1
  129. package/dist/utils/decisionLimits.js +52 -1
  130. package/dist/utils/decisionMedia.d.ts +4 -2
  131. package/dist/utils/decisionMedia.js +216 -14
  132. package/dist/utils/timeout.js +4 -2
  133. package/dist/voice/livekit/realtimeMcpTools.js +11 -2
  134. package/dist/voice/livekit/realtimeMemory.d.ts +20 -2
  135. package/dist/voice/livekit/realtimeMemory.js +139 -46
  136. package/dist/voice/livekit/realtimeVoiceAgent.js +20 -8
  137. package/dist/voice/livekit/turnRecorder.js +22 -9
  138. package/dist/workflow/core/ensembleExecutor.js +2 -0
  139. package/dist/workflow/core/judgeScorer.js +1 -0
  140. package/dist/workflow/core/responseConditioner.js +1 -0
  141. package/dist/workflow/core/workflowRunner.js +6 -1
  142. package/dist/workflow/index.d.ts +1 -0
  143. package/dist/workflow/index.js +4 -0
  144. package/docs-site/static/search-index.json +56 -48
  145. package/package.json +21 -2
@@ -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 = {
@@ -4,7 +4,7 @@
4
4
  * src/lib/providers/catalog/<id>.json file per provider is the single
5
5
  * source of truth; see docs/superpowers/plans/2026-08-28-provider-json-catalog-spec.md.
6
6
  */
7
- import type { DecideAmbientKeyGuard, DecideDialectName, DecideMedia, DecisionErrorKind, DecisionLimits } from "./decision.js";
7
+ import type { DecideAmbientKeyGuard, DecideCapableModel, DecideDialectName, DecideMedia, DecisionErrorKind, DecisionLimits } from "./decision.js";
8
8
  export type CatalogPricingPerMTok = {
9
9
  input: number;
10
10
  output: number;
@@ -338,6 +338,15 @@ export type DecideBlock = {
338
338
  stateEncoding?: "text";
339
339
  /** Body keys for images and video, after `model`, `state` and `questions`. Omitted means the body never carries media. */
340
340
  media?: DecideMedia;
341
+ /**
342
+ * A model the request is sent to instead of the default when the call names
343
+ * no model, no model variable is set, and the request carries media or a
344
+ * question of one of `questionTypes`. Its `decisionLimits` row is the one
345
+ * checked. For a text default that cannot read those requests, whose server
346
+ * would otherwise hand them to this model behind its back, with a smaller
347
+ * window than the one checked. Omitted means the default model is always sent.
348
+ */
349
+ capableModel?: DecideCapableModel;
341
350
  /**
342
351
  * Keeps a key found only in the environment from becoming the default
343
352
  * decision provider when it plainly belongs to something else. Omitted means
@@ -368,6 +377,8 @@ export type DecideCatalogJson = {
368
377
  };
369
378
  timeouts?: {
370
379
  decideMs?: number;
380
+ /** Added to `decideMs` per 1,000 estimated state tokens, for a model whose latency grows with the state. */
381
+ decideMsPerKStateTokens?: number;
371
382
  };
372
383
  decisionLimits?: DecisionLimits;
373
384
  decide: DecideBlock;
@@ -2459,6 +2459,8 @@ export type ProviderDescriptor = {
2459
2459
  generateMs?: number;
2460
2460
  streamMs?: number;
2461
2461
  decideMs?: number;
2462
+ /** Added to `decideMs` per 1,000 estimated state tokens. */
2463
+ decideMsPerKStateTokens?: number;
2462
2464
  };
2463
2465
  /**
2464
2466
  * What a `decide` provider can read. Absent means the provider leaves size
@@ -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;
@@ -141,8 +141,24 @@ export type SpanAttributes = {
141
141
  export type SpanEvent = {
142
142
  name: string;
143
143
  timestamp: string;
144
+ /** When the event happened as nanoseconds since the Unix epoch (decimal string); finer than `timestamp`. */
145
+ timestampUnixNano?: string;
144
146
  attributes?: Record<string, unknown>;
145
147
  };
148
+ /**
149
+ * Instrumentation scope that created a span
150
+ */
151
+ export type SpanScope = {
152
+ name: string;
153
+ version?: string;
154
+ };
155
+ /**
156
+ * The fields an OpenTelemetry SDK 1.x span exposes where 2.x uses `parentSpanContext` and `instrumentationScope`
157
+ */
158
+ export type LegacyReadableSpan = {
159
+ parentSpanId?: string;
160
+ instrumentationLibrary?: SpanScope;
161
+ };
146
162
  /**
147
163
  * Link to related span
148
164
  */
@@ -169,8 +185,18 @@ export type SpanData = {
169
185
  startTime: string;
170
186
  /** End timestamp (ISO 8601) */
171
187
  endTime?: string;
188
+ /** Start as nanoseconds since the Unix epoch (decimal string); finer than `startTime` */
189
+ startTimeUnixNano?: string;
190
+ /** End as nanoseconds since the Unix epoch (decimal string); finer than `endTime` */
191
+ endTimeUnixNano?: string;
172
192
  /** Duration in milliseconds */
173
193
  durationMs?: number;
194
+ /** OpenTelemetry span kind: 0 internal, 1 server, 2 client, 3 producer, 4 consumer */
195
+ kind?: number;
196
+ /** Attributes of the resource that produced the span, such as `service.name` */
197
+ resource?: SpanAttributes;
198
+ /** Instrumentation scope that created the span */
199
+ scope?: SpanScope;
174
200
  /** Span status */
175
201
  status: SpanStatus;
176
202
  /** Status message (for errors) */
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * @module utils/decisionAnswers
9
9
  */
10
- import type { DecisionAnswerMap, DecisionBooleanGate, DecisionChoiceReading, DecisionQuestion, DecisionResult, DecisionScoreReading, ProviderDescriptor } from "../types/index.js";
10
+ import type { DecisionAnswerMap, DecisionBooleanGate, DecisionChoiceReading, DecisionQuestion, DecisionResult, DecisionScoreReading, DecisionValueReading, ProviderDescriptor } from "../types/index.js";
11
11
  /**
12
12
  * Read a yes/no answer as a probability.
13
13
  *
@@ -24,6 +24,12 @@ export declare function readDecisionBoolean(answers: DecisionAnswerMap, id: stri
24
24
  */
25
25
  export declare function readDecisionChoice(answers: DecisionAnswerMap, id: string): DecisionChoiceReading | undefined;
26
26
  export declare function readDecisionScore(answers: DecisionAnswerMap, id: string): DecisionScoreReading | undefined;
27
+ /**
28
+ * Read an extracted value. `value` is null and `unknown` true when the model
29
+ * found nothing to extract, which is an answer, not a missing one: undefined
30
+ * still means the id is absent or the answer was another type.
31
+ */
32
+ export declare function readDecisionValue(answers: DecisionAnswerMap, id: string): DecisionValueReading | undefined;
27
33
  /**
28
34
  * A yes/no answer carries no confidence of its own, so certainty has to be
29
35
  * inferred from how far the probability sits from a coin flip. 0.5 → 0, and
@@ -34,7 +34,13 @@ export function readDecisionChoice(answers, id) {
34
34
  const ranked = Object.entries(probabilities)
35
35
  .map(([name, probability]) => ({ name, probability }))
36
36
  .sort((a, b) => b.probability - a.probability);
37
- return { choice, confidence, probabilities, ranked };
37
+ return {
38
+ choice,
39
+ confidence,
40
+ probabilities,
41
+ ranked,
42
+ unknown: answer.unknown === true,
43
+ };
38
44
  }
39
45
  export function readDecisionScore(answers, id) {
40
46
  const answer = answers[id];
@@ -44,6 +50,22 @@ export function readDecisionScore(answers, id) {
44
50
  const { score, confidence, legend, probabilities } = answer;
45
51
  return { score, confidence, legend, probabilities };
46
52
  }
53
+ /**
54
+ * Read an extracted value. `value` is null and `unknown` true when the model
55
+ * found nothing to extract, which is an answer, not a missing one: undefined
56
+ * still means the id is absent or the answer was another type.
57
+ */
58
+ export function readDecisionValue(answers, id) {
59
+ const answer = answers[id];
60
+ if (answer?.type !== "value") {
61
+ return undefined;
62
+ }
63
+ return {
64
+ value: answer.value,
65
+ confidence: answer.confidence,
66
+ unknown: answer.unknown === true || answer.value === null,
67
+ };
68
+ }
47
69
  /**
48
70
  * A yes/no answer carries no confidence of its own, so certainty has to be
49
71
  * inferred from how far the probability sits from a coin flip. 0.5 → 0, and
@@ -136,6 +158,15 @@ export function stripHostAnswers(result, hostIdByWireId) {
136
158
  return { ...result, answers };
137
159
  }
138
160
  const isRecordValue = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
161
+ function compilesAsRegex(pattern) {
162
+ try {
163
+ new RegExp(pattern);
164
+ return true;
165
+ }
166
+ catch {
167
+ return false;
168
+ }
169
+ }
139
170
  /**
140
171
  * Whether `value` is something a decision model accepts as content: a
141
172
  * string, number or boolean, or an array or plain object of the same.
@@ -198,7 +229,17 @@ function isWellFormedDecisionQuestion(value) {
198
229
  case "choice":
199
230
  return (isRecordValue(value.criteria) &&
200
231
  Object.keys(value.criteria).length >= 2 &&
201
- Object.values(value.criteria).every((rubric) => rubric === null || isDecisionInput(rubric)));
232
+ Object.values(value.criteria).every((rubric) => rubric === null || isDecisionInput(rubric)) &&
233
+ (value.unknown === undefined || typeof value.unknown === "boolean"));
234
+ case "value":
235
+ return ((value.dtype === undefined ||
236
+ value.dtype === "string" ||
237
+ value.dtype === "int" ||
238
+ value.dtype === "float") &&
239
+ (value.pattern === undefined ||
240
+ (typeof value.pattern === "string" &&
241
+ value.pattern.length > 0 &&
242
+ compilesAsRegex(value.pattern))));
202
243
  case "score":
203
244
  return (Array.isArray(value.criteria) &&
204
245
  value.criteria.length >= 2 &&
@@ -11,7 +11,7 @@
11
11
  *
12
12
  * @module utils/decisionLimits
13
13
  */
14
- import type { DecisionLimits, DecisionLimitsReading, DecisionState, ProviderDescriptor } from "../types/index.js";
14
+ import type { DecisionLimits, DecisionLimitsReading, DecisionQuestion, DecisionQuestionType, DecisionState, ProviderDescriptor } from "../types/index.js";
15
15
  /**
16
16
  * Estimate how many tokens a decision model will read from `state`.
17
17
  *
@@ -37,3 +37,15 @@ export declare function estimateDecisionStateTokens(state: DecisionState, limits
37
37
  * limits.
38
38
  */
39
39
  export declare function resolveDecisionLimitsReading(descriptor: Pick<ProviderDescriptor, "name" | "decisionLimits">, model: string): DecisionLimitsReading | null;
40
+ /** What a provider reads when its limits list no `questionTypes`. */
41
+ export declare const DEFAULT_DECISION_QUESTION_TYPES: readonly DecisionQuestionType[];
42
+ /** The type a question needs a provider to read; a `choice` with `unknown` on needs `choice-unknown`. */
43
+ export declare function decisionQuestionTypeOf(question: DecisionQuestion): DecisionQuestionType;
44
+ /**
45
+ * Why a provider with these limits cannot be asked `question`, or undefined
46
+ * when it can. Applies whether or not the limits are advisory: a question type
47
+ * the server does not know is refused outright, never answered from a guess.
48
+ * `limits` is null for a provider that declares no limits at all, which reads
49
+ * the default types and has no per-question caps.
50
+ */
51
+ export declare function unsupportedDecisionQuestion(question: DecisionQuestion, limits: Pick<DecisionLimitsReading, "questionTypes" | "maxScoreLevels" | "maxChoiceOptions"> | null): string | undefined;
@@ -94,10 +94,19 @@ export function resolveDecisionLimitsReading(descriptor, model) {
94
94
  model,
95
95
  maxStateTokens: modelLimits?.maxStateTokens ?? limits.maxStateTokens,
96
96
  // Absent for a provider that caps by tokens or bytes rather than by
97
- // question count (TypeSafe, XOR).
97
+ // question count (TypeSafe).
98
98
  ...(limits.maxQuestions !== undefined
99
99
  ? { maxQuestions: limits.maxQuestions }
100
100
  : {}),
101
+ ...(limits.questionTypes !== undefined
102
+ ? { questionTypes: [...limits.questionTypes] }
103
+ : {}),
104
+ ...(limits.maxScoreLevels !== undefined
105
+ ? { maxScoreLevels: limits.maxScoreLevels }
106
+ : {}),
107
+ ...(limits.maxChoiceOptions !== undefined
108
+ ? { maxChoiceOptions: limits.maxChoiceOptions }
109
+ : {}),
101
110
  ...(nonAsciiTokensPerChar !== undefined ? { nonAsciiTokensPerChar } : {}),
102
111
  // Not model-overridable — `models` only carries `maxStateTokens` and
103
112
  // `nonAsciiTokensPerChar` — so these three come from the base limits only.
@@ -114,3 +123,45 @@ export function resolveDecisionLimitsReading(descriptor, model) {
114
123
  enforcedLocally: limits.advisory !== true,
115
124
  };
116
125
  }
126
+ /** What a provider reads when its limits list no `questionTypes`. */
127
+ export const DEFAULT_DECISION_QUESTION_TYPES = ["boolean", "choice", "score"];
128
+ /** The type a question needs a provider to read; a `choice` with `unknown` on needs `choice-unknown`. */
129
+ export function decisionQuestionTypeOf(question) {
130
+ return question.type === "choice" && question.unknown === true
131
+ ? "choice-unknown"
132
+ : question.type;
133
+ }
134
+ /**
135
+ * Why a provider with these limits cannot be asked `question`, or undefined
136
+ * when it can. Applies whether or not the limits are advisory: a question type
137
+ * the server does not know is refused outright, never answered from a guess.
138
+ * `limits` is null for a provider that declares no limits at all, which reads
139
+ * the default types and has no per-question caps.
140
+ */
141
+ export function unsupportedDecisionQuestion(question, limits) {
142
+ const types = limits?.questionTypes ?? DEFAULT_DECISION_QUESTION_TYPES;
143
+ const type = decisionQuestionTypeOf(question);
144
+ if (!types.includes(type)) {
145
+ return type === "choice-unknown"
146
+ ? "a choice with unknown: true is not accepted"
147
+ : `the question type "${type}" is not accepted (accepted: ${types.join(", ")})`;
148
+ }
149
+ if (question.type === "choice") {
150
+ const keys = Object.keys(question.criteria);
151
+ if (question.unknown === true && keys.includes("unknown")) {
152
+ return 'a choice with unknown: true cannot also name an option "unknown"';
153
+ }
154
+ const options = keys.length + (question.unknown === true ? 1 : 0);
155
+ const max = limits?.maxChoiceOptions;
156
+ if (max !== undefined && options > max) {
157
+ return `a choice may have at most ${max} options${question.unknown === true ? ", counting unknown" : ""}; this one has ${options}`;
158
+ }
159
+ }
160
+ if (question.type === "score") {
161
+ const max = limits?.maxScoreLevels;
162
+ if (max !== undefined && question.criteria.length > max) {
163
+ return `a score may have at most ${max} levels; this one has ${question.criteria.length}`;
164
+ }
165
+ }
166
+ return undefined;
167
+ }
@@ -1,9 +1,11 @@
1
1
  import type { DecisionMediaLimits, DecisionMediaResult, DecisionMediaSource } from "../types/index.js";
2
2
  /**
3
- * Turn the caller's images and video into `data:` URLs, or say why not.
4
- * Runs before any request, so every refusal costs nothing and none is retried.
3
+ * Turn the caller's images and video into `data:` URLs, and an audio clip into
4
+ * `{ data, format }`, or say why not. Runs before any request, so every
5
+ * refusal costs nothing and none is retried.
5
6
  */
6
7
  export declare function prepareDecisionMedia(request: {
7
8
  images?: readonly DecisionMediaSource[];
8
9
  video?: DecisionMediaSource;
10
+ audio?: DecisionMediaSource;
9
11
  }, limits: DecisionMediaLimits | undefined, vendor: string, alternatives: readonly string[]): Promise<DecisionMediaResult>;