@juspay/neurolink 12.59.0 → 13.0.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 (146) hide show
  1. package/CHANGELOG.md +3 -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/auth.d.ts +5 -1
  81. package/dist/server/middleware/auth.js +10 -3
  82. package/dist/server/middleware/common.js +1 -1
  83. package/dist/server/routes/agentRoutes.js +4 -4
  84. package/dist/server/routes/claudeProxyRoutes.js +99 -196
  85. package/dist/server/routes/codexProxyRoutes.js +621 -591
  86. package/dist/server/routes/healthRoutes.js +6 -6
  87. package/dist/server/routes/mcpRoutes.js +7 -7
  88. package/dist/server/routes/memoryRoutes.js +7 -7
  89. package/dist/server/routes/openApiRoutes.js +3 -3
  90. package/dist/server/routes/toolRoutes.js +5 -5
  91. package/dist/server/routes/transcribeRoutes.js +1 -1
  92. package/dist/server/utils/validation.d.ts +3 -3
  93. package/dist/services/server/ai/observability/instrumentation.d.ts +34 -201
  94. package/dist/services/server/ai/observability/instrumentation.js +220 -1267
  95. package/dist/services/server/ai/observability/langfuseSetup.d.ts +14 -0
  96. package/dist/services/server/ai/observability/langfuseSetup.js +55 -0
  97. package/dist/services/server/ai/observability/otlpSetup.d.ts +16 -0
  98. package/dist/services/server/ai/observability/otlpSetup.js +58 -0
  99. package/dist/session/globalSessionState.js +0 -11
  100. package/dist/telemetry/attributes.d.ts +23 -46
  101. package/dist/telemetry/attributes.js +76 -162
  102. package/dist/telemetry/chatMessages.d.ts +13 -0
  103. package/dist/telemetry/chatMessages.js +59 -0
  104. package/dist/telemetry/index.d.ts +5 -19
  105. package/dist/telemetry/index.js +5 -26
  106. package/dist/telemetry/observation.d.ts +37 -0
  107. package/dist/telemetry/observation.js +112 -0
  108. package/dist/telemetry/observationNames.d.ts +18 -0
  109. package/dist/telemetry/observationNames.js +24 -0
  110. package/dist/telemetry/spanSanitizer.d.ts +39 -0
  111. package/dist/telemetry/spanSanitizer.js +92 -0
  112. package/dist/telemetry/spanSerialization.d.ts +13 -0
  113. package/dist/telemetry/spanSerialization.js +46 -0
  114. package/dist/telemetry/tracers.d.ts +37 -18
  115. package/dist/telemetry/tracers.js +97 -19
  116. package/dist/telemetry/withSpan.d.ts +47 -25
  117. package/dist/telemetry/withSpan.js +158 -71
  118. package/dist/types/cli.d.ts +0 -11
  119. package/dist/types/decision.d.ts +3 -0
  120. package/dist/types/exporter.d.ts +54 -0
  121. package/dist/types/exporter.js +0 -4
  122. package/dist/types/generate.d.ts +4 -1
  123. package/dist/types/isolatedAgent.d.ts +5 -4
  124. package/dist/types/loopEngine.d.ts +18 -28
  125. package/dist/types/observability.d.ts +127 -160
  126. package/dist/types/providers.d.ts +15 -0
  127. package/dist/types/proxy.d.ts +41 -2
  128. package/dist/types/server.d.ts +9 -5
  129. package/dist/utils/anthropicTraceSanitizer.d.ts +2 -2
  130. package/dist/utils/anthropicTraceSanitizer.js +3 -1
  131. package/dist/utils/conversationMemory.js +4 -4
  132. package/dist/utils/fileDetector.js +1 -1
  133. package/dist/utils/messageBuilder.js +1 -1
  134. package/dist/utils/noOutputSentinel.d.ts +4 -16
  135. package/dist/utils/noOutputSentinel.js +12 -28
  136. package/dist/utils/providerRetry.js +4 -4
  137. package/dist/voice/livekit/brain.js +1 -1
  138. package/dist/voice/livekit/realtimeMemory.d.ts +1 -1
  139. package/dist/voice/livekit/realtimeMemory.js +66 -1
  140. package/dist/voice/livekit/realtimeVoiceAgent.js +1 -1
  141. package/docs-site/static/search-index.json +42 -64
  142. package/package.json +5 -4
  143. package/dist/telemetry/telemetryService.d.ts +0 -79
  144. package/dist/telemetry/telemetryService.js +0 -572
  145. package/dist/utils/observabilityHelpers.d.ts +0 -27
  146. package/dist/utils/observabilityHelpers.js +0 -47
@@ -115,7 +115,7 @@ export class LoopSession {
115
115
  // The .fail() handler in cli.ts is now session-aware and will
116
116
  // handle all parsing and validation errors without exiting the loop.
117
117
  // We create a fresh instance for each command to prevent state pollution.
118
- await tracers.sdk.startActiveSpan("neurolink.cli.turn", async (turnSpan) => {
118
+ await tracers.sdk.startActiveSpan("cli.turn", async (turnSpan) => {
119
119
  try {
120
120
  turnSpan.setAttribute("cli.command", typeof processedCommand === "string"
121
121
  ? processedCommand.slice(0, 100)
@@ -187,7 +187,7 @@ export class NeuroLinkClient {
187
187
  */
188
188
  async request(method, path, body, options) {
189
189
  return withClientSpan({
190
- name: "neurolink.client.http.request",
190
+ name: "client.http.request",
191
191
  tracer: tracers.http,
192
192
  attributes: {
193
193
  "http.method": method,
@@ -73,7 +73,7 @@ export class NeuroLinkSSE {
73
73
  */
74
74
  async stream(path, options = {}, callbacks = {}) {
75
75
  return withClientSpan({
76
- name: "neurolink.client.sse.stream",
76
+ name: "client.sse.stream",
77
77
  tracer: tracers.http,
78
78
  attributes: {
79
79
  "http.method": options.body ? "POST" : "GET",
@@ -64,7 +64,7 @@ export class SSEClient {
64
64
  */
65
65
  async connect(requestOptions = {}) {
66
66
  return withClientSpan({
67
- name: "neurolink.client.streaming.sse.connect",
67
+ name: "client.streaming.sse.connect",
68
68
  tracer: tracers.http,
69
69
  attributes: {
70
70
  "http.url": this.url,
@@ -406,7 +406,7 @@ export class WebSocketStreamingClient {
406
406
  */
407
407
  async connect() {
408
408
  return withClientSpan({
409
- name: "neurolink.client.streaming.ws.connect",
409
+ name: "client.streaming.ws.connect",
410
410
  tracer: tracers.http,
411
411
  attributes: { "http.url": this.options.url },
412
412
  }, async () => this._connectWS());
@@ -111,7 +111,7 @@ export class NeuroLinkWebSocket {
111
111
  // Start an OTel span that tracks the lifetime of this connection attempt.
112
112
  // Ended in onclose/onerror/disconnect so metrics capture connection
113
113
  // duration and error attribution.
114
- this.connectionSpan = tracers.http.startSpan("neurolink.client.ws.connect", {
114
+ this.connectionSpan = tracers.http.startSpan("client.ws.connect", {
115
115
  kind: SpanKind.CLIENT,
116
116
  attributes: {
117
117
  "http.url": this.config.baseUrl,
@@ -0,0 +1,25 @@
1
+ import { SpanStatus } from "../types/index.js";
2
+ /** @description Langfuse OTLP ingestion endpoint path. */
3
+ export declare const TRACES_PATH = "/api/public/otel/v1/traces";
4
+ /** @description Langfuse health endpoint path. */
5
+ export declare const HEALTH_PATH = "/api/public/health";
6
+ /** @description Langfuse Cloud base URL used when none is configured. */
7
+ export declare const DEFAULT_BASE_URL = "https://cloud.langfuse.com";
8
+ /** @description Spans buffered before an automatic flush. */
9
+ export declare const DEFAULT_FLUSH_AT = 15;
10
+ /** @description Milliseconds between scheduled flushes. */
11
+ export declare const DEFAULT_FLUSH_INTERVAL_MS = 5000;
12
+ /** @description Milliseconds before an export request is aborted. */
13
+ export declare const DEFAULT_TIMEOUT_MS = 30000;
14
+ /** @description Maximum spans sent in one export request. */
15
+ export declare const MAX_SPANS_PER_REQUEST = 500;
16
+ /** @description Maximum serialized bytes sent in one export request. */
17
+ export declare const MAX_BYTES_PER_REQUEST = 3500000;
18
+ /** @description Placeholder that replaces a value fully removed by the user's mask. */
19
+ export declare const FULLY_MASKED = "<fully masked>";
20
+ /** @description Span attributes holding trace and observation input or output, which the mask applies to. */
21
+ export declare const CONTENT_KEYS: Set<string>;
22
+ /** @description Attribute prefixes whose values are metadata entries. */
23
+ export declare const METADATA_PREFIXES: string[];
24
+ /** @description OTLP numeric status code for each span status. */
25
+ export declare const OTLP_STATUS_CODES: Record<SpanStatus, number>;
@@ -0,0 +1,38 @@
1
+ import { SpanStatus } from "../types/index.js";
2
+ /** @description Langfuse OTLP ingestion endpoint path. */
3
+ export const TRACES_PATH = "/api/public/otel/v1/traces";
4
+ /** @description Langfuse health endpoint path. */
5
+ export const HEALTH_PATH = "/api/public/health";
6
+ /** @description Langfuse Cloud base URL used when none is configured. */
7
+ export const DEFAULT_BASE_URL = "https://cloud.langfuse.com";
8
+ /** @description Spans buffered before an automatic flush. */
9
+ export const DEFAULT_FLUSH_AT = 15;
10
+ /** @description Milliseconds between scheduled flushes. */
11
+ export const DEFAULT_FLUSH_INTERVAL_MS = 5_000;
12
+ /** @description Milliseconds before an export request is aborted. */
13
+ export const DEFAULT_TIMEOUT_MS = 30_000;
14
+ /** @description Maximum spans sent in one export request. */
15
+ export const MAX_SPANS_PER_REQUEST = 500;
16
+ /** @description Maximum serialized bytes sent in one export request. */
17
+ export const MAX_BYTES_PER_REQUEST = 3_500_000;
18
+ /** @description Placeholder that replaces a value fully removed by the user's mask. */
19
+ export const FULLY_MASKED = "<fully masked>";
20
+ /** @description Span attributes holding trace and observation input or output, which the mask applies to. */
21
+ export const CONTENT_KEYS = new Set([
22
+ "langfuse.observation.input",
23
+ "langfuse.observation.output",
24
+ "langfuse.trace.input",
25
+ "langfuse.trace.output",
26
+ ]);
27
+ /** @description Attribute prefixes whose values are metadata entries. */
28
+ export const METADATA_PREFIXES = [
29
+ "langfuse.observation.metadata.",
30
+ "langfuse.trace.metadata.",
31
+ ];
32
+ /** @description OTLP numeric status code for each span status. */
33
+ export const OTLP_STATUS_CODES = {
34
+ [SpanStatus.UNSET]: 0,
35
+ [SpanStatus.WARNING]: 0,
36
+ [SpanStatus.OK]: 1,
37
+ [SpanStatus.ERROR]: 2,
38
+ };
@@ -0,0 +1,20 @@
1
+ /** @description Smallest per-string character budget when trimming span payloads. */
2
+ export declare const MIN_STRING_BUDGET_CHARS = 64;
3
+ /** @description Base64 payload length above which a value is treated as binary and replaced. */
4
+ export declare const BINARY_PAYLOAD_MIN_CHARS = 1024;
5
+ /** @description Placeholder written when a span value cannot be serialized. */
6
+ export declare const UNSERIALIZABLE_VALUE = "[unserializable]";
7
+ /** @description Object keys whose values are treated as binary payloads. */
8
+ export declare const BINARY_KEYS: Set<string>;
9
+ /** @description Placeholder that replaces a masked credential in span content. */
10
+ export declare const REDACTED_VALUE = "[REDACTED]";
11
+ /** @description Finish reasons that mark a completed model call as a Langfuse warning. */
12
+ export declare const WARNING_FINISH_REASONS: Set<string>;
13
+ /** @description Room for the "...[truncated N chars]" note, kept outside the opening a cap preserves. */
14
+ export declare const TRUNCATION_NOTE_ALLOWANCE_CHARS = 48;
15
+ /** @description Name of the OpenTelemetry context key that carries a trace's full metadata. */
16
+ export declare const TRACE_METADATA_CONTEXT_KEY = "neurolink.trace.metadata";
17
+ /** @description Log prefix for NeuroLink's OpenTelemetry setup. */
18
+ export declare const OTEL_LOG_PREFIX = "[OpenTelemetry]";
19
+ /** @description Most open and most ended spans the exporter span processor remembers for re-parenting; the oldest are forgotten first. */
20
+ export declare const MAX_REMEMBERED_SPANS = 5000;
@@ -0,0 +1,34 @@
1
+ /** @description Smallest per-string character budget when trimming span payloads. */
2
+ export const MIN_STRING_BUDGET_CHARS = 64;
3
+ /** @description Base64 payload length above which a value is treated as binary and replaced. */
4
+ export const BINARY_PAYLOAD_MIN_CHARS = 1_024;
5
+ /** @description Placeholder written when a span value cannot be serialized. */
6
+ export const UNSERIALIZABLE_VALUE = "[unserializable]";
7
+ /** @description Object keys whose values are treated as binary payloads. */
8
+ export const BINARY_KEYS = new Set([
9
+ "data",
10
+ "image",
11
+ "bytes",
12
+ "base64",
13
+ "b64json",
14
+ ]);
15
+ /** @description Placeholder that replaces a masked credential in span content. */
16
+ export const REDACTED_VALUE = "[REDACTED]";
17
+ /** @description Finish reasons that mark a completed model call as a Langfuse warning. */
18
+ export const WARNING_FINISH_REASONS = new Set([
19
+ "length",
20
+ "max_tokens",
21
+ "content-filter",
22
+ "content_filter",
23
+ "abort",
24
+ "aborted",
25
+ "no-output",
26
+ ]);
27
+ /** @description Room for the "...[truncated N chars]" note, kept outside the opening a cap preserves. */
28
+ export const TRUNCATION_NOTE_ALLOWANCE_CHARS = 48;
29
+ /** @description Name of the OpenTelemetry context key that carries a trace's full metadata. */
30
+ export const TRACE_METADATA_CONTEXT_KEY = "neurolink.trace.metadata";
31
+ /** @description Log prefix for NeuroLink's OpenTelemetry setup. */
32
+ export const OTEL_LOG_PREFIX = "[OpenTelemetry]";
33
+ /** @description Most open and most ended spans the exporter span processor remembers for re-parenting; the oldest are forgotten first. */
34
+ export const MAX_REMEMBERED_SPANS = 5_000;
@@ -3,6 +3,7 @@ import type { EvaluationData } from "../index.js";
3
3
  import type { NeuroLink } from "../neurolink.js";
4
4
  import type { FileToolRootPolicy, UnknownRecord, MiddlewareFactoryOptions, StreamOptions, StreamResult, AIProvider, AnalyticsData, EnhancedGenerateResult, TextGenerationOptions, TextGenerationResult, ValidationSchema, EmbedInput, DecisionRequest, DecisionResult, MemoryToolCallRecord, MemoryToolResultRecord, ToolExecutionSummaryInternal } from "../types/index.js";
5
5
  import { TelemetryHandler } from "./modules/TelemetryHandler.js";
6
+ import { ToolsManager } from "./modules/ToolsManager.js";
6
7
  import type { LanguageModel, ModelMessage, RawUsageObject, Tool, ToolCallRepairFunction, ToolSet } from "../types/index.js";
7
8
  export declare abstract class BaseProvider implements AIProvider {
8
9
  protected modelName: string;
@@ -25,7 +26,7 @@ export declare abstract class BaseProvider implements AIProvider {
25
26
  private streamHandler;
26
27
  protected telemetryHandler: TelemetryHandler;
27
28
  private utilities;
28
- private readonly toolsManager;
29
+ protected readonly toolsManager: ToolsManager;
29
30
  constructor(modelName?: string, providerName?: AIProviderName, neurolink?: NeuroLink, middleware?: MiddlewareFactoryOptions);
30
31
  /**
31
32
  * Update modelName and rebuild composition handlers with the new value.
@@ -174,7 +174,7 @@ export class BaseProvider {
174
174
  this.modelName = model;
175
175
  trace
176
176
  .getSpan(context.active())
177
- ?.setAttribute(ATTR.GEN_AI_MODEL, this.modelName);
177
+ ?.setAttribute(ATTR.NL_MODEL, this.modelName);
178
178
  this.messageBuilder = new MessageBuilder(this.providerName, this.modelName);
179
179
  this.streamHandler = new StreamHandler(this.providerName, this.modelName);
180
180
  this.telemetryHandler = new TelemetryHandler(this.providerName, this.modelName, this.neurolink);
@@ -1538,20 +1538,15 @@ export class BaseProvider {
1538
1538
  // model that failed rather than the one that served.
1539
1539
  const attempt = async () => {
1540
1540
  // OTEL span for provider-level generate tracing
1541
- // Use startActiveSpan pattern via context.with() so child spans become descendants
1542
- const otelSpan = tracers.provider.startSpan("neurolink.provider.generate", {
1541
+ const otelSpan = tracers.provider.startSpan("provider.generate", {
1543
1542
  kind: SpanKind.CLIENT,
1544
1543
  attributes: {
1545
- [ATTR.GEN_AI_SYSTEM]: this.providerName || "unknown",
1546
- [ATTR.GEN_AI_MODEL]: this.modelName || options.model || "unknown",
1547
- [ATTR.GEN_AI_OPERATION]: "generate",
1544
+ [ATTR.NL_MODEL]: this.modelName || options.model || "unknown",
1548
1545
  [ATTR.NL_PROVIDER]: this.providerName || "unknown",
1549
1546
  },
1550
1547
  });
1551
- // Set this span as the active context so child spans (provider calls, tool executions) become descendants
1552
- const activeCtx = trace.setSpan(context.active(), otelSpan);
1553
1548
  const otelSpanState = { ended: false };
1554
- return await context.with(activeCtx, async () => this.runGenerateInActiveContext(options, startTime, otelSpan, otelSpanState));
1549
+ return await this.runGenerateInActiveContext(options, startTime, otelSpan, otelSpanState);
1555
1550
  };
1556
1551
  // Callers that own fallback order (providerFallback / modelChain callers,
1557
1552
  // or a router that retries on its own) pass the flag on both paths;
@@ -1761,7 +1756,6 @@ export class BaseProvider {
1761
1756
  error,
1762
1757
  latency,
1763
1758
  };
1764
- this.telemetryHandler.recordTTSFailure(ttsProvider, error, latency);
1765
1759
  logger.error(`TTS synthesis failed in Mode 1 (direct input synthesis):`, ttsError);
1766
1760
  }
1767
1761
  return this.enhanceResult(baseResult, options, startTime);
@@ -2119,7 +2113,6 @@ export class BaseProvider {
2119
2113
  catch (ttsError) {
2120
2114
  const latency = Date.now() - ttsStartTime;
2121
2115
  const error = this.getTTSErrorDetails(ttsError);
2122
- this.telemetryHandler.recordTTSFailure(ttsProvider, error, latency);
2123
2116
  logger.error(`TTS synthesis failed in Mode 2 (AI response synthesis):`, ttsError);
2124
2117
  return {
2125
2118
  ...enhancedResult,
@@ -6,7 +6,7 @@ import { randomUUID } from "crypto";
6
6
  import { DEFAULT_MAX_SESSIONS, MEMORY_THRESHOLD_PERCENTAGE, MESSAGES_PER_TURN, } from "../config/conversationMemory.js";
7
7
  import { TokenUtils } from "../constants/tokens.js";
8
8
  import { SummarizationEngine } from "../context/summarizationEngine.js";
9
- import { runWithCurrentLangfuseContext } from "../services/server/ai/observability/instrumentation.js";
9
+ import { context } from "@opentelemetry/api";
10
10
  import { tracers, withSpan } from "../telemetry/index.js";
11
11
  import { ConversationMemoryError } from "../types/index.js";
12
12
  import { buildContextFromPointer, getEffectiveTokenThreshold, } from "../utils/conversationMemory.js";
@@ -67,7 +67,7 @@ export class ConversationMemoryManager {
67
67
  */
68
68
  async storeConversationTurn(options) {
69
69
  return withSpan({
70
- name: "neurolink.memory.storeTurn",
70
+ name: "memory.storeTurn",
71
71
  tracer: tracers.memory,
72
72
  attributes: {
73
73
  "memory.type": "in-memory",
@@ -114,10 +114,7 @@ export class ConversationMemoryManager {
114
114
  if (shouldSummarize) {
115
115
  // Only trigger summarization if not already in progress for this session
116
116
  if (!this.summarizationInProgress.has(options.sessionId)) {
117
- // Capture the current Langfuse ALS context before setImmediate,
118
- // which breaks automatic AsyncLocalStorage propagation and would
119
- // otherwise cause orphaned traces in Langfuse.
120
- const summarizeWithContext = runWithCurrentLangfuseContext(async () => {
117
+ const summarizeWithContext = context.bind(context.active(), async () => {
121
118
  try {
122
119
  await this.checkAndSummarize(session, tokenThreshold, options.requestId);
123
120
  }
@@ -229,7 +226,7 @@ export class ConversationMemoryManager {
229
226
  */
230
227
  async buildContextMessages(sessionId, _userId, _enableSummarization, requestId) {
231
228
  return withSpan({
232
- name: "neurolink.memory.buildContext",
229
+ name: "memory.buildContext",
233
230
  tracer: tracers.memory,
234
231
  attributes: {
235
232
  "memory.type": "in-memory",
@@ -345,7 +342,7 @@ export class ConversationMemoryManager {
345
342
  }
346
343
  async clearSession(sessionId) {
347
344
  return withSpan({
348
- name: "neurolink.memory.clear",
345
+ name: "memory.clear",
349
346
  tracer: tracers.memory,
350
347
  attributes: {
351
348
  "memory.type": "in-memory",
@@ -214,7 +214,7 @@ export class AIProviderFactory {
214
214
  static async createProvider(providerName, modelName, enableMCP = true, sdk, region, credentials) {
215
215
  const functionTag = "AIProviderFactory.createProvider";
216
216
  // Providers are registered via ProviderFactory.initialize() on first use
217
- return factoryTracer.startActiveSpan("neurolink.factory.createProvider", {
217
+ return factoryTracer.startActiveSpan("factory.createProvider", {
218
218
  kind: SpanKind.INTERNAL,
219
219
  attributes: {
220
220
  "provider.name": providerName,
@@ -16,5 +16,12 @@
16
16
  * and original -> sanitized when writing `functionResponse` parts. Neither the
17
17
  * engine nor any other adapter needs to know sanitization happened.
18
18
  */
19
- import type { AgenticLoopAdapter, GeminiLoopAdapterConfig, GeminiStepRaw, GeminiTurnContent } from "../types/index.js";
19
+ import type { AgenticLoopAdapter, GeminiLoopAdapterConfig, GeminiStepRaw, GeminiStepRequest, GeminiTurnContent, GenerationRecord } from "../types/index.js";
20
+ /**
21
+ * @description Describes one Gemini request as the request half of a Langfuse generation.
22
+ * @param provider {string} - Provider name used to price the call.
23
+ * @param request {GeminiStepRequest} - Model, contents and generation config sent.
24
+ * @returns {GenerationRecord} Provider, model, sampling and thinking parameters, and the system instruction plus contents.
25
+ */
26
+ export declare function describeGeminiRequest(provider: string, request: GeminiStepRequest): GenerationRecord;
20
27
  export declare function createGeminiLoopAdapter(config: GeminiLoopAdapterConfig): AgenticLoopAdapter<GeminiTurnContent[], GeminiStepRaw>;
@@ -19,6 +19,28 @@
19
19
  import { guardToolExecutor } from "./toolExecutionGuards.js";
20
20
  import { resolveLiveTool } from "../tools/toolDiscovery.js";
21
21
  import { collectStreamChunksIncremental, extractTextFromParts, mapGeminiFinishReason, pushModelResponseToHistory, refreshNativeToolDeclarations, } from "../providers/googleNativeGemini3/utils.js";
22
+ /**
23
+ * @description Describes one Gemini request as the request half of a Langfuse generation.
24
+ * @param provider {string} - Provider name used to price the call.
25
+ * @param request {GeminiStepRequest} - Model, contents and generation config sent.
26
+ * @returns {GenerationRecord} Provider, model, sampling and thinking parameters, and the system instruction plus contents.
27
+ */
28
+ export function describeGeminiRequest(provider, request) {
29
+ const settings = (request.config ?? {});
30
+ const thinking = settings.thinkingConfig;
31
+ return {
32
+ provider,
33
+ model: request.model,
34
+ modelParameters: {
35
+ temperature: settings.temperature,
36
+ maxTokens: settings.maxOutputTokens,
37
+ topP: settings.topP,
38
+ thinkingLevel: thinking?.thinkingLevel,
39
+ thinkingBudget: thinking?.thinkingBudget,
40
+ },
41
+ input: { system: settings.systemInstruction, messages: request.contents },
42
+ };
43
+ }
22
44
  export function createGeminiLoopAdapter(config) {
23
45
  /**
24
46
  * Sanitized wire name -> the name the caller registered. Rebuilt per step
@@ -62,8 +84,10 @@ export function createGeminiLoopAdapter(config) {
62
84
  const hydratedToolNames = config.declarations
63
85
  ? refreshNativeToolDeclarations(config.liveTools, config.declarations)
64
86
  : [];
87
+ const raw = config.buildRequest(conversation, step);
65
88
  return {
66
- raw: config.buildRequest(conversation, step),
89
+ raw,
90
+ generation: describeGeminiRequest(config.provider, raw),
67
91
  ...(hydratedToolNames.length > 0 ? { hydratedToolNames } : {}),
68
92
  };
69
93
  },
@@ -1,4 +1,6 @@
1
1
  import { createStreamChannel } from "./streamChannel.js";
2
+ import { observeGeneration } from "../telemetry/observation.js";
3
+ import { StepSpans, withGeneration } from "../telemetry/withSpan.js";
2
4
  import { logger } from "../utils/logger.js";
3
5
  import { withProviderRetry } from "../utils/providerRetry.js";
4
6
  import { resolveToolTimeoutMs } from "./constants.js";
@@ -266,6 +268,67 @@ async function dispatchStepTools(params) {
266
268
  }
267
269
  return { toolResults, executions, dispatched, abortedMidBatch };
268
270
  }
271
+ async function executeObservedStep(adapter, request, channel, signal, span) {
272
+ // Pre-first-chunk 429/5xx retry: watch whether THIS attempt of
273
+ // THIS step pushes anything to the shared channel before it
274
+ // throws. `hasEmitted` resets at the top of every attempt
275
+ // withProviderRetry makes; the instant an attempt emits and then
276
+ // throws, the thrown error is rewrapped as a PostEmissionStepError
277
+ // (no status/branding info survives the rewrap), which
278
+ // isRetryableProviderError() duck-types as non-retryable — so
279
+ // withProviderRetry gives up immediately instead of sleeping and
280
+ // re-invoking executeStep, which would duplicate/interleave
281
+ // output already sent to the consumer. The original error (not
282
+ // the wrapper) is what the caller of runAgenticLoop ultimately
283
+ // sees, via the unwrap in the catch below.
284
+ let hasEmitted = false;
285
+ let firstChunkAt;
286
+ const watchedChannel = {
287
+ push: (chunk) => {
288
+ hasEmitted = true;
289
+ firstChunkAt ??= new Date();
290
+ channel.push(chunk);
291
+ },
292
+ };
293
+ return withGeneration(request.generation ?? {}, async (generationSpan) => {
294
+ let result;
295
+ try {
296
+ result = await withProviderRetry(async () => {
297
+ hasEmitted = false;
298
+ try {
299
+ return await adapter.executeStep(request, watchedChannel, signal);
300
+ }
301
+ catch (err) {
302
+ throw hasEmitted ? new PostEmissionStepError(err) : err;
303
+ }
304
+ }, span, `${adapter.providerLabel}.step`, undefined, signal);
305
+ }
306
+ catch (err) {
307
+ throw err instanceof PostEmissionStepError ? err.cause : err;
308
+ }
309
+ observeGeneration(generationSpan, {
310
+ provider: request.generation?.provider,
311
+ model: request.generation?.model,
312
+ responseModel: result.model,
313
+ output: {
314
+ text: result.text,
315
+ ...(result.reasoning ? { reasoning: result.reasoning } : {}),
316
+ ...(result.toolCalls.length > 0 ? { toolCalls: result.toolCalls } : {}),
317
+ },
318
+ usage: {
319
+ inputTokens: result.usage.inputTokens,
320
+ outputTokens: result.usage.outputTokens,
321
+ cacheReadTokens: result.usage.cacheReadTokens,
322
+ cacheCreationTokens: result.usage.cacheWriteTokens,
323
+ reasoningTokens: result.usage.reasoningTokens,
324
+ },
325
+ finishReason: result.rawStopReason,
326
+ aborted: signal.aborted,
327
+ completionStartTime: firstChunkAt,
328
+ });
329
+ return result;
330
+ });
331
+ }
269
332
  /**
270
333
  * Run one adapter-parameterized agentic tool-calling turn. Owns the
271
334
  * maxSteps-bounded loop, generic tool dispatch (with an opt-in
@@ -307,11 +370,13 @@ export function runAgenticLoop(adapter, initialConversation, options) {
307
370
  // ever writes to it, so the loop is the same loop it was.
308
371
  let stepCap = adapter.maxSteps;
309
372
  const turnStartedAt = Date.now();
373
+ const stepSpans = new StepSpans();
310
374
  try {
311
375
  for (let step = 0; step < stepCap; step++) {
312
376
  if (internalAbort.signal.aborted) {
313
377
  break;
314
378
  }
379
+ stepSpans.begin(step + 1);
315
380
  if (adapter.planReclaim) {
316
381
  const reclaimed = adapter.planReclaim(conversation, step);
317
382
  if (reclaimed?.conversation !== undefined) {
@@ -333,44 +398,7 @@ export function runAgenticLoop(adapter, initialConversation, options) {
333
398
  for (const name of request.hydratedToolNames ?? []) {
334
399
  failedTools.delete(name);
335
400
  }
336
- // Pre-first-chunk 429/5xx retry: watch whether THIS attempt of
337
- // THIS step pushes anything to the shared channel before it
338
- // throws. `hasEmitted` resets at the top of every attempt
339
- // withProviderRetry makes; the instant an attempt emits and then
340
- // throws, the thrown error is rewrapped as a PostEmissionStepError
341
- // (no status/branding info survives the rewrap), which
342
- // isRetryableProviderError() duck-types as non-retryable — so
343
- // withProviderRetry gives up immediately instead of sleeping and
344
- // re-invoking executeStep, which would duplicate/interleave
345
- // output already sent to the consumer. The original error (not
346
- // the wrapper) is what the caller of runAgenticLoop ultimately
347
- // sees, via the unwrap in the catch below.
348
- let hasEmitted = false;
349
- const watchedChannel = {
350
- push: (chunk) => {
351
- hasEmitted = true;
352
- channel.push(chunk);
353
- },
354
- };
355
- let stepResult;
356
- try {
357
- stepResult = await withProviderRetry(async () => {
358
- hasEmitted = false;
359
- try {
360
- return await adapter.executeStep(request, watchedChannel, internalAbort.signal);
361
- }
362
- catch (err) {
363
- throw hasEmitted ? new PostEmissionStepError(err) : err;
364
- }
365
- },
366
- // The caller's span, when it passes one. withProviderRetry writes
367
- // gen_ai.provider.total_attempts here, so a loop that threaded a
368
- // span before it moved onto this engine keeps emitting it.
369
- options.span, `${adapter.providerLabel}.step`, undefined, internalAbort.signal);
370
- }
371
- catch (err) {
372
- throw err instanceof PostEmissionStepError ? err.cause : err;
373
- }
401
+ const stepResult = await stepSpans.run(() => executeObservedStep(adapter, request, channel, internalAbort.signal, options.span));
374
402
  usage = sumUsage(usage, stepResult.usage);
375
403
  rawStopReason = stepResult.rawStopReason;
376
404
  lastStepText = stepResult.text || lastStepText;
@@ -391,14 +419,14 @@ export function runAgenticLoop(adapter, initialConversation, options) {
391
419
  if (step === stepCap - 1) {
392
420
  hadToolCallsAtCap = true;
393
421
  }
394
- const dispatch = await dispatchStepTools({
422
+ const dispatch = await stepSpans.run(() => dispatchStepTools({
395
423
  calls: stepResult.toolCalls,
396
424
  adapter,
397
425
  tools: options.tools,
398
426
  failedTools,
399
427
  abortSignal: internalAbort.signal,
400
428
  toolTimeoutMs: resolveToolTimeoutMs(options.toolTimeoutMs),
401
- });
429
+ }));
402
430
  const toolResults = dispatch.toolResults;
403
431
  allToolCalls.push(...dispatch.dispatched);
404
432
  allToolExecutions.push(...dispatch.executions);
@@ -510,6 +538,7 @@ export function runAgenticLoop(adapter, initialConversation, options) {
510
538
  throw err;
511
539
  }
512
540
  finally {
541
+ stepSpans.end();
513
542
  // close() after error() is harmless: it only flips `done`, and
514
543
  // streamChannel.error() keeps its error state intact regardless of a
515
544
  // later close() call.
@@ -72,7 +72,7 @@ export class MessageBuilder {
72
72
  */
73
73
  async buildMessages(options) {
74
74
  return withSpan({
75
- name: "neurolink.message.build",
75
+ name: "message.build",
76
76
  tracer: tracers.sdk,
77
77
  attributes: {
78
78
  [ATTR.NL_PROVIDER]: this.providerName,
@@ -220,7 +220,7 @@ export class MessageBuilder {
220
220
  */
221
221
  async buildMessagesForStream(options) {
222
222
  return withSpan({
223
- name: "neurolink.message.build_for_stream",
223
+ name: "message.build_for_stream",
224
224
  tracer: tracers.sdk,
225
225
  attributes: {
226
226
  [ATTR.NL_PROVIDER]: this.providerName,
@@ -35,7 +35,7 @@ export class StreamHandler {
35
35
  * Validate stream options - consolidates validation from 7/10 providers
36
36
  */
37
37
  validateStreamOptions(options) {
38
- const span = tracers.stream.startSpan("neurolink.stream.validate", {
38
+ const span = tracers.stream.startSpan("stream.validate", {
39
39
  attributes: {
40
40
  [ATTR.NL_PROVIDER]: this.providerName,
41
41
  [ATTR.NL_MODEL]: this.modelName,
@@ -189,7 +189,7 @@ export class StreamHandler {
189
189
  */
190
190
  async createStreamAnalytics(result, startTime, options) {
191
191
  return withSpan({
192
- name: "neurolink.stream.analytics",
192
+ name: "stream.analytics",
193
193
  tracer: tracers.stream,
194
194
  attributes: {
195
195
  [ATTR.NL_PROVIDER]: this.providerName,
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Telemetry Handler Module
3
3
  *
4
- * Handles analytics, evaluation, performance metrics, and telemetry configuration.
4
+ * Handles analytics, evaluation and performance metrics.
5
5
  * Extracted from BaseProvider to follow Single Responsibility Principle.
6
6
  *
7
7
  * Responsibilities:
@@ -9,12 +9,11 @@
9
9
  * - Evaluation generation
10
10
  * - Performance metrics recording
11
11
  * - Cost calculation
12
- * - Telemetry configuration
13
12
  *
14
13
  * @module core/modules/TelemetryHandler
15
14
  */
16
15
  import type { NeuroLink } from "../../neurolink.js";
17
- import type { StreamOptions, AIProviderName, AnalyticsData, EnhancedGenerateResult, EvaluationData, MemoryToolCallRecord, MemoryToolResultRecord, RawUsageObject, TextGenerationOptions, TokenUsage, TTSMetadata } from "../../types/index.js";
16
+ import type { StreamOptions, AIProviderName, AnalyticsData, EnhancedGenerateResult, EvaluationData, MemoryToolCallRecord, MemoryToolResultRecord, RawUsageObject, TextGenerationOptions, TokenUsage } from "../../types/index.js";
18
17
  /**
19
18
  * TelemetryHandler class - Handles analytics and telemetry for AI providers
20
19
  */
@@ -35,26 +34,11 @@ export declare class TelemetryHandler {
35
34
  * Record performance metrics for a generation
36
35
  */
37
36
  recordPerformanceMetrics(usage: RawUsageObject | undefined, responseTime: number): Promise<void>;
38
- /**
39
- * Record a TTS synthesis failure without affecting the generation result.
40
- */
41
- recordTTSFailure(ttsProvider: string, error: NonNullable<TTSMetadata["error"]>, latency: number): void;
42
37
  /**
43
38
  * Estimate cost only from known per-model rates. Unknown pricing remains
44
39
  * absent from cost metrics instead of using another model's default rate.
45
40
  */
46
41
  calculateActualCost(usage: TokenUsage): Promise<number | undefined>;
47
- /**
48
- * Create telemetry configuration for Vercel AI SDK experimental_telemetry
49
- * This enables automatic OpenTelemetry tracing when telemetry is enabled
50
- */
51
- getTelemetryConfig(options: StreamOptions | TextGenerationOptions, operationType?: "stream" | "generate"): {
52
- isEnabled: boolean;
53
- functionId?: string;
54
- metadata?: Record<string, string | number | boolean>;
55
- recordInputs?: boolean;
56
- recordOutputs?: boolean;
57
- } | undefined;
58
42
  /**
59
43
  * Handle tool execution storage if available
60
44
  */