@juspay/neurolink 9.24.0 → 9.25.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 (219) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/adapters/tts/googleTTSHandler.js +26 -1
  3. package/dist/adapters/video/vertexVideoHandler.js +23 -17
  4. package/dist/cli/commands/config.d.ts +3 -3
  5. package/dist/cli/commands/observability.d.ts +53 -0
  6. package/dist/cli/commands/observability.js +453 -0
  7. package/dist/cli/commands/telemetry.d.ts +63 -0
  8. package/dist/cli/commands/telemetry.js +689 -0
  9. package/dist/cli/factories/commandFactory.js +29 -15
  10. package/dist/cli/parser.js +6 -9
  11. package/dist/cli/utils/formatters.d.ts +13 -0
  12. package/dist/cli/utils/formatters.js +23 -0
  13. package/dist/constants/contextWindows.js +6 -0
  14. package/dist/constants/enums.d.ts +6 -0
  15. package/dist/constants/enums.js +8 -2
  16. package/dist/context/budgetChecker.js +75 -48
  17. package/dist/context/contextCompactor.js +135 -127
  18. package/dist/core/baseProvider.d.ts +5 -0
  19. package/dist/core/baseProvider.js +117 -110
  20. package/dist/core/conversationMemoryInitializer.js +7 -4
  21. package/dist/core/conversationMemoryManager.d.ts +2 -0
  22. package/dist/core/conversationMemoryManager.js +6 -2
  23. package/dist/core/modules/GenerationHandler.d.ts +2 -2
  24. package/dist/core/modules/GenerationHandler.js +12 -12
  25. package/dist/evaluation/ragasEvaluator.js +39 -19
  26. package/dist/evaluation/scoring.js +46 -20
  27. package/dist/features/ppt/presentationOrchestrator.js +23 -0
  28. package/dist/features/ppt/slideGenerator.js +13 -0
  29. package/dist/features/ppt/slideRenderers.d.ts +1 -1
  30. package/dist/features/ppt/slideRenderers.js +6 -4
  31. package/dist/features/ppt/slideTypeInference.d.ts +1 -1
  32. package/dist/features/ppt/slideTypeInference.js +75 -73
  33. package/dist/files/fileTools.d.ts +6 -6
  34. package/dist/index.d.ts +46 -12
  35. package/dist/index.js +79 -17
  36. package/dist/lib/adapters/tts/googleTTSHandler.js +26 -1
  37. package/dist/lib/adapters/video/vertexVideoHandler.js +23 -17
  38. package/dist/lib/constants/contextWindows.js +6 -0
  39. package/dist/lib/constants/enums.d.ts +6 -0
  40. package/dist/lib/constants/enums.js +8 -2
  41. package/dist/lib/context/budgetChecker.js +75 -48
  42. package/dist/lib/context/contextCompactor.js +135 -127
  43. package/dist/lib/core/baseProvider.d.ts +5 -0
  44. package/dist/lib/core/baseProvider.js +117 -110
  45. package/dist/lib/core/conversationMemoryInitializer.js +7 -4
  46. package/dist/lib/core/conversationMemoryManager.d.ts +2 -0
  47. package/dist/lib/core/conversationMemoryManager.js +6 -2
  48. package/dist/lib/core/modules/GenerationHandler.d.ts +2 -2
  49. package/dist/lib/core/modules/GenerationHandler.js +12 -12
  50. package/dist/lib/evaluation/ragasEvaluator.js +39 -19
  51. package/dist/lib/evaluation/scoring.js +46 -20
  52. package/dist/lib/features/ppt/presentationOrchestrator.js +23 -0
  53. package/dist/lib/features/ppt/slideGenerator.js +13 -0
  54. package/dist/lib/features/ppt/slideRenderers.d.ts +1 -1
  55. package/dist/lib/features/ppt/slideRenderers.js +6 -4
  56. package/dist/lib/features/ppt/slideTypeInference.d.ts +1 -1
  57. package/dist/lib/features/ppt/slideTypeInference.js +75 -73
  58. package/dist/lib/files/fileTools.d.ts +6 -6
  59. package/dist/lib/index.d.ts +46 -12
  60. package/dist/lib/index.js +79 -17
  61. package/dist/lib/mcp/httpRateLimiter.js +39 -12
  62. package/dist/lib/mcp/httpRetryHandler.js +22 -1
  63. package/dist/lib/mcp/mcpClientFactory.js +13 -15
  64. package/dist/lib/memory/memoryRetrievalTools.js +22 -0
  65. package/dist/lib/neurolink.d.ts +64 -72
  66. package/dist/lib/neurolink.js +1007 -564
  67. package/dist/lib/observability/exporterRegistry.d.ts +152 -0
  68. package/dist/lib/observability/exporterRegistry.js +414 -0
  69. package/dist/lib/observability/exporters/arizeExporter.d.ts +32 -0
  70. package/dist/lib/observability/exporters/arizeExporter.js +139 -0
  71. package/dist/lib/observability/exporters/baseExporter.d.ts +117 -0
  72. package/dist/lib/observability/exporters/baseExporter.js +191 -0
  73. package/dist/lib/observability/exporters/braintrustExporter.d.ts +30 -0
  74. package/dist/lib/observability/exporters/braintrustExporter.js +155 -0
  75. package/dist/lib/observability/exporters/datadogExporter.d.ts +37 -0
  76. package/dist/lib/observability/exporters/datadogExporter.js +197 -0
  77. package/dist/lib/observability/exporters/index.d.ts +13 -0
  78. package/dist/lib/observability/exporters/index.js +14 -0
  79. package/dist/lib/observability/exporters/laminarExporter.d.ts +48 -0
  80. package/dist/lib/observability/exporters/laminarExporter.js +303 -0
  81. package/dist/lib/observability/exporters/langfuseExporter.d.ts +47 -0
  82. package/dist/lib/observability/exporters/langfuseExporter.js +204 -0
  83. package/dist/lib/observability/exporters/langsmithExporter.d.ts +26 -0
  84. package/dist/lib/observability/exporters/langsmithExporter.js +124 -0
  85. package/dist/lib/observability/exporters/otelExporter.d.ts +39 -0
  86. package/dist/lib/observability/exporters/otelExporter.js +165 -0
  87. package/dist/lib/observability/exporters/posthogExporter.d.ts +48 -0
  88. package/dist/lib/observability/exporters/posthogExporter.js +288 -0
  89. package/dist/lib/observability/exporters/sentryExporter.d.ts +32 -0
  90. package/dist/lib/observability/exporters/sentryExporter.js +166 -0
  91. package/dist/lib/observability/index.d.ts +25 -0
  92. package/dist/lib/observability/index.js +32 -0
  93. package/dist/lib/observability/metricsAggregator.d.ts +260 -0
  94. package/dist/lib/observability/metricsAggregator.js +557 -0
  95. package/dist/lib/observability/otelBridge.d.ts +49 -0
  96. package/dist/lib/observability/otelBridge.js +132 -0
  97. package/dist/lib/observability/retryPolicy.d.ts +192 -0
  98. package/dist/lib/observability/retryPolicy.js +384 -0
  99. package/dist/lib/observability/sampling/index.d.ts +4 -0
  100. package/dist/lib/observability/sampling/index.js +5 -0
  101. package/dist/lib/observability/sampling/samplers.d.ts +116 -0
  102. package/dist/lib/observability/sampling/samplers.js +217 -0
  103. package/dist/lib/observability/spanProcessor.d.ts +129 -0
  104. package/dist/lib/observability/spanProcessor.js +304 -0
  105. package/dist/lib/observability/tokenTracker.d.ts +156 -0
  106. package/dist/lib/observability/tokenTracker.js +414 -0
  107. package/dist/lib/observability/types/exporterTypes.d.ts +250 -0
  108. package/dist/lib/observability/types/exporterTypes.js +6 -0
  109. package/dist/lib/observability/types/index.d.ts +6 -0
  110. package/dist/lib/observability/types/index.js +5 -0
  111. package/dist/lib/observability/types/spanTypes.d.ts +244 -0
  112. package/dist/lib/observability/types/spanTypes.js +93 -0
  113. package/dist/lib/observability/utils/index.d.ts +4 -0
  114. package/dist/lib/observability/utils/index.js +5 -0
  115. package/dist/lib/observability/utils/safeMetadata.d.ts +10 -0
  116. package/dist/lib/observability/utils/safeMetadata.js +26 -0
  117. package/dist/lib/observability/utils/spanSerializer.d.ts +115 -0
  118. package/dist/lib/observability/utils/spanSerializer.js +291 -0
  119. package/dist/lib/providers/amazonSagemaker.d.ts +5 -4
  120. package/dist/lib/providers/amazonSagemaker.js +3 -4
  121. package/dist/lib/providers/googleVertex.d.ts +7 -0
  122. package/dist/lib/providers/googleVertex.js +76 -2
  123. package/dist/lib/rag/pipeline/RAGPipeline.d.ts +0 -5
  124. package/dist/lib/rag/pipeline/RAGPipeline.js +122 -87
  125. package/dist/lib/rag/ragIntegration.js +30 -0
  126. package/dist/lib/rag/retrieval/hybridSearch.js +22 -0
  127. package/dist/lib/server/abstract/baseServerAdapter.js +51 -19
  128. package/dist/lib/server/middleware/common.js +44 -12
  129. package/dist/lib/services/server/ai/observability/instrumentation.d.ts +2 -2
  130. package/dist/lib/services/server/ai/observability/instrumentation.js +10 -5
  131. package/dist/lib/types/conversationMemoryInterface.d.ts +2 -0
  132. package/dist/lib/types/modelTypes.d.ts +18 -18
  133. package/dist/lib/types/providers.d.ts +5 -0
  134. package/dist/lib/utils/pricing.js +25 -1
  135. package/dist/lib/utils/ttsProcessor.js +74 -59
  136. package/dist/lib/workflow/config.d.ts +36 -36
  137. package/dist/lib/workflow/core/ensembleExecutor.js +10 -0
  138. package/dist/lib/workflow/core/judgeScorer.js +20 -2
  139. package/dist/lib/workflow/core/workflowRunner.js +34 -1
  140. package/dist/mcp/httpRateLimiter.js +39 -12
  141. package/dist/mcp/httpRetryHandler.js +22 -1
  142. package/dist/mcp/mcpClientFactory.js +13 -15
  143. package/dist/memory/memoryRetrievalTools.js +22 -0
  144. package/dist/neurolink.d.ts +64 -72
  145. package/dist/neurolink.js +1007 -564
  146. package/dist/observability/FEATURE-STATUS.md +269 -0
  147. package/dist/observability/exporterRegistry.d.ts +152 -0
  148. package/dist/observability/exporterRegistry.js +413 -0
  149. package/dist/observability/exporters/arizeExporter.d.ts +32 -0
  150. package/dist/observability/exporters/arizeExporter.js +138 -0
  151. package/dist/observability/exporters/baseExporter.d.ts +117 -0
  152. package/dist/observability/exporters/baseExporter.js +190 -0
  153. package/dist/observability/exporters/braintrustExporter.d.ts +30 -0
  154. package/dist/observability/exporters/braintrustExporter.js +154 -0
  155. package/dist/observability/exporters/datadogExporter.d.ts +37 -0
  156. package/dist/observability/exporters/datadogExporter.js +196 -0
  157. package/dist/observability/exporters/index.d.ts +13 -0
  158. package/dist/observability/exporters/index.js +13 -0
  159. package/dist/observability/exporters/laminarExporter.d.ts +48 -0
  160. package/dist/observability/exporters/laminarExporter.js +302 -0
  161. package/dist/observability/exporters/langfuseExporter.d.ts +47 -0
  162. package/dist/observability/exporters/langfuseExporter.js +203 -0
  163. package/dist/observability/exporters/langsmithExporter.d.ts +26 -0
  164. package/dist/observability/exporters/langsmithExporter.js +123 -0
  165. package/dist/observability/exporters/otelExporter.d.ts +39 -0
  166. package/dist/observability/exporters/otelExporter.js +164 -0
  167. package/dist/observability/exporters/posthogExporter.d.ts +48 -0
  168. package/dist/observability/exporters/posthogExporter.js +287 -0
  169. package/dist/observability/exporters/sentryExporter.d.ts +32 -0
  170. package/dist/observability/exporters/sentryExporter.js +165 -0
  171. package/dist/observability/index.d.ts +25 -0
  172. package/dist/observability/index.js +31 -0
  173. package/dist/observability/metricsAggregator.d.ts +260 -0
  174. package/dist/observability/metricsAggregator.js +556 -0
  175. package/dist/observability/otelBridge.d.ts +49 -0
  176. package/dist/observability/otelBridge.js +131 -0
  177. package/dist/observability/retryPolicy.d.ts +192 -0
  178. package/dist/observability/retryPolicy.js +383 -0
  179. package/dist/observability/sampling/index.d.ts +4 -0
  180. package/dist/observability/sampling/index.js +4 -0
  181. package/dist/observability/sampling/samplers.d.ts +116 -0
  182. package/dist/observability/sampling/samplers.js +216 -0
  183. package/dist/observability/spanProcessor.d.ts +129 -0
  184. package/dist/observability/spanProcessor.js +303 -0
  185. package/dist/observability/tokenTracker.d.ts +156 -0
  186. package/dist/observability/tokenTracker.js +413 -0
  187. package/dist/observability/types/exporterTypes.d.ts +250 -0
  188. package/dist/observability/types/exporterTypes.js +5 -0
  189. package/dist/observability/types/index.d.ts +6 -0
  190. package/dist/observability/types/index.js +4 -0
  191. package/dist/observability/types/spanTypes.d.ts +244 -0
  192. package/dist/observability/types/spanTypes.js +92 -0
  193. package/dist/observability/utils/index.d.ts +4 -0
  194. package/dist/observability/utils/index.js +4 -0
  195. package/dist/observability/utils/safeMetadata.d.ts +10 -0
  196. package/dist/observability/utils/safeMetadata.js +25 -0
  197. package/dist/observability/utils/spanSerializer.d.ts +115 -0
  198. package/dist/observability/utils/spanSerializer.js +290 -0
  199. package/dist/providers/amazonSagemaker.d.ts +5 -4
  200. package/dist/providers/amazonSagemaker.js +3 -4
  201. package/dist/providers/googleVertex.d.ts +7 -0
  202. package/dist/providers/googleVertex.js +76 -2
  203. package/dist/rag/pipeline/RAGPipeline.d.ts +0 -5
  204. package/dist/rag/pipeline/RAGPipeline.js +122 -87
  205. package/dist/rag/ragIntegration.js +30 -0
  206. package/dist/rag/retrieval/hybridSearch.js +22 -0
  207. package/dist/server/abstract/baseServerAdapter.js +51 -19
  208. package/dist/server/middleware/common.js +44 -12
  209. package/dist/services/server/ai/observability/instrumentation.d.ts +2 -2
  210. package/dist/services/server/ai/observability/instrumentation.js +10 -5
  211. package/dist/types/conversationMemoryInterface.d.ts +2 -0
  212. package/dist/types/providers.d.ts +5 -0
  213. package/dist/utils/pricing.js +25 -1
  214. package/dist/utils/ttsProcessor.js +74 -59
  215. package/dist/workflow/config.d.ts +52 -52
  216. package/dist/workflow/core/ensembleExecutor.js +10 -0
  217. package/dist/workflow/core/judgeScorer.js +20 -2
  218. package/dist/workflow/core/workflowRunner.js +34 -1
  219. package/package.json +1 -1
@@ -8,9 +8,11 @@
8
8
  * Stage 3: LLM Summarization (expensive -- requires LLM call)
9
9
  * Stage 4: Sliding Window Truncation (fallback -- no LLM call)
10
10
  */
11
- import { trace, SpanStatusCode } from "@opentelemetry/api";
12
11
  import { estimateMessagesTokens } from "../utils/tokenEstimation.js";
13
12
  import { logger } from "../utils/logger.js";
13
+ import { withTimeout } from "../utils/async/withTimeout.js";
14
+ import { SpanSerializer, SpanType, SpanStatus, } from "../observability/index.js";
15
+ import { getMetricsAggregator } from "../observability/index.js";
14
16
  import { pruneToolOutputs } from "./stages/toolOutputPruner.js";
15
17
  import { deduplicateFileReads } from "./stages/fileReadDeduplicator.js";
16
18
  import { summarizeMessages } from "./stages/structuredSummarizer.js";
@@ -38,154 +40,160 @@ export class ContextCompactor {
38
40
  * Run the multi-stage compaction pipeline until messages fit within budget.
39
41
  */
40
42
  async compact(messages, targetTokens, memoryConfig, requestId) {
41
- const compactionStartTime = Date.now();
42
- const provider = this.config.provider || undefined;
43
- const tokensBefore = estimateMessagesTokens(messages, provider);
44
- const stagesUsed = [];
45
- let currentMessages = [...messages];
46
- logger.info("[Compaction] Starting", {
47
- requestId,
48
- estimatedTokens: tokensBefore,
49
- budgetTokens: targetTokens,
43
+ const span = SpanSerializer.createSpan(SpanType.CONTEXT_COMPACTION, "context.compact", {
44
+ "context.operation": "compact",
45
+ "context.targetTokens": targetTokens,
50
46
  });
51
- // Stage 1: Tool Output Pruning
52
- if (this.config.enablePrune &&
53
- estimateMessagesTokens(currentMessages, provider) > targetTokens) {
54
- const stageTokensBefore = estimateMessagesTokens(currentMessages, provider);
55
- const pruneResult = pruneToolOutputs(currentMessages, {
56
- protectTokens: this.config.pruneProtectTokens,
57
- minimumSavings: this.config.pruneMinimumSavings,
58
- protectedTools: this.config.pruneProtectedTools,
59
- provider,
60
- });
61
- if (pruneResult.pruned) {
62
- currentMessages = pruneResult.messages;
63
- stagesUsed.push("prune");
64
- }
65
- const stageTokensAfter = estimateMessagesTokens(currentMessages, provider);
66
- logger.info("[Compaction] Stage 1 (prune)", {
47
+ const spanStartTime = Date.now();
48
+ try {
49
+ const provider = this.config.provider || undefined;
50
+ const tokensBefore = estimateMessagesTokens(messages, provider);
51
+ const stagesUsed = [];
52
+ let currentMessages = [...messages];
53
+ logger.info("[Compaction] Starting", {
67
54
  requestId,
68
- ran: pruneResult.pruned,
69
- tokensBefore: stageTokensBefore,
70
- tokensAfter: stageTokensAfter,
71
- saved: stageTokensBefore - stageTokensAfter,
55
+ estimatedTokens: tokensBefore,
56
+ budgetTokens: targetTokens,
72
57
  });
73
- }
74
- // Stage 2: File Read Deduplication
75
- if (this.config.enableDeduplicate &&
76
- estimateMessagesTokens(currentMessages, provider) > targetTokens) {
77
- const stageTokensBefore = estimateMessagesTokens(currentMessages, provider);
78
- const dedupResult = deduplicateFileReads(currentMessages);
79
- if (dedupResult.deduplicated) {
80
- currentMessages = dedupResult.messages;
81
- stagesUsed.push("deduplicate");
82
- }
83
- const stageTokensAfter = estimateMessagesTokens(currentMessages, provider);
84
- logger.info("[Compaction] Stage 2 (deduplicate)", {
85
- requestId,
86
- ran: dedupResult.deduplicated,
87
- tokensBefore: stageTokensBefore,
88
- tokensAfter: stageTokensAfter,
89
- saved: stageTokensBefore - stageTokensAfter,
90
- });
91
- }
92
- // Stage 3: LLM Summarization
93
- if (this.config.enableSummarize &&
94
- estimateMessagesTokens(currentMessages, provider) > targetTokens) {
95
- const stageTokensBefore = estimateMessagesTokens(currentMessages, provider);
96
- try {
97
- const summarizeResult = await summarizeMessages(currentMessages, {
98
- provider: this.config.summarizationProvider,
99
- model: this.config.summarizationModel,
100
- keepRecentRatio: this.config.keepRecentRatio,
101
- memoryConfig,
58
+ // Stage 1: Tool Output Pruning
59
+ if (this.config.enablePrune &&
60
+ estimateMessagesTokens(currentMessages, provider) > targetTokens) {
61
+ const stageTokensBefore = estimateMessagesTokens(currentMessages, provider);
62
+ const pruneResult = pruneToolOutputs(currentMessages, {
63
+ protectTokens: this.config.pruneProtectTokens,
64
+ minimumSavings: this.config.pruneMinimumSavings,
65
+ protectedTools: this.config.pruneProtectedTools,
66
+ provider,
102
67
  });
103
- if (summarizeResult.summarized) {
104
- currentMessages = summarizeResult.messages;
105
- stagesUsed.push("summarize");
68
+ if (pruneResult.pruned) {
69
+ currentMessages = pruneResult.messages;
70
+ stagesUsed.push("prune");
106
71
  }
107
72
  const stageTokensAfter = estimateMessagesTokens(currentMessages, provider);
108
- logger.info("[Compaction] Stage 3 (summarize)", {
73
+ logger.info("[Compaction] Stage 1 (prune)", {
109
74
  requestId,
110
- ran: summarizeResult.summarized,
75
+ ran: pruneResult.pruned,
111
76
  tokensBefore: stageTokensBefore,
112
77
  tokensAfter: stageTokensAfter,
113
78
  saved: stageTokensBefore - stageTokensAfter,
114
79
  });
115
80
  }
116
- catch (error) {
117
- // Capture the actual error for debugging
118
- const errorMessage = error instanceof Error ? error.message : String(error);
119
- const errorName = error instanceof Error ? error.name : "UnknownError";
120
- logger.warn("[Compaction] Stage 3 (summarize) FAILED", {
81
+ // Stage 2: File Read Deduplication
82
+ if (this.config.enableDeduplicate &&
83
+ estimateMessagesTokens(currentMessages, provider) > targetTokens) {
84
+ const stageTokensBefore = estimateMessagesTokens(currentMessages, provider);
85
+ const dedupResult = deduplicateFileReads(currentMessages);
86
+ if (dedupResult.deduplicated) {
87
+ currentMessages = dedupResult.messages;
88
+ stagesUsed.push("deduplicate");
89
+ }
90
+ const stageTokensAfter = estimateMessagesTokens(currentMessages, provider);
91
+ logger.info("[Compaction] Stage 2 (deduplicate)", {
121
92
  requestId,
122
- error: errorMessage,
123
- errorName,
93
+ ran: dedupResult.deduplicated,
124
94
  tokensBefore: stageTokensBefore,
125
- tokensAfter: stageTokensBefore,
126
- saved: 0,
95
+ tokensAfter: stageTokensAfter,
96
+ saved: stageTokensBefore - stageTokensAfter,
127
97
  });
128
- // Record on OTel span for trace visibility
129
- const activeSpan = trace.getActiveSpan();
130
- if (activeSpan) {
131
- activeSpan.addEvent("compaction.stage3.failed", {
132
- "error.message": errorMessage,
133
- "error.name": errorName,
134
- "stage.tokens_before": stageTokensBefore,
135
- });
136
- if (error instanceof Error) {
137
- activeSpan.recordException(error);
98
+ }
99
+ // Stage 3: LLM Summarization
100
+ if (this.config.enableSummarize &&
101
+ estimateMessagesTokens(currentMessages, provider) > targetTokens) {
102
+ const stageTokensBefore = estimateMessagesTokens(currentMessages, provider);
103
+ try {
104
+ const summarizeResult = await withTimeout(summarizeMessages(currentMessages, {
105
+ provider: this.config.summarizationProvider,
106
+ model: this.config.summarizationModel,
107
+ keepRecentRatio: this.config.keepRecentRatio,
108
+ memoryConfig,
109
+ }), 120_000, "LLM summarization timed out after 120s");
110
+ if (summarizeResult.summarized) {
111
+ currentMessages = summarizeResult.messages;
112
+ stagesUsed.push("summarize");
138
113
  }
139
- // NLK-GAP-005 fix: set error status alongside recordException
140
- activeSpan.setStatus({
141
- code: SpanStatusCode.ERROR,
142
- message: `Compaction stage 3 (summarize) failed: ${errorMessage}`,
114
+ const stageTokensAfter = estimateMessagesTokens(currentMessages, provider);
115
+ logger.info("[Compaction] Stage 3 (summarize)", {
116
+ requestId,
117
+ ran: summarizeResult.summarized,
118
+ tokensBefore: stageTokensBefore,
119
+ tokensAfter: stageTokensAfter,
120
+ saved: stageTokensBefore - stageTokensAfter,
121
+ });
122
+ }
123
+ catch (error) {
124
+ const err = error instanceof Error ? error : new Error(String(error));
125
+ logger.warn("[Compaction] Stage 3 (summarize) FAILED", {
126
+ requestId,
127
+ error: err.message,
128
+ errorName: err.name,
129
+ tokensBefore: stageTokensBefore,
130
+ tokensAfter: stageTokensBefore,
131
+ saved: 0,
132
+ });
133
+ // Record failure on the compaction span for trace visibility
134
+ SpanSerializer.updateAttributes(span, {
135
+ "compaction.stage3.error": err.message,
136
+ "compaction.stage3.errorName": err.name,
137
+ "compaction.stage3.tokensBefore": stageTokensBefore,
138
+ "compaction.stage3_failed": true,
143
139
  });
140
+ // Fall through to Stage 4 truncation as before
144
141
  }
145
- // Fall through to Stage 4 truncation as before
146
142
  }
147
- }
148
- // Stage 4: Sliding Window Truncation (fallback)
149
- if (this.config.enableTruncate &&
150
- estimateMessagesTokens(currentMessages, provider) > targetTokens) {
151
- const stageTokensBefore = estimateMessagesTokens(currentMessages, provider);
152
- const truncResult = truncateWithSlidingWindow(currentMessages, {
153
- fraction: this.config.truncationFraction,
154
- currentTokens: stageTokensBefore,
155
- targetTokens: targetTokens,
156
- provider: provider,
157
- adaptiveBuffer: 0.15,
158
- maxIterations: 3,
159
- });
160
- if (truncResult.truncated) {
161
- currentMessages = truncResult.messages;
162
- stagesUsed.push("truncate");
143
+ // Stage 4: Sliding Window Truncation (fallback)
144
+ if (this.config.enableTruncate &&
145
+ estimateMessagesTokens(currentMessages, provider) > targetTokens) {
146
+ const stageTokensBefore = estimateMessagesTokens(currentMessages, provider);
147
+ const truncResult = truncateWithSlidingWindow(currentMessages, {
148
+ fraction: this.config.truncationFraction,
149
+ });
150
+ if (truncResult.truncated) {
151
+ currentMessages = truncResult.messages;
152
+ stagesUsed.push("truncate");
153
+ }
154
+ const stageTokensAfter = estimateMessagesTokens(currentMessages, provider);
155
+ logger.info("[Compaction] Stage 4 (truncate)", {
156
+ requestId,
157
+ ran: truncResult.truncated,
158
+ tokensBefore: stageTokensBefore,
159
+ tokensAfter: stageTokensAfter,
160
+ saved: stageTokensBefore - stageTokensAfter,
161
+ });
163
162
  }
164
- const stageTokensAfter = estimateMessagesTokens(currentMessages, provider);
165
- logger.info("[Compaction] Stage 4 (truncate)", {
163
+ const tokensAfter = estimateMessagesTokens(currentMessages, provider);
164
+ logger.info("[Compaction] Complete", {
166
165
  requestId,
167
- ran: truncResult.truncated,
168
- tokensBefore: stageTokensBefore,
169
- tokensAfter: stageTokensAfter,
170
- saved: stageTokensBefore - stageTokensAfter,
166
+ tokensBefore,
167
+ tokensAfter,
168
+ totalSaved: tokensBefore - tokensAfter,
169
+ stagesUsed,
170
+ durationMs: Date.now() - spanStartTime,
171
171
  });
172
+ const result = {
173
+ compacted: stagesUsed.length > 0,
174
+ stagesUsed,
175
+ tokensBefore,
176
+ tokensAfter,
177
+ tokensSaved: tokensBefore - tokensAfter,
178
+ messages: currentMessages,
179
+ };
180
+ span.durationMs = Date.now() - spanStartTime;
181
+ const endedSpan = SpanSerializer.endSpan(SpanSerializer.updateAttributes(span, {
182
+ "context.stage": stagesUsed.join(",") || "none",
183
+ "context.tokensBefore": tokensBefore,
184
+ "context.tokensAfter": tokensAfter,
185
+ "context.tokensSaved": tokensBefore - tokensAfter,
186
+ }), SpanStatus.OK);
187
+ getMetricsAggregator().recordSpan(endedSpan);
188
+ return result;
189
+ }
190
+ catch (error) {
191
+ span.durationMs = Date.now() - spanStartTime;
192
+ const endedSpan = SpanSerializer.endSpan(span, SpanStatus.ERROR);
193
+ endedSpan.statusMessage =
194
+ error instanceof Error ? error.message : String(error);
195
+ getMetricsAggregator().recordSpan(endedSpan);
196
+ throw error;
172
197
  }
173
- const tokensAfter = estimateMessagesTokens(currentMessages, provider);
174
- logger.info("[Compaction] Complete", {
175
- requestId,
176
- tokensBefore,
177
- tokensAfter,
178
- totalSaved: tokensBefore - tokensAfter,
179
- stagesUsed,
180
- durationMs: Date.now() - compactionStartTime,
181
- });
182
- return {
183
- compacted: stagesUsed.length > 0,
184
- stagesUsed,
185
- tokensBefore,
186
- tokensAfter,
187
- tokensSaved: tokensBefore - tokensAfter,
188
- messages: currentMessages,
189
- };
190
198
  }
191
199
  }
@@ -24,6 +24,11 @@ export declare abstract class BaseProvider implements AIProvider {
24
24
  protected sessionId?: string;
25
25
  protected userId?: string;
26
26
  protected neurolink?: NeuroLink;
27
+ /** Trace context propagated from NeuroLink SDK for span hierarchy */
28
+ _traceContext: {
29
+ traceId: string;
30
+ parentSpanId: string;
31
+ } | null;
27
32
  private readonly messageBuilder;
28
33
  private readonly streamHandler;
29
34
  private readonly generationHandler;
@@ -1,17 +1,16 @@
1
+ import { context, SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
1
2
  import { generateText } from "ai";
2
- import { SpanKind, SpanStatusCode } from "@opentelemetry/api";
3
- import { tracers } from "../telemetry/tracers.js";
4
3
  import { directAgentTools } from "../agent/directTools.js";
5
4
  import { IMAGE_GENERATION_MODELS } from "../core/constants.js";
6
5
  import { MiddlewareFactory } from "../middleware/factory.js";
6
+ import { ATTR, tracers } from "../telemetry/index.js";
7
7
  import { isAbortError } from "../utils/errorHandling.js";
8
8
  import { logger } from "../utils/logger.js";
9
- import { calculateCost } from "../utils/pricing.js";
10
9
  import { composeAbortSignals, createTimeoutController, TimeoutError, } from "../utils/timeout.js";
11
10
  import { shouldDisableBuiltinTools } from "../utils/toolUtils.js";
12
11
  import { getKeyCount, getKeysAsString } from "../utils/transformationUtils.js";
13
12
  import { TTSProcessor } from "../utils/ttsProcessor.js";
14
- import { hasVideoFrames, executeVideoAnalysis, } from "../utils/videoAnalysisProcessor.js";
13
+ import { executeVideoAnalysis, hasVideoFrames, } from "../utils/videoAnalysisProcessor.js";
15
14
  import { GenerationHandler } from "./modules/GenerationHandler.js";
16
15
  // Import modules for composition
17
16
  import { MessageBuilder } from "./modules/MessageBuilder.js";
@@ -19,7 +18,6 @@ import { StreamHandler } from "./modules/StreamHandler.js";
19
18
  import { TelemetryHandler } from "./modules/TelemetryHandler.js";
20
19
  import { ToolsManager } from "./modules/ToolsManager.js";
21
20
  import { Utilities } from "./modules/Utilities.js";
22
- const providerTracer = tracers.provider;
23
21
  /**
24
22
  * Abstract base class for all AI providers
25
23
  * Tools are integrated as first-class citizens - always available by default
@@ -39,6 +37,8 @@ export class BaseProvider {
39
37
  sessionId;
40
38
  userId;
41
39
  neurolink; // Reference to actual NeuroLink instance for MCP tools
40
+ /** Trace context propagated from NeuroLink SDK for span hierarchy */
41
+ _traceContext = null;
42
42
  // Composition modules - Single Responsibility Principle
43
43
  messageBuilder;
44
44
  streamHandler;
@@ -80,10 +80,18 @@ export class BaseProvider {
80
80
  * When tools are involved, falls back to generate() with synthetic streaming
81
81
  */
82
82
  async stream(optionsOrPrompt, analysisSchema) {
83
- return providerTracer.startActiveSpan("neurolink.provider.stream", { kind: SpanKind.INTERNAL }, async (span) => {
84
- let options = this.normalizeStreamOptions(optionsOrPrompt);
85
- span.setAttribute("gen_ai.system", this.providerName || "unknown");
86
- span.setAttribute("gen_ai.request.model", this.modelName || options.model || "unknown");
83
+ let options = this.normalizeStreamOptions(optionsOrPrompt);
84
+ // OTEL span for provider-level stream tracing
85
+ const otelStreamSpan = tracers.provider.startSpan("neurolink.provider.stream", {
86
+ kind: SpanKind.CLIENT,
87
+ attributes: {
88
+ [ATTR.GEN_AI_SYSTEM]: this.providerName || "unknown",
89
+ [ATTR.GEN_AI_MODEL]: this.modelName || options.model || "unknown",
90
+ [ATTR.GEN_AI_OPERATION]: "stream",
91
+ [ATTR.NL_PROVIDER]: this.providerName || "unknown",
92
+ },
93
+ });
94
+ try {
87
95
  logger.info(`Starting stream`, {
88
96
  provider: this.providerName,
89
97
  hasTools: !options.disableTools && this.supportsTools(),
@@ -94,93 +102,91 @@ export class BaseProvider {
94
102
  temperature: options.temperature,
95
103
  timestamp: Date.now(),
96
104
  });
97
- try {
98
- // ===== EARLY MULTIMODAL DETECTION =====
99
- const hasFileInput = !!options.input?.files?.length ||
100
- !!options.input?.videoFiles?.length;
101
- if (hasFileInput) {
102
- // ===== VIDEO ANALYSIS DETECTION =====
103
- // Check if video frames are present and handle with fake streaming
104
- const messages = await this.buildMessagesForStream(options);
105
- if (hasVideoFrames(messages)) {
106
- logger.info(`Video frames detected in stream, using fake streaming for video analysis`, {
107
- provider: this.providerName,
108
- model: this.modelName,
109
- });
110
- span.setAttribute("neurolink.stream_mode", "fake");
111
- return await this.executeFakeStreaming(options, analysisSchema);
112
- }
113
- }
114
- // Image generation models don't support real streaming
115
- // Force fake streaming for image models to ensure image output is yielded
116
- const isImageModel = IMAGE_GENERATION_MODELS.some((m) => this.modelName.includes(m));
117
- if (isImageModel) {
118
- logger.info(`Image model detected, forcing fake streaming`, {
105
+ // ===== EARLY MULTIMODAL DETECTION =====
106
+ const hasFileInput = !!options.input?.files?.length || !!options.input?.videoFiles?.length;
107
+ if (hasFileInput) {
108
+ // ===== VIDEO ANALYSIS DETECTION =====
109
+ // Check if video frames are present and handle with fake streaming
110
+ const messages = await this.buildMessagesForStream(options);
111
+ if (hasVideoFrames(messages)) {
112
+ logger.info(`Video frames detected in stream, using fake streaming for video analysis`, {
119
113
  provider: this.providerName,
120
114
  model: this.modelName,
121
- reason: "Image generation requires fake streaming to yield image output",
122
115
  });
123
- // Skip real streaming, go directly to fake streaming
124
- span.setAttribute("neurolink.stream_mode", "fake");
125
116
  return await this.executeFakeStreaming(options, analysisSchema);
126
117
  }
127
- // Central tool merge: Pre-merge base tools (MCP/built-in) with user-provided
128
- // tools (e.g. RAG tools) into options.tools. This way, every provider's
129
- // executeStream() can simply use options.tools (or getAllTools() + options.tools)
130
- // and get the complete tool set without needing per-provider merge logic.
118
+ }
119
+ // CRITICAL: Image generation models don't support real streaming
120
+ // Force fake streaming for image models to ensure image output is yielded
121
+ const isImageModel = IMAGE_GENERATION_MODELS.some((m) => this.modelName.includes(m));
122
+ if (isImageModel) {
123
+ logger.info(`Image model detected, forcing fake streaming`, {
124
+ provider: this.providerName,
125
+ model: this.modelName,
126
+ reason: "Image generation requires fake streaming to yield image output",
127
+ });
128
+ // Skip real streaming, go directly to fake streaming
129
+ return await this.executeFakeStreaming(options, analysisSchema);
130
+ }
131
+ // Central tool merge: Pre-merge base tools (MCP/built-in) with user-provided
132
+ // tools (e.g. RAG tools) into options.tools. This way, every provider's
133
+ // executeStream() can simply use options.tools (or getAllTools() + options.tools)
134
+ // and get the complete tool set without needing per-provider merge logic.
135
+ if (!options.disableTools && this.supportsTools()) {
136
+ const mergedTools = await this.getToolsForStream(options);
137
+ options = { ...options, tools: mergedTools };
138
+ }
139
+ else {
140
+ options = { ...options, tools: {} };
141
+ }
142
+ // CRITICAL FIX: Always prefer real streaming over fake streaming
143
+ // Try real streaming first, use fake streaming only as fallback
144
+ try {
145
+ logger.debug(`Attempting real streaming`, {
146
+ provider: this.providerName,
147
+ timestamp: Date.now(),
148
+ });
149
+ const realStreamResult = await this.executeStream(options, analysisSchema);
150
+ logger.info(`Real streaming succeeded`, {
151
+ provider: this.providerName,
152
+ timestamp: Date.now(),
153
+ });
154
+ // If real streaming succeeds, return it (with tools support via Vercel AI SDK)
155
+ return realStreamResult;
156
+ }
157
+ catch (realStreamError) {
158
+ logger.warn(`Real streaming failed for ${this.providerName}, falling back to fake streaming:`, {
159
+ error: realStreamError instanceof Error
160
+ ? realStreamError.message
161
+ : String(realStreamError),
162
+ timestamp: Date.now(),
163
+ });
164
+ // Fallback to fake streaming only if real streaming fails AND tools are enabled
131
165
  if (!options.disableTools && this.supportsTools()) {
132
- const mergedTools = await this.getToolsForStream(options);
133
- options = { ...options, tools: mergedTools };
166
+ return await this.executeFakeStreaming(options, analysisSchema);
134
167
  }
135
168
  else {
136
- options = { ...options, tools: {} };
137
- }
138
- // CRITICAL FIX: Always prefer real streaming over fake streaming
139
- // Try real streaming first, use fake streaming only as fallback
140
- try {
141
- logger.debug(`Attempting real streaming`, {
142
- provider: this.providerName,
143
- timestamp: Date.now(),
144
- });
145
- const realStreamResult = await this.executeStream(options, analysisSchema);
146
- logger.info(`Real streaming succeeded`, {
147
- provider: this.providerName,
148
- timestamp: Date.now(),
149
- });
150
- span.setAttribute("neurolink.stream_mode", "real");
151
- // If real streaming succeeds, return it (with tools support via Vercel AI SDK)
152
- return realStreamResult;
153
- }
154
- catch (realStreamError) {
155
- logger.warn(`Real streaming failed for ${this.providerName}, falling back to fake streaming:`, {
156
- error: realStreamError instanceof Error
157
- ? realStreamError.message
158
- : String(realStreamError),
159
- timestamp: Date.now(),
160
- });
161
- // Fallback to fake streaming only if real streaming fails AND tools are enabled
162
- if (!options.disableTools && this.supportsTools()) {
163
- span.setAttribute("neurolink.stream_mode", "fake");
164
- return await this.executeFakeStreaming(options, analysisSchema);
165
- }
166
- else {
167
- // If real streaming failed and no tools are enabled, re-throw the original error
168
- logger.error(`Real streaming failed for ${this.providerName}:`, realStreamError);
169
- throw this.handleProviderError(realStreamError);
170
- }
169
+ // If real streaming failed and no tools are enabled, re-throw the original error
170
+ logger.error(`Real streaming failed for ${this.providerName}:`, realStreamError);
171
+ throw this.handleProviderError(realStreamError);
171
172
  }
172
173
  }
173
- catch (error) {
174
- span.setStatus({
175
- code: SpanStatusCode.ERROR,
176
- message: error instanceof Error ? error.message : String(error),
177
- });
178
- throw error;
179
- }
180
- finally {
181
- span.end();
174
+ }
175
+ catch (error) {
176
+ otelStreamSpan.setStatus({
177
+ code: SpanStatusCode.ERROR,
178
+ message: error instanceof Error ? error.message : String(error),
179
+ });
180
+ otelStreamSpan.end();
181
+ throw error;
182
+ }
183
+ finally {
184
+ // End OTEL span on success (only if not already ended via error path)
185
+ if (otelStreamSpan.isRecording()) {
186
+ otelStreamSpan.setStatus({ code: SpanStatusCode.OK });
187
+ otelStreamSpan.end();
182
188
  }
183
- });
189
+ }
184
190
  }
185
191
  /**
186
192
  * Execute fake streaming - extracted method for reusability
@@ -468,12 +474,24 @@ export class BaseProvider {
468
474
  * for consistency and better performance
469
475
  */
470
476
  async generate(optionsOrPrompt, _analysisSchema) {
471
- return providerTracer.startActiveSpan("neurolink.provider.generate", { kind: SpanKind.INTERNAL }, async (span) => {
472
- const options = this.normalizeTextOptions(optionsOrPrompt);
473
- this.validateOptions(options);
474
- const startTime = Date.now();
475
- span.setAttribute("gen_ai.system", this.providerName || "unknown");
476
- span.setAttribute("gen_ai.request.model", this.modelName || options.model || "unknown");
477
+ const options = this.normalizeTextOptions(optionsOrPrompt);
478
+ this.validateOptions(options);
479
+ const startTime = Date.now();
480
+ // OTEL span for provider-level generate tracing
481
+ // Use startActiveSpan pattern via context.with() so child spans become descendants
482
+ const otelSpan = tracers.provider.startSpan("neurolink.provider.generate", {
483
+ kind: SpanKind.CLIENT,
484
+ attributes: {
485
+ [ATTR.GEN_AI_SYSTEM]: this.providerName || "unknown",
486
+ [ATTR.GEN_AI_MODEL]: this.modelName || options.model || "unknown",
487
+ [ATTR.GEN_AI_OPERATION]: "generate",
488
+ [ATTR.NL_PROVIDER]: this.providerName || "unknown",
489
+ },
490
+ });
491
+ // Set this span as the active context so child spans (GenerationHandler, etc.) become descendants
492
+ const activeCtx = trace.setSpan(context.active(), otelSpan);
493
+ let otelSpanEnded = false;
494
+ return await context.with(activeCtx, async () => {
477
495
  try {
478
496
  // ===== VIDEO GENERATION MODE =====
479
497
  // Generate video from image + prompt using Veo 3.1
@@ -647,29 +665,15 @@ export class BaseProvider {
647
665
  });
648
666
  }
649
667
  }
650
- // Set token usage on span from the result
651
- if (enhancedResult?.usage) {
652
- span.setAttribute("gen_ai.usage.input_tokens", enhancedResult.usage.input || 0);
653
- span.setAttribute("gen_ai.usage.output_tokens", enhancedResult.usage.output || 0);
654
- // Cost on span so users can query "what did this trace cost?"
655
- const cost = calculateCost(this.providerName, this.modelName, {
656
- input: enhancedResult.usage.input || 0,
657
- output: enhancedResult.usage.output || 0,
658
- total: enhancedResult.usage.total || 0,
659
- });
660
- span.setAttribute("neurolink.cost", cost ?? 0);
661
- }
662
- if (enhancedResult?.finishReason) {
663
- span.setAttribute("gen_ai.response.finish_reason", enhancedResult.finishReason);
664
- }
665
- span.setStatus({ code: SpanStatusCode.OK });
666
668
  return await this.enhanceResult(enhancedResult, options, startTime);
667
669
  }
668
670
  catch (error) {
669
- span.setStatus({
671
+ otelSpan.setStatus({
670
672
  code: SpanStatusCode.ERROR,
671
673
  message: error instanceof Error ? error.message : String(error),
672
674
  });
675
+ otelSpan.end();
676
+ otelSpanEnded = true;
673
677
  // Abort errors are expected when a generation is cancelled — log at info, not error
674
678
  if (isAbortError(error)) {
675
679
  logger.info(`Generate aborted for ${this.providerName}`, {
@@ -682,9 +686,12 @@ export class BaseProvider {
682
686
  throw this.handleProviderError(error);
683
687
  }
684
688
  finally {
685
- span.end();
689
+ if (!otelSpanEnded) {
690
+ otelSpan.setStatus({ code: SpanStatusCode.OK });
691
+ otelSpan.end();
692
+ }
686
693
  }
687
- });
694
+ }); // end context.with
688
695
  }
689
696
  /**
690
697
  * Alias for generate method - implements AIProvider interface