@juspay/neurolink 12.58.1 → 13.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/CHANGELOG.md +14 -3
  2. package/dist/auth/anthropicOAuth.js +3 -3
  3. package/dist/auth/middleware/AuthMiddleware.d.ts +1 -1
  4. package/dist/auth/sessionManager.js +4 -4
  5. package/dist/auth/tokenStore.js +4 -4
  6. package/dist/browser/neurolink.min.js +474 -473
  7. package/dist/cli/commands/observability.d.ts +1 -6
  8. package/dist/cli/commands/observability.js +0 -106
  9. package/dist/cli/commands/proxy.js +9 -18
  10. package/dist/cli/commands/telemetry.d.ts +3 -13
  11. package/dist/cli/commands/telemetry.js +7 -354
  12. package/dist/cli/loop/session.js +1 -1
  13. package/dist/client/httpClient.js +1 -1
  14. package/dist/client/sseClient.js +1 -1
  15. package/dist/client/streamingClient.js +2 -2
  16. package/dist/client/wsClient.js +1 -1
  17. package/dist/constants/langfuse.d.ts +25 -0
  18. package/dist/constants/langfuse.js +38 -0
  19. package/dist/constants/telemetry.d.ts +20 -0
  20. package/dist/constants/telemetry.js +34 -0
  21. package/dist/core/baseProvider.d.ts +2 -1
  22. package/dist/core/baseProvider.js +4 -11
  23. package/dist/core/conversationMemoryManager.js +5 -8
  24. package/dist/core/factory.js +1 -1
  25. package/dist/core/geminiLoopAdapter.d.ts +8 -1
  26. package/dist/core/geminiLoopAdapter.js +25 -1
  27. package/dist/core/loopEngine.js +69 -40
  28. package/dist/core/modules/MessageBuilder.js +2 -2
  29. package/dist/core/modules/StreamHandler.js +2 -2
  30. package/dist/core/modules/TelemetryHandler.d.ts +2 -18
  31. package/dist/core/modules/TelemetryHandler.js +1 -66
  32. package/dist/core/modules/ToolsManager.js +40 -31
  33. package/dist/core/nativeGenerateLoop.js +78 -8
  34. package/dist/core/redisConversationMemoryManager.js +8 -15
  35. package/dist/core/toolExecutionGuards.js +1 -13
  36. package/dist/index.d.ts +5 -12
  37. package/dist/index.js +5 -37
  38. package/dist/mcp/batching/requestBatcher.js +1 -1
  39. package/dist/mcp/externalServerManager.js +3 -21
  40. package/dist/mcp/toolDiscoveryService.js +4 -16
  41. package/dist/mcp/toolRegistry.js +2 -5
  42. package/dist/memory/memoryRetrievalTools.js +1 -1
  43. package/dist/neurolink.d.ts +16 -27
  44. package/dist/neurolink.js +788 -792
  45. package/dist/observability/exporterSpanProcessor.d.ts +50 -0
  46. package/dist/observability/exporterSpanProcessor.js +181 -0
  47. package/dist/observability/exporters/langfuseExporter.d.ts +13 -34
  48. package/dist/observability/exporters/langfuseExporter.js +101 -154
  49. package/dist/observability/index.d.ts +1 -0
  50. package/dist/observability/index.js +1 -0
  51. package/dist/observability/langfuseMasking.d.ts +8 -0
  52. package/dist/observability/langfuseMasking.js +47 -0
  53. package/dist/observability/otlpEncoding.d.ts +20 -0
  54. package/dist/observability/otlpEncoding.js +85 -0
  55. package/dist/providers/amazonBedrock/client.js +142 -176
  56. package/dist/providers/amazonBedrock/loopAdapter.js +14 -1
  57. package/dist/providers/amazonSagemaker.js +118 -80
  58. package/dist/providers/anthropic/client.js +3 -52
  59. package/dist/providers/anthropic/loopAdapter.js +24 -1
  60. package/dist/providers/anthropic/rateLimitCapture.js +9 -12
  61. package/dist/providers/googleAiStudio/client.d.ts +4 -5
  62. package/dist/providers/googleAiStudio/client.js +38 -40
  63. package/dist/providers/googleVertex/client.d.ts +2 -7
  64. package/dist/providers/googleVertex/client.js +282 -378
  65. package/dist/providers/googleVertex/constants.js +2 -2
  66. package/dist/providers/litellm/client.d.ts +2 -9
  67. package/dist/providers/litellm/client.js +1 -63
  68. package/dist/providers/openAI/client.d.ts +1 -8
  69. package/dist/providers/openAI/client.js +0 -65
  70. package/dist/providers/openaiChatCompletionsBase.d.ts +1 -0
  71. package/dist/providers/openaiChatCompletionsBase.js +61 -4
  72. package/dist/providers/replicate.js +22 -4
  73. package/dist/proxy/codexUsage.js +7 -0
  74. package/dist/proxy/proxyFetch.js +19 -25
  75. package/dist/proxy/proxyTracer.d.ts +14 -7
  76. package/dist/proxy/proxyTracer.js +129 -83
  77. package/dist/proxy/proxyTranslationEngine.js +0 -1
  78. package/dist/proxy/requestLogger.d.ts +1 -1
  79. package/dist/proxy/requestLogger.js +39 -45
  80. package/dist/server/middleware/common.js +1 -1
  81. package/dist/server/routes/agentRoutes.js +4 -4
  82. package/dist/server/routes/claudeProxyRoutes.js +99 -196
  83. package/dist/server/routes/codexProxyRoutes.js +621 -591
  84. package/dist/server/routes/healthRoutes.js +6 -6
  85. package/dist/server/routes/mcpRoutes.js +7 -7
  86. package/dist/server/routes/memoryRoutes.js +7 -7
  87. package/dist/server/routes/openApiRoutes.js +3 -3
  88. package/dist/server/routes/toolRoutes.js +5 -5
  89. package/dist/server/routes/transcribeRoutes.js +1 -1
  90. package/dist/server/utils/validation.d.ts +3 -3
  91. package/dist/services/server/ai/observability/instrumentation.d.ts +34 -201
  92. package/dist/services/server/ai/observability/instrumentation.js +220 -1267
  93. package/dist/services/server/ai/observability/langfuseSetup.d.ts +14 -0
  94. package/dist/services/server/ai/observability/langfuseSetup.js +55 -0
  95. package/dist/services/server/ai/observability/otlpSetup.d.ts +16 -0
  96. package/dist/services/server/ai/observability/otlpSetup.js +58 -0
  97. package/dist/session/globalSessionState.js +0 -11
  98. package/dist/telemetry/attributes.d.ts +23 -46
  99. package/dist/telemetry/attributes.js +76 -162
  100. package/dist/telemetry/chatMessages.d.ts +13 -0
  101. package/dist/telemetry/chatMessages.js +59 -0
  102. package/dist/telemetry/index.d.ts +5 -19
  103. package/dist/telemetry/index.js +5 -26
  104. package/dist/telemetry/observation.d.ts +37 -0
  105. package/dist/telemetry/observation.js +112 -0
  106. package/dist/telemetry/observationNames.d.ts +18 -0
  107. package/dist/telemetry/observationNames.js +24 -0
  108. package/dist/telemetry/spanSanitizer.d.ts +39 -0
  109. package/dist/telemetry/spanSanitizer.js +92 -0
  110. package/dist/telemetry/spanSerialization.d.ts +13 -0
  111. package/dist/telemetry/spanSerialization.js +46 -0
  112. package/dist/telemetry/tracers.d.ts +37 -18
  113. package/dist/telemetry/tracers.js +97 -19
  114. package/dist/telemetry/withSpan.d.ts +47 -25
  115. package/dist/telemetry/withSpan.js +158 -71
  116. package/dist/types/cli.d.ts +0 -11
  117. package/dist/types/decision.d.ts +3 -0
  118. package/dist/types/exporter.d.ts +54 -0
  119. package/dist/types/exporter.js +0 -4
  120. package/dist/types/generate.d.ts +4 -1
  121. package/dist/types/isolatedAgent.d.ts +5 -4
  122. package/dist/types/loopEngine.d.ts +18 -28
  123. package/dist/types/observability.d.ts +127 -160
  124. package/dist/types/providers.d.ts +15 -0
  125. package/dist/types/proxy.d.ts +41 -2
  126. package/dist/utils/anthropicTraceSanitizer.d.ts +2 -2
  127. package/dist/utils/anthropicTraceSanitizer.js +3 -1
  128. package/dist/utils/conversationMemory.js +4 -4
  129. package/dist/utils/fileDetector.js +1 -1
  130. package/dist/utils/messageBuilder.js +1 -1
  131. package/dist/utils/noOutputSentinel.d.ts +4 -16
  132. package/dist/utils/noOutputSentinel.js +12 -28
  133. package/dist/utils/providerRetry.js +4 -4
  134. package/dist/voice/livekit/brain.js +1 -1
  135. package/dist/voice/livekit/realtimeMemory.d.ts +1 -1
  136. package/dist/voice/livekit/realtimeMemory.js +66 -1
  137. package/dist/voice/livekit/realtimeVoiceAgent.js +1 -1
  138. package/docs-site/static/search-index.json +42 -63
  139. package/package.json +10 -4
  140. package/dist/telemetry/telemetryService.d.ts +0 -79
  141. package/dist/telemetry/telemetryService.js +0 -572
  142. package/dist/utils/observabilityHelpers.d.ts +0 -27
  143. package/dist/utils/observabilityHelpers.js +0 -47
@@ -4,29 +4,8 @@
4
4
  * to enable telemetry and observability features in Neurolink SDK
5
5
  */
6
6
  import type { AttributeValue } from "@opentelemetry/api";
7
+ import type { JsonValue, RawUsageObject, Unknown } from "./common.js";
7
8
  import type { SpanData } from "./span.js";
8
- /**
9
- * Trace name format for Langfuse traces
10
- *
11
- * Controls how userId and operationName are combined to form the trace name.
12
- * Can be a predefined format string or a custom function.
13
- *
14
- * @example
15
- * // Predefined formats:
16
- * "userId:operationName" → "user@email.com:ai.streamText"
17
- * "operationName:userId" → "ai.streamText:user@email.com"
18
- * "operationName" → "ai.streamText"
19
- * "userId" → "user@email.com" (legacy)
20
- *
21
- * @example
22
- * // Custom function:
23
- * (ctx) => `[${ctx.operationName}] ${ctx.userId}`
24
- * // → "[ai.streamText] user@email.com"
25
- */
26
- export type TraceNameFormat = "userId:operationName" | "operationName:userId" | "operationName" | "userId" | ((context: {
27
- userId?: string;
28
- operationName?: string;
29
- }) => string);
30
9
  /**
31
10
  * Standard GenAI semantic convention attributes from OpenTelemetry
32
11
  * These are the attributes that Vercel AI SDK's experimental_telemetry creates
@@ -55,121 +34,57 @@ export type LangfuseSpanAttributes = {
55
34
  [key: string]: AttributeValue | undefined;
56
35
  };
57
36
  /**
58
- * Langfuse observability configuration
37
+ * @description Langfuse exporter options. Every field falls back to its `LANGFUSE_*` environment variable when omitted.
59
38
  */
60
- export type LangfuseConfig = {
61
- /** Whether Langfuse is enabled */
62
- enabled: boolean;
63
- /** Langfuse public key */
64
- publicKey: string;
65
- /**
66
- * Langfuse secret key
67
- * @sensitive
68
- * WARNING: This is a sensitive credential. Handle securely.
69
- * Do NOT log, expose, or share this key. Follow best practices for secret management.
70
- */
71
- secretKey: string;
72
- /** Langfuse base URL (default: https://cloud.langfuse.com) */
39
+ export type NeurolinkLangfuseConfig = {
40
+ /** Project public key; defaults to `LANGFUSE_PUBLIC_KEY`. */
41
+ publicKey?: string;
42
+ /** Project secret key; defaults to `LANGFUSE_SECRET_KEY`. */
43
+ secretKey?: string;
44
+ /** Langfuse host; defaults to `LANGFUSE_BASE_URL`, then Langfuse Cloud. */
73
45
  baseUrl?: string;
74
- /** Environment name (e.g., dev, staging, prod) */
46
+ /** Environment name; defaults to `LANGFUSE_TRACING_ENVIRONMENT`. */
75
47
  environment?: string;
76
- /** Release/version identifier */
48
+ /** Release identifier; defaults to `LANGFUSE_RELEASE`. */
77
49
  release?: string;
78
- /** Optional default user id to attach to spans */
79
- userId?: string;
80
- /** Optional default session id to attach to spans */
81
- sessionId?: string;
82
- /**
83
- * If true, NeuroLink will NOT create or register its own TracerProvider.
84
- * Instead, it will only create the LangfuseSpanProcessor and ContextEnricher,
85
- * which the parent application must add to its own TracerProvider.
86
- *
87
- * Use this when your application already has OpenTelemetry instrumentation.
88
- *
89
- * @default false
90
- */
91
- useExternalTracerProvider?: boolean;
92
- /**
93
- * If true, NeuroLink will automatically detect if a TracerProvider is already
94
- * registered globally and skip its own registration to avoid conflicts.
95
- *
96
- * This is a convenience option that combines well with useExternalTracerProvider.
97
- *
98
- * @default false
99
- */
100
- autoDetectExternalProvider?: boolean;
101
- /**
102
- * If true, NeuroLink will NOT register its own LangfuseSpanProcessor with the
103
- * global TracerProvider when using external provider mode. Only the ContextEnricher
104
- * will be registered. Use this when the host application already registers a
105
- * LangfuseSpanProcessor (e.g., via a DeferredSpanProcessor) to prevent duplicate
106
- * trace exports to Langfuse.
107
- *
108
- * @default false
109
- */
110
- skipLangfuseSpanProcessor?: boolean;
111
- /**
112
- * Enable auto-detection of operation names from span names.
113
- *
114
- * When true (default), AI operation spans (ai.streamText, ai.generateText, etc.)
115
- * will have their operation name automatically extracted and included in the
116
- * trace name.
117
- *
118
- * @default true
119
- *
120
- * @example
121
- * // With auto-detection enabled (default):
122
- * // Span "ai.streamText" + userId "user@email.com"
123
- * // → Trace name: "user@email.com:ai.streamText"
124
- *
125
- * @example
126
- * // With auto-detection disabled:
127
- * // → Trace name: "user@email.com" (legacy behavior)
128
- */
129
- autoDetectOperationName?: boolean;
130
- /**
131
- * Format for trace names in Langfuse.
132
- *
133
- * Controls how userId and operationName are combined to form the trace name.
134
- * Can be a predefined format string or a custom function for full control.
135
- *
136
- * @default "userId:operationName"
137
- *
138
- * @example
139
- * // Predefined formats:
140
- * traceNameFormat: "userId:operationName" // "user@email.com:ai.streamText"
141
- * traceNameFormat: "operationName:userId" // "ai.streamText:user@email.com"
142
- * traceNameFormat: "operationName" // "ai.streamText"
143
- * traceNameFormat: "userId" // "user@email.com" (legacy)
144
- *
145
- * @example
146
- * // Custom function:
147
- * traceNameFormat: (ctx) => `[${ctx.operationName || 'unknown'}] ${ctx.userId}`
148
- * // → "[ai.streamText] user@email.com"
149
- */
150
- traceNameFormat?: TraceNameFormat;
50
+ /** Applied to input, output and metadata before they leave the process. */
51
+ mask?: (params: {
52
+ data: JsonValue;
53
+ }) => JsonValue;
54
+ /** `"immediate"` sends every span as it ends; `"batched"` (default) queues them. */
55
+ exportMode?: "batched" | "immediate";
56
+ /** Spans queued before a batch is sent; defaults to 15. */
57
+ flushAt?: number;
58
+ /** Seconds between batch sends; defaults to 5. */
59
+ flushInterval?: number;
151
60
  };
152
61
  /**
153
- * OpenTelemetry configuration
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.
154
63
  */
155
- export type OpenTelemetryConfig = {
156
- /** Whether OpenTelemetry is enabled */
157
- enabled: boolean;
158
- /** OTLP endpoint URL */
159
- endpoint?: string;
160
- /** Service name for traces */
161
- serviceName?: string;
162
- /** Service version */
163
- serviceVersion?: string;
64
+ export type ObservabilityConfig = {
65
+ /** `false` disables Langfuse, `true` or an options object enables it. */
66
+ langfuse?: boolean | NeurolinkLangfuseConfig;
164
67
  };
165
68
  /**
166
- * Complete observability configuration for Neurolink SDK
69
+ * @description Per-call trace attributes propagated to every span created inside the call.
167
70
  */
168
- export type ObservabilityConfig = {
169
- /** Langfuse configuration */
170
- langfuse?: LangfuseConfig;
171
- /** OpenTelemetry configuration */
172
- openTelemetry?: OpenTelemetryConfig;
71
+ export type NeurolinkTraceContext = {
72
+ /** End user the trace belongs to. */
73
+ userId?: string | null;
74
+ /** Session that groups related traces. */
75
+ sessionId?: string | null;
76
+ /** Conversation identifier, recorded as trace metadata. */
77
+ conversationId?: string | null;
78
+ /** Request identifier, recorded as trace metadata. */
79
+ requestId?: string | null;
80
+ /** Trace name shown in Langfuse. */
81
+ traceName?: string | null;
82
+ /** Trace tags. */
83
+ tags?: string[];
84
+ /** Trace metadata; values up to 200 characters once stringified are on every observation, longer values and arrays only on the trace. */
85
+ metadata?: Record<string, SpanContent | undefined>;
86
+ /** Name of the root observation of this `generate` or `stream` call; not propagated to child observations and not the trace name. */
87
+ observationName?: string | null;
173
88
  };
174
89
  /**
175
90
  * Retry policy type for observability exporters.
@@ -225,45 +140,93 @@ export type TraceView = {
225
140
  /** Overall trace status */
226
141
  status: "ok" | "error" | "partial";
227
142
  };
228
- /**
229
- * Extended context for Langfuse spans.
230
- * Supports all Langfuse trace attributes for rich observability.
231
- */
232
143
  /** A model request serialized onto a span: the system prompt plus messages, or a bare prompt. */
233
144
  export type SpanModelRequest = {
234
145
  system?: unknown;
235
146
  messages?: readonly unknown[];
236
147
  prompt?: string;
237
148
  };
238
- export type LangfuseContext = {
239
- userId?: string | null;
240
- sessionId?: string | null;
241
- /** Conversation/thread identifier for grouping related traces */
242
- conversationId?: string | null;
243
- /** Request identifier for correlating with application logs */
244
- requestId?: string | null;
245
- /** Custom trace name for better organization in Langfuse UI */
246
- traceName?: string | null;
247
- /** Custom metadata to attach to spans */
248
- metadata?: Record<string, unknown> | null;
249
- /** Langfuse trace tags, set on the trace's root span (e.g. to label what a trace is for) */
250
- tags?: string[] | null;
251
- /**
252
- * Explicit operation name (e.g., "ai.streamText", "chat", "embeddings").
253
- * If set, overrides auto-detection from the span name.
254
- */
255
- operationName?: string | null;
256
- /**
257
- * Override global autoDetectOperationName setting for this context.
258
- * When undefined, uses the global setting (defaults to true).
259
- */
260
- autoDetectOperationName?: boolean;
261
- /**
262
- * Custom attributes to set on all spans within this context.
263
- * These attributes are propagated to every span created within the
264
- * AsyncLocalStorage context.
265
- */
266
- customAttributes?: Record<string, string | number | boolean>;
149
+ export type GenerationOperation = "generate" | "stream";
150
+ /** @description Any value a span attribute can carry: JSON, or an object serialized on the way out. */
151
+ export type SpanContent = JsonValue | object;
152
+ /** @description Observation kinds Langfuse renders a span as. */
153
+ export type ObservationType = "span" | "generation" | "event" | "embedding" | "agent" | "tool" | "chain" | "retriever" | "evaluator" | "guardrail";
154
+ /** @description Severity of an observation. */
155
+ export type ObservationLevel = "DEBUG" | "DEFAULT" | "WARNING" | "ERROR";
156
+ /** @description Attributes stamped on an observation span by `observe`. */
157
+ export type ObservationAttributes = {
158
+ input?: Unknown;
159
+ output?: Unknown;
160
+ metadata?: Record<string, SpanContent | undefined>;
161
+ level?: ObservationLevel;
162
+ statusMessage?: string;
163
+ completionStartTime?: Date;
164
+ model?: string;
165
+ modelParameters?: Record<string, string | number>;
166
+ usageDetails?: Record<string, number>;
167
+ costDetails?: Record<string, number>;
168
+ };
169
+ /** @description Trace-level input, output and per-key metadata recorded by `observeTrace`. */
170
+ export type TraceContent = Pick<ObservationAttributes, "input" | "output"> & {
171
+ metadata?: Record<string, SpanContent | undefined>;
172
+ };
173
+ /** @description A tool call as a provider, the agentic loop or a tool execution names its fields. */
174
+ export type AssistantToolCall = {
175
+ id?: string;
176
+ toolCallId?: string;
177
+ name?: string;
178
+ toolName?: string;
179
+ args?: SpanContent;
180
+ input?: SpanContent;
181
+ arguments?: SpanContent;
182
+ function?: {
183
+ name?: string;
184
+ arguments?: SpanContent;
185
+ };
186
+ };
187
+ /** @description A model response split into text, reasoning and tool calls. */
188
+ export type AssistantReply = {
189
+ text?: string;
190
+ reasoning?: string;
191
+ toolCalls?: AssistantToolCall[];
192
+ };
193
+ /** @description A model response as plain text, a structured reply, or message or content-part arrays. */
194
+ export type AssistantOutput = string | AssistantReply | object[];
195
+ /** @description A tool call in OpenAI chat shape, as Langfuse renders it. */
196
+ export type ObservationToolCall = {
197
+ id?: string;
198
+ type: "function";
199
+ function: {
200
+ name?: string;
201
+ arguments: string;
202
+ };
203
+ };
204
+ /**
205
+ * @description One model request as a Langfuse generation. Every field is optional so the request half and the response half can be recorded separately.
206
+ */
207
+ export type GenerationRecord = {
208
+ /** Public API the call serves; inferred from the enclosing span when unset. */
209
+ operation?: GenerationOperation;
210
+ /** Provider name, used with the model to price the call. */
211
+ provider?: string;
212
+ /** Model the request asked for. */
213
+ model?: string;
214
+ /** Model the response says served it, when the API reports one. */
215
+ responseModel?: string;
216
+ /** Sampling and thinking parameters sent; undefined entries are dropped. */
217
+ modelParameters?: Record<string, string | number | undefined>;
218
+ /** System prompt and messages, or the bare prompt, sent. */
219
+ input?: SpanModelRequest;
220
+ /** Text and tool calls returned. */
221
+ output?: AssistantOutput;
222
+ /** Token usage in any naming convention `extractTokenUsage` reads. */
223
+ usage?: RawUsageObject | null;
224
+ /** Provider finish reason. */
225
+ finishReason?: string;
226
+ /** Whether the caller aborted the call before it finished. */
227
+ aborted?: boolean;
228
+ /** When the first chunk of a streamed response arrived. */
229
+ completionStartTime?: Date;
267
230
  };
268
231
  /**
269
232
  * Latency statistics with percentile calculations
@@ -426,6 +389,10 @@ export type SpanOptions = {
426
389
  tracer: import("@opentelemetry/api").Tracer;
427
390
  kind?: import("@opentelemetry/api").SpanKind;
428
391
  attributes?: Record<string, string | number | boolean | undefined>;
392
+ /** Marks a thrown error as an expected skip whose Langfuse level the span set itself, so `withSpan` keeps it instead of raising ERROR. */
393
+ expectedFailure?: (error: unknown) => boolean;
394
+ /** Keeps the span out of the active context, so spans started inside `fn` parent to the nearest ancestor that is exported. */
395
+ detached?: boolean;
429
396
  };
430
397
  /** Trace + parent span IDs used to correlate metric records with spans. */
431
398
  export type MetricsTraceContext = {
@@ -2797,3 +2797,18 @@ export type VertexAnthropicContentBlock = {
2797
2797
  tool_use_id: string;
2798
2798
  content: string;
2799
2799
  };
2800
+ /** Content block as seen by the trace sanitizer; media blocks carry a `source`. */
2801
+ export type AnthropicTraceBlock = {
2802
+ type: string;
2803
+ source?: string | {
2804
+ type?: string;
2805
+ media_type?: string;
2806
+ data?: string;
2807
+ url?: string;
2808
+ };
2809
+ };
2810
+ /** Anthropic message shape accepted by the trace sanitizer. */
2811
+ export type AnthropicTraceMessage = {
2812
+ role: string;
2813
+ content: string | AnthropicTraceBlock[];
2814
+ };
@@ -1,4 +1,6 @@
1
- import type { ProxyTokenBudgetSnapshot } from "./proxyBudget.js";
1
+ import type { ProxyTokenBudgetLease, ProxyTokenBudgetSnapshot } from "./proxyBudget.js";
2
+ import type { CodexRuntimeAccount } from "./codex.js";
3
+ import type { JsonObject, JsonValue } from "./common.js";
2
4
  import type { ProxyContextEvidence, ProxyPreparedContext } from "./proxyContext.js";
3
5
  /**
4
6
  * Proxy type definitions for NeuroLink
@@ -27,7 +29,7 @@ import type { MCPToolRegistry } from "../mcp/toolRegistry.js";
27
29
  import type { ProxyTracer } from "../proxy/proxyTracer.js";
28
30
  import type { ACCOUNT_COOLING_REASONS, PROXY_ACCOUNT_TYPES, PROXY_ACCOUNT_ROUTING_MODES, PROXY_ACCOUNT_ROUTING_REASONS, PROXY_ACCOUNT_ROUTING_STRATEGIES } from "../proxy/routingEvidence.js";
29
31
  import type { CodexFallbackTarget, FallbackEntry, ModelMapping, ProxyRoutingConfig, CloakingConfig } from "./subscription.js";
30
- import type { RouteDeprecation } from "./server.js";
32
+ import type { RouteDeprecation, ServerContext } from "./server.js";
31
33
  import type { StoredOAuthTokens } from "./auth.js";
32
34
  /**
33
35
  * Type describing the ModelRouter contract.
@@ -1033,6 +1035,37 @@ export type CodexFinalLogExtra = Partial<Pick<RequestLogEntry, "errorType" | "er
1033
1035
  export type CodexOutboundServedAttribution = Required<Pick<RequestLogEntry, "account" | "accountType" | "provider" | "model">> & Pick<RequestLogEntry, "accountKey">;
1034
1036
  /** Additional fields recorded for each upstream Codex account attempt. */
1035
1037
  export type CodexAttemptLogExtra = Partial<Pick<RequestAttemptLogEntry, "inputTokens" | "outputTokens" | "cacheReadTokens" | "cacheCreationTokens" | "reasoningTokens" | "inputIncludesCachedTokens" | "errorType" | "errorMessage" | "errorCode" | "transportScope" | "retryable" | "rateLimitKind" | "cooldownReason" | "quotaResetAt" | "quotaScope">>;
1038
+ /** Mutable per-request state shared by the Codex request helpers. */
1039
+ export type CodexRequestState = {
1040
+ body: JsonObject;
1041
+ bodyStr: string;
1042
+ contextPreflight?: ProxyContextEvidence;
1043
+ budgetLease?: ProxyTokenBudgetLease;
1044
+ budgetDispatched: boolean;
1045
+ finalOutcomeRecorded: boolean;
1046
+ };
1047
+ /** Everything the Codex request helpers need to serve and account one turn. */
1048
+ export type CodexRequestRuntime = {
1049
+ ctx: ServerContext;
1050
+ model: string;
1051
+ tracer?: ProxyTracer;
1052
+ requestStartTime: number;
1053
+ isFallbackRequest: boolean;
1054
+ state: CodexRequestState;
1055
+ runtimeConfigProvider?: ProxyRuntimeConfigProvider;
1056
+ loopbackPort?: number;
1057
+ internalDispatch?: (request: Request) => Response | Promise<Response>;
1058
+ settleBudget: (actualTokens?: number) => Promise<void>;
1059
+ recordFinalOutcome: (account: CodexRuntimeAccount | undefined, responseStatus: number, extra?: CodexFinalLogExtra) => Promise<void>;
1060
+ writeAttempt: (account: CodexRuntimeAccount, attempt: number, startedAt: number, responseStatus: number, extra?: CodexAttemptLogExtra) => void;
1061
+ };
1062
+ /** Failure details recorded when a Codex account is rotated past. */
1063
+ export type CodexRotationFailure = {
1064
+ lastFailure: CodexFinalLogExtra;
1065
+ lastErrorStatus: number;
1066
+ lastErrorCode: string | undefined;
1067
+ lastErrorMessage: string;
1068
+ };
1036
1069
  /** Minimal persistence contract needed by Codex rotating-token refreshes. */
1037
1070
  export type CodexRefreshTokenStore = {
1038
1071
  peekTokens(provider: string): Promise<StoredOAuthTokens | null>;
@@ -1928,6 +1961,11 @@ export type ResponseInfoContext = {
1928
1961
  stopSequence?: string;
1929
1962
  /** Names of the tools the model actually invoked (tool_use blocks). */
1930
1963
  toolCalls?: string[];
1964
+ output?: JsonValue | Array<{
1965
+ type?: string;
1966
+ toolName?: string;
1967
+ }>;
1968
+ firstOutputAt?: number;
1931
1969
  };
1932
1970
  /** Context recorded when an account is selected for a proxy request. */
1933
1971
  export type AccountSelectionContext = {
@@ -2445,6 +2483,7 @@ export type CodexStreamEvidence = {
2445
2483
  errorType?: string;
2446
2484
  errorMessage?: string;
2447
2485
  errorCode?: string;
2486
+ response?: JsonObject;
2448
2487
  };
2449
2488
  /** Validated account-routing evidence joined to a final request log. */
2450
2489
  export type ProxyAnalysisRoutingRecord = {
@@ -1,7 +1,7 @@
1
- import type { VertexAnthropicMessage } from "../types/index.js";
1
+ import type { AnthropicTraceMessage } from "../types/index.js";
2
2
  /**
3
3
  * Strips base64 image/PDF payloads from Anthropic messages before they go on a
4
4
  * trace attribute — one screenshot would otherwise be megabytes on a span.
5
5
  * Other block types pass through; the serializer still applies its length cap.
6
6
  */
7
- export declare function sanitizeAnthropicMessagesForTrace(messages: VertexAnthropicMessage[]): Array<Record<string, unknown>>;
7
+ export declare function sanitizeAnthropicMessagesForTrace(messages: AnthropicTraceMessage[]): Array<Record<string, unknown>>;
@@ -11,7 +11,9 @@ export function sanitizeAnthropicMessagesForTrace(messages) {
11
11
  return {
12
12
  role: message.role,
13
13
  content: message.content.map((block) => {
14
- if (block.type === "image" || block.type === "document") {
14
+ if ((block.type === "image" || block.type === "document") &&
15
+ typeof block.source === "object" &&
16
+ typeof block.source.data === "string") {
15
17
  return {
16
18
  type: block.type,
17
19
  media_type: block.source.media_type,
@@ -143,7 +143,7 @@ export async function getConversationMessages(conversationMemory, options) {
143
143
  });
144
144
  return [];
145
145
  }
146
- return memoryTracer.startActiveSpan("neurolink.conversation.getMessages", {
146
+ return memoryTracer.startActiveSpan("conversation.getMessages", {
147
147
  kind: SpanKind.INTERNAL,
148
148
  attributes: {
149
149
  "session.id": sessionId,
@@ -174,7 +174,7 @@ export async function getConversationMessages(conversationMemory, options) {
174
174
  const repair = repairToolPairs(filtered);
175
175
  const messages = repair.messages;
176
176
  if (repair.repaired) {
177
- span.setAttribute("neurolink.memory.tool_pairs_repaired", repair.orphanedCallsFixed + repair.orphanedResultsFixed);
177
+ span.setAttribute("memory.tool_pairs_repaired", repair.orphanedCallsFixed + repair.orphanedResultsFixed);
178
178
  logger.debug("[conversationMemoryUtils] Repaired orphaned tool pairs on read", {
179
179
  sessionId,
180
180
  orphanedCallsFixed: repair.orphanedCallsFixed,
@@ -188,7 +188,7 @@ export async function getConversationMessages(conversationMemory, options) {
188
188
  // signal. The warn log is deduped per session so a long-lived
189
189
  // polluted conversation only generates one log line, not one per
190
190
  // turn (would otherwise be noisy at scale).
191
- span.setAttribute("neurolink.memory.polluted_turns_dropped", droppedCount);
191
+ span.setAttribute("memory.polluted_turns_dropped", droppedCount);
192
192
  const alreadyWarned = pollutedWarnedSessions.has(sessionId);
193
193
  if (!alreadyWarned) {
194
194
  if (pollutedWarnedSessions.size >= POLLUTED_WARN_DEDUP_MAX) {
@@ -364,7 +364,7 @@ export async function storeConversationTurn(conversationMemory, originalOptions,
364
364
  });
365
365
  }
366
366
  }
367
- await memoryTracer.startActiveSpan("neurolink.conversation.storeTurn", {
367
+ await memoryTracer.startActiveSpan("conversation.storeTurn", {
368
368
  kind: SpanKind.INTERNAL,
369
369
  attributes: {
370
370
  "session.id": sessionId,
@@ -278,7 +278,7 @@ export class FileDetector {
278
278
  const inputFilename = FileDetector.deriveInputFilename(input);
279
279
  const inputSizeBytes = FileDetector.deriveInputSize(input);
280
280
  return withSpan({
281
- name: "neurolink.file.detect_and_process",
281
+ name: "file.detect_and_process",
282
282
  tracer: tracers.file,
283
283
  attributes: {
284
284
  [ATTR.FILE_NAME]: inputFilename,
@@ -1318,7 +1318,7 @@ export async function processUnifiedFilesArray(options, maxSize, provider) {
1318
1318
  const totalFiles = pending.length;
1319
1319
  const files = pending;
1320
1320
  return withSpan({
1321
- name: "neurolink.file.process_all",
1321
+ name: "file.process_all",
1322
1322
  tracer: tracers.file,
1323
1323
  attributes: {
1324
1324
  [ATTR.FILE_TOTAL_COUNT]: totalFiles,
@@ -38,25 +38,13 @@ underlyingError?: unknown): Promise<{
38
38
  error: Error;
39
39
  } | null>;
40
40
  /**
41
- * Reviewer follow-up: every provider's post-stream NoOutput detect must
42
- * stamp the active OTel span so Pipeline B (`ContextEnricher.onEnd()` →
43
- * `applyNonErrorLangfuseLevel`) surfaces a WARNING-level Langfuse
44
- * observation with the enriched status message. Without this, only
45
- * `StreamHandler`-based providers produced the rich telemetry; the
46
- * provider-specific paths (openAI, openaiCompatible, litellm,
47
- * huggingFace, openRouter, anthropic) yielded the sentinel
48
- * to direct stream consumers but Pipeline B saw nothing.
49
- *
50
- * Stamps three attributes:
51
- * - `neurolink.no_output = true` (Pipeline B trigger)
52
- * - `langfuse.status_message` (enriched, with finishReason + tokens)
53
- * - `neurolink.no_output.finish_reason` (raw finish reason)
54
- *
55
- * Safe to call when tracing isn't initialized — silently no-ops.
41
+ * Marks the active span as a no-output stream so Langfuse shows a WARNING
42
+ * observation with the enriched status message. Safe to call when tracing
43
+ * isn't initialized — silently no-ops.
56
44
  */
57
45
  export declare function stampNoOutputSpan(sentinel: StreamNoOutputSentinel): void;
58
46
  /**
59
- * Build the OTel `langfuse.status_message` summary string for a no-output
47
+ * Build the OTel `langfuse.observation.status_message` summary string for a no-output
60
48
  * stream. Used by `StreamHandler.createTextStream` and any future provider
61
49
  * that wants to stamp the active span with the same enriched message.
62
50
  *
@@ -1,4 +1,6 @@
1
+ import { LangfuseOtelSpanAttributes } from "@langfuse/tracing";
1
2
  import { trace, context as otelContext } from "@opentelemetry/api";
3
+ import { observationLevel } from "../telemetry/observation.js";
2
4
  import { NoOutputGeneratedError } from "./generationErrors.js";
3
5
  export async function buildNoOutputSentinel(error, result,
4
6
  /**
@@ -129,38 +131,20 @@ underlyingError) {
129
131
  }
130
132
  }
131
133
  /**
132
- * Reviewer follow-up: every provider's post-stream NoOutput detect must
133
- * stamp the active OTel span so Pipeline B (`ContextEnricher.onEnd()` →
134
- * `applyNonErrorLangfuseLevel`) surfaces a WARNING-level Langfuse
135
- * observation with the enriched status message. Without this, only
136
- * `StreamHandler`-based providers produced the rich telemetry; the
137
- * provider-specific paths (openAI, openaiCompatible, litellm,
138
- * huggingFace, openRouter, anthropic) yielded the sentinel
139
- * to direct stream consumers but Pipeline B saw nothing.
140
- *
141
- * Stamps three attributes:
142
- * - `neurolink.no_output = true` (Pipeline B trigger)
143
- * - `langfuse.status_message` (enriched, with finishReason + tokens)
144
- * - `neurolink.no_output.finish_reason` (raw finish reason)
145
- *
146
- * Safe to call when tracing isn't initialized — silently no-ops.
134
+ * Marks the active span as a no-output stream so Langfuse shows a WARNING
135
+ * observation with the enriched status message. Safe to call when tracing
136
+ * isn't initialized — silently no-ops.
147
137
  */
148
138
  export function stampNoOutputSpan(sentinel) {
149
- try {
150
- const activeSpan = trace.getSpan(otelContext.active());
151
- if (!activeSpan) {
152
- return;
153
- }
154
- activeSpan.setAttribute("neurolink.no_output", true);
155
- activeSpan.setAttribute("langfuse.status_message", buildNoOutputStatusMessage(sentinel.metadata.finishReason, sentinel.metadata.usage));
156
- activeSpan.setAttribute("neurolink.no_output.finish_reason", String(sentinel.metadata.finishReason));
157
- }
158
- catch {
159
- // Tracing not initialized — ignore.
160
- }
139
+ trace.getSpan(otelContext.active())?.setAttributes({
140
+ no_output: true,
141
+ "no_output.finish_reason": String(sentinel.metadata.finishReason),
142
+ [LangfuseOtelSpanAttributes.OBSERVATION_LEVEL]: observationLevel("no-output"),
143
+ [LangfuseOtelSpanAttributes.OBSERVATION_STATUS_MESSAGE]: buildNoOutputStatusMessage(sentinel.metadata.finishReason, sentinel.metadata.usage),
144
+ });
161
145
  }
162
146
  /**
163
- * Build the OTel `langfuse.status_message` summary string for a no-output
147
+ * Build the OTel `langfuse.observation.status_message` summary string for a no-output
164
148
  * stream. Used by `StreamHandler.createTextStream` and any future provider
165
149
  * that wants to stamp the active span with the same enriched message.
166
150
  *
@@ -270,7 +270,7 @@ export async function withProviderRetry(operation, span, label, sleep = sleepWit
270
270
  try {
271
271
  const result = await operation();
272
272
  // Record how many attempts it took on the span
273
- span?.setAttribute("gen_ai.provider.total_attempts", attempt + 1);
273
+ span?.setAttribute("provider.total_attempts", attempt + 1);
274
274
  if (attempt > 0) {
275
275
  logger.info(`[providerRetry] ${label} succeeded after ${attempt + 1} attempts`);
276
276
  }
@@ -282,9 +282,9 @@ export async function withProviderRetry(operation, span, label, sleep = sleepWit
282
282
  const errorMessage = error instanceof Error ? error.message : String(error);
283
283
  if (!retryable || attempt === MAX_PROVIDER_RETRIES) {
284
284
  // Record failure details before re-throwing
285
- span?.setAttribute("gen_ai.provider.total_attempts", attempt + 1);
285
+ span?.setAttribute("provider.total_attempts", attempt + 1);
286
286
  if (attempt > 0) {
287
- span?.setAttribute("gen_ai.provider.retries_exhausted", true);
287
+ span?.setAttribute("provider.retries_exhausted", true);
288
288
  }
289
289
  logger.warn(`[providerRetry] ${label} failed (non-retryable or retries exhausted)`, {
290
290
  attempt: attempt + 1,
@@ -296,7 +296,7 @@ export async function withProviderRetry(operation, span, label, sleep = sleepWit
296
296
  }
297
297
  const retryAfterMs = getRetryAfterMs(error);
298
298
  if (retryAfterMs !== undefined && retryAfterMs > MAX_RETRY_AFTER_MS) {
299
- span?.setAttribute("gen_ai.provider.total_attempts", attempt + 1);
299
+ span?.setAttribute("provider.total_attempts", attempt + 1);
300
300
  span?.addEvent("gen_ai.provider.retry_suppressed", {
301
301
  "retry.after_ms": retryAfterMs,
302
302
  ...(statusCode !== undefined && { "retry.status_code": statusCode }),
@@ -56,7 +56,7 @@ export function createVoiceBrain(config) {
56
56
  systemPrompt,
57
57
  temperature,
58
58
  maxTokens,
59
- context,
59
+ context: { ...context, tags: ["voice"] },
60
60
  // Every turn through this brain is spoken; stored messages are marked so.
61
61
  modality: "voice",
62
62
  abortSignal: signal,
@@ -19,4 +19,4 @@
19
19
  import type { JobContext } from "@livekit/agents";
20
20
  import type { RealtimeCallMemory, RealtimeVoiceMemoryConfig } from "../../types/index.js";
21
21
  /** Verify the call's binding and prepare its recorder; `undefined` means "no memory for this call". */
22
- export declare function setUpRealtimeCallMemory(ctx: Pick<JobContext, "room" | "addShutdownCallback">, config: RealtimeVoiceMemoryConfig | undefined): Promise<RealtimeCallMemory | undefined>;
22
+ export declare function setUpRealtimeCallMemory(ctx: Pick<JobContext, "room" | "addShutdownCallback">, config: RealtimeVoiceMemoryConfig | undefined, model?: string): Promise<RealtimeCallMemory | undefined>;