@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
@@ -0,0 +1,556 @@
1
+ /**
2
+ * Metrics Aggregator
3
+ * Comprehensive metrics aggregation with latency percentiles, token usage, and cost tracking
4
+ */
5
+ import { TokenTracker } from "./tokenTracker.js";
6
+ /**
7
+ * Metrics Aggregator for comprehensive telemetry analysis
8
+ * Provides latency percentiles, token aggregation, and cost tracking
9
+ */
10
+ export class MetricsAggregator {
11
+ spans = [];
12
+ latencyValues = [];
13
+ tokenTracker;
14
+ timeWindows = new Map();
15
+ config;
16
+ spansByType = new Map();
17
+ costByProvider = new Map();
18
+ costByModel = new Map();
19
+ successCount = 0;
20
+ failureCount = 0;
21
+ firstSpanTime;
22
+ lastSpanTime;
23
+ constructor(config = {}) {
24
+ this.config = {
25
+ maxSpansRetained: config.maxSpansRetained ?? 10000,
26
+ enableTimeWindows: config.enableTimeWindows ?? true,
27
+ timeWindowMs: config.timeWindowMs ?? 60000, // 1 minute default
28
+ maxTimeWindows: config.maxTimeWindows ?? 60, // 1 hour of 1-minute windows
29
+ };
30
+ this.tokenTracker = new TokenTracker();
31
+ }
32
+ /**
33
+ * Record a span for metrics aggregation
34
+ */
35
+ recordSpan(span) {
36
+ // Enforce maximum spans limit
37
+ if (this.spans.length >= this.config.maxSpansRetained) {
38
+ const evicted = this.spans.shift(); // Remove oldest span
39
+ // Only trim latencyValues when the evicted span had a duration recorded
40
+ if (evicted?.durationMs !== undefined) {
41
+ this.latencyValues.shift();
42
+ }
43
+ // Note: We keep aggregated metrics, only raw spans and latency values are trimmed
44
+ }
45
+ this.spans.push(span);
46
+ // Update timestamps
47
+ const spanTime = new Date(span.startTime);
48
+ if (!this.firstSpanTime || spanTime < this.firstSpanTime) {
49
+ this.firstSpanTime = spanTime;
50
+ }
51
+ if (!this.lastSpanTime || spanTime > this.lastSpanTime) {
52
+ this.lastSpanTime = spanTime;
53
+ }
54
+ // Track latency if duration is available
55
+ if (span.durationMs !== undefined) {
56
+ this.latencyValues.push(span.durationMs);
57
+ }
58
+ // Track success/failure
59
+ if (span.status === 2) {
60
+ // SpanStatus.ERROR = 2
61
+ this.failureCount++;
62
+ }
63
+ else {
64
+ this.successCount++;
65
+ }
66
+ // Track by span type
67
+ const currentCount = this.spansByType.get(span.type) ?? 0;
68
+ this.spansByType.set(span.type, currentCount + 1);
69
+ // Track tokens via TokenTracker
70
+ this.tokenTracker.trackSpan(span);
71
+ // Track cost by provider and model
72
+ this.trackCosts(span);
73
+ // Update time window if enabled
74
+ if (this.config.enableTimeWindows) {
75
+ this.updateTimeWindow(span);
76
+ }
77
+ }
78
+ /**
79
+ * Track cost aggregations from a span
80
+ */
81
+ trackCosts(span) {
82
+ const attrs = span.attributes;
83
+ const provider = attrs["ai.provider"];
84
+ const model = attrs["ai.model"];
85
+ const inputCost = attrs["ai.cost.input"] ?? 0;
86
+ const outputCost = attrs["ai.cost.output"] ?? 0;
87
+ const totalCost = attrs["ai.cost.total"] ?? inputCost + outputCost;
88
+ const inputTokens = attrs["ai.tokens.input"] ?? 0;
89
+ const outputTokens = attrs["ai.tokens.output"] ?? 0;
90
+ // Update provider costs
91
+ if (provider) {
92
+ const existing = this.costByProvider.get(provider) ?? {
93
+ provider,
94
+ totalCost: 0,
95
+ requestCount: 0,
96
+ avgCostPerRequest: 0,
97
+ inputCost: 0,
98
+ outputCost: 0,
99
+ };
100
+ existing.totalCost += totalCost;
101
+ existing.requestCount += 1;
102
+ existing.avgCostPerRequest = existing.totalCost / existing.requestCount;
103
+ existing.inputCost += inputCost;
104
+ existing.outputCost += outputCost;
105
+ this.costByProvider.set(provider, existing);
106
+ }
107
+ // Update model costs
108
+ if (model) {
109
+ const existing = this.costByModel.get(model) ?? {
110
+ model,
111
+ provider: provider ?? "unknown",
112
+ totalCost: 0,
113
+ requestCount: 0,
114
+ avgCostPerRequest: 0,
115
+ inputTokens: 0,
116
+ outputTokens: 0,
117
+ inputCost: 0,
118
+ outputCost: 0,
119
+ };
120
+ existing.totalCost += totalCost;
121
+ existing.requestCount += 1;
122
+ existing.avgCostPerRequest = existing.totalCost / existing.requestCount;
123
+ existing.inputTokens += inputTokens;
124
+ existing.outputTokens += outputTokens;
125
+ existing.inputCost += inputCost;
126
+ existing.outputCost += outputCost;
127
+ this.costByModel.set(model, existing);
128
+ }
129
+ }
130
+ /**
131
+ * Update time window statistics
132
+ */
133
+ updateTimeWindow(span) {
134
+ const spanTime = new Date(span.startTime).getTime();
135
+ const windowKey = Math.floor(spanTime / this.config.timeWindowMs) *
136
+ this.config.timeWindowMs;
137
+ // Enforce maximum time windows
138
+ if (this.timeWindows.size >= this.config.maxTimeWindows &&
139
+ !this.timeWindows.has(windowKey)) {
140
+ // Remove oldest window
141
+ const oldestKey = Math.min(...Array.from(this.timeWindows.keys()));
142
+ this.timeWindows.delete(oldestKey);
143
+ }
144
+ let window = this.timeWindows.get(windowKey);
145
+ if (!window) {
146
+ window = this.createEmptyTimeWindow(windowKey);
147
+ this.timeWindows.set(windowKey, window);
148
+ }
149
+ // Update window stats
150
+ window.requestCount++;
151
+ if (span.status === 2) {
152
+ window.errorCount++;
153
+ }
154
+ window.successRate =
155
+ (window.requestCount - window.errorCount) / window.requestCount;
156
+ window.throughput = window.requestCount / (window.windowDurationMs / 1000);
157
+ // Update window end time
158
+ const spanEndTime = new Date(span.endTime ?? span.startTime);
159
+ if (spanEndTime > window.windowEnd) {
160
+ window.windowEnd = spanEndTime;
161
+ }
162
+ }
163
+ /**
164
+ * Create an empty time window
165
+ */
166
+ createEmptyTimeWindow(windowKey) {
167
+ return {
168
+ windowStart: new Date(windowKey),
169
+ windowEnd: new Date(windowKey + this.config.timeWindowMs),
170
+ windowDurationMs: this.config.timeWindowMs,
171
+ requestCount: 0,
172
+ errorCount: 0,
173
+ successRate: 1,
174
+ throughput: 0,
175
+ latency: this.createEmptyLatencyStats(),
176
+ tokens: {
177
+ totalInputTokens: 0,
178
+ totalOutputTokens: 0,
179
+ totalTokens: 0,
180
+ cacheReadTokens: 0,
181
+ cacheCreationTokens: 0,
182
+ reasoningTokens: 0,
183
+ totalCost: 0,
184
+ byProvider: new Map(),
185
+ byModel: new Map(),
186
+ bySpanType: new Map(),
187
+ },
188
+ costByProvider: new Map(),
189
+ costByModel: new Map(),
190
+ };
191
+ }
192
+ /**
193
+ * Create empty latency stats
194
+ */
195
+ createEmptyLatencyStats() {
196
+ return {
197
+ min: 0,
198
+ max: 0,
199
+ mean: 0,
200
+ median: 0,
201
+ p50: 0,
202
+ p75: 0,
203
+ p90: 0,
204
+ p95: 0,
205
+ p99: 0,
206
+ stdDev: 0,
207
+ count: 0,
208
+ };
209
+ }
210
+ /**
211
+ * Calculate latency percentile from sorted array
212
+ */
213
+ calculatePercentile(sortedValues, percentile) {
214
+ if (sortedValues.length === 0) {
215
+ return 0;
216
+ }
217
+ const index = Math.ceil((percentile / 100) * sortedValues.length) - 1;
218
+ return sortedValues[Math.max(0, index)] ?? 0;
219
+ }
220
+ /**
221
+ * Calculate standard deviation
222
+ */
223
+ calculateStdDev(values, mean) {
224
+ if (values.length < 2) {
225
+ return 0;
226
+ }
227
+ const squaredDiffs = values.map((v) => (v - mean) ** 2);
228
+ const avgSquaredDiff = squaredDiffs.reduce((a, b) => a + b, 0) / values.length;
229
+ return Math.sqrt(avgSquaredDiff);
230
+ }
231
+ /**
232
+ * Get comprehensive latency statistics
233
+ */
234
+ getLatencyStats() {
235
+ if (this.latencyValues.length === 0) {
236
+ return this.createEmptyLatencyStats();
237
+ }
238
+ const sorted = [...this.latencyValues].sort((a, b) => a - b);
239
+ const sum = sorted.reduce((a, b) => a + b, 0);
240
+ const mean = sum / sorted.length;
241
+ return {
242
+ min: sorted[0] ?? 0,
243
+ max: sorted[sorted.length - 1] ?? 0,
244
+ mean,
245
+ median: this.calculatePercentile(sorted, 50),
246
+ p50: this.calculatePercentile(sorted, 50),
247
+ p75: this.calculatePercentile(sorted, 75),
248
+ p90: this.calculatePercentile(sorted, 90),
249
+ p95: this.calculatePercentile(sorted, 95),
250
+ p99: this.calculatePercentile(sorted, 99),
251
+ stdDev: this.calculateStdDev(sorted, mean),
252
+ count: sorted.length,
253
+ };
254
+ }
255
+ /**
256
+ * Get token usage statistics
257
+ */
258
+ getTokenStats() {
259
+ return this.tokenTracker.getStats();
260
+ }
261
+ /**
262
+ * Get cost breakdown by provider
263
+ */
264
+ getCostByProvider() {
265
+ return Array.from(this.costByProvider.values());
266
+ }
267
+ /**
268
+ * Get cost breakdown by model
269
+ */
270
+ getCostByModel() {
271
+ return Array.from(this.costByModel.values());
272
+ }
273
+ /**
274
+ * Get total cost across all providers
275
+ */
276
+ getTotalCost() {
277
+ let total = 0;
278
+ const providerStatsArray = Array.from(this.costByProvider.values());
279
+ for (const stats of providerStatsArray) {
280
+ total += stats.totalCost;
281
+ }
282
+ return total;
283
+ }
284
+ /**
285
+ * Get time window statistics
286
+ */
287
+ getTimeWindows() {
288
+ return Array.from(this.timeWindows.values()).sort((a, b) => a.windowStart.getTime() - b.windowStart.getTime());
289
+ }
290
+ /**
291
+ * Get statistics for a specific time range
292
+ */
293
+ getStatsForTimeRange(startTime, endTime) {
294
+ const relevantSpans = this.spans.filter((span) => {
295
+ const spanTime = new Date(span.startTime);
296
+ return spanTime >= startTime && spanTime <= endTime;
297
+ });
298
+ // Create a temporary aggregator for this time range
299
+ const tempAggregator = new MetricsAggregator({
300
+ enableTimeWindows: false,
301
+ });
302
+ for (const span of relevantSpans) {
303
+ tempAggregator.recordSpan(span);
304
+ }
305
+ const summary = tempAggregator.getSummary();
306
+ const durationMs = endTime.getTime() - startTime.getTime();
307
+ return {
308
+ windowStart: startTime,
309
+ windowEnd: endTime,
310
+ windowDurationMs: durationMs,
311
+ requestCount: summary.totalSpans,
312
+ errorCount: summary.failedSpans,
313
+ successRate: summary.successRate,
314
+ throughput: summary.totalSpans / (durationMs / 1000),
315
+ latency: summary.latency,
316
+ tokens: summary.tokens,
317
+ costByProvider: new Map(summary.costByProvider.map((p) => [p.provider, p])),
318
+ costByModel: new Map(summary.costByModel.map((m) => [m.model, m])),
319
+ };
320
+ }
321
+ /**
322
+ * Record a latency measurement for an operation
323
+ * Use this for standalone latency tracking without a full span
324
+ */
325
+ recordLatency(operation, latencyMs) {
326
+ this.latencyValues.push(latencyMs);
327
+ // Track by operation type (similar to span type)
328
+ const currentCount = this.spansByType.get(operation) ?? 0;
329
+ this.spansByType.set(operation, currentCount + 1);
330
+ // Update timestamps
331
+ const now = new Date();
332
+ if (!this.firstSpanTime) {
333
+ this.firstSpanTime = now;
334
+ }
335
+ this.lastSpanTime = now;
336
+ // Increment success count (standalone latency records are assumed successful)
337
+ this.successCount++;
338
+ }
339
+ /**
340
+ * Get comprehensive metrics summary (alias for getSummary)
341
+ */
342
+ getMetrics() {
343
+ return this.getSummary();
344
+ }
345
+ /**
346
+ * Get comprehensive metrics summary
347
+ */
348
+ getSummary() {
349
+ const totalSpans = this.successCount + this.failureCount;
350
+ return {
351
+ totalSpans,
352
+ successfulSpans: this.successCount,
353
+ failedSpans: this.failureCount,
354
+ successRate: totalSpans > 0 ? this.successCount / totalSpans : 1,
355
+ latency: this.getLatencyStats(),
356
+ tokens: this.getTokenStats(),
357
+ costByProvider: this.getCostByProvider(),
358
+ costByModel: this.getCostByModel(),
359
+ totalCost: this.getTotalCost(),
360
+ spansByType: Object.fromEntries(this.spansByType),
361
+ firstSpanTime: this.firstSpanTime,
362
+ lastSpanTime: this.lastSpanTime,
363
+ trackingDurationMs: this.firstSpanTime && this.lastSpanTime
364
+ ? this.lastSpanTime.getTime() - this.firstSpanTime.getTime()
365
+ : undefined,
366
+ };
367
+ }
368
+ /**
369
+ * Get all recorded spans (returns a copy)
370
+ */
371
+ getSpans() {
372
+ return [...this.spans];
373
+ }
374
+ /**
375
+ * Get spans grouped by traceId as hierarchical trace views
376
+ */
377
+ getTraces() {
378
+ const traceMap = new Map();
379
+ for (const span of this.spans) {
380
+ const existing = traceMap.get(span.traceId) || [];
381
+ existing.push(span);
382
+ traceMap.set(span.traceId, existing);
383
+ }
384
+ const traces = [];
385
+ for (const [traceId, spans] of traceMap) {
386
+ // Root span is the one with no parentSpanId, or first span if all have parents
387
+ const rootSpan = spans.find((s) => !s.parentSpanId) || spans[0];
388
+ const childSpans = spans.filter((s) => s !== rootSpan);
389
+ // Calculate total duration
390
+ let totalDurationMs = 0;
391
+ for (const s of spans) {
392
+ if (s.durationMs) {
393
+ totalDurationMs = Math.max(totalDurationMs, s.durationMs);
394
+ }
395
+ }
396
+ // Determine status
397
+ const hasError = spans.some((s) => s.status === 2); // SpanStatus.ERROR
398
+ const allOk = spans.every((s) => s.status === 1); // SpanStatus.OK
399
+ const status = hasError
400
+ ? "error"
401
+ : allOk
402
+ ? "ok"
403
+ : "partial";
404
+ traces.push({
405
+ traceId,
406
+ rootSpan,
407
+ childSpans,
408
+ totalDurationMs,
409
+ spanCount: spans.length,
410
+ status,
411
+ });
412
+ }
413
+ return traces;
414
+ }
415
+ /**
416
+ * Get the underlying token tracker for custom pricing configuration
417
+ */
418
+ getTokenTracker() {
419
+ return this.tokenTracker;
420
+ }
421
+ /**
422
+ * Reset all metrics
423
+ */
424
+ reset() {
425
+ this.spans = [];
426
+ this.latencyValues = [];
427
+ this.tokenTracker.reset();
428
+ this.timeWindows.clear();
429
+ this.spansByType.clear();
430
+ this.costByProvider.clear();
431
+ this.costByModel.clear();
432
+ this.successCount = 0;
433
+ this.failureCount = 0;
434
+ this.firstSpanTime = undefined;
435
+ this.lastSpanTime = undefined;
436
+ }
437
+ /**
438
+ * Export metrics as JSON
439
+ */
440
+ toJSON() {
441
+ const summary = this.getSummary();
442
+ return {
443
+ totalSpans: summary.totalSpans,
444
+ successfulSpans: summary.successfulSpans,
445
+ failedSpans: summary.failedSpans,
446
+ successRate: summary.successRate,
447
+ latency: summary.latency,
448
+ tokens: this.tokenTracker.toJSON(),
449
+ costByProvider: summary.costByProvider,
450
+ costByModel: summary.costByModel,
451
+ totalCost: summary.totalCost,
452
+ spansByType: summary.spansByType,
453
+ firstSpanTime: summary.firstSpanTime?.toISOString(),
454
+ lastSpanTime: summary.lastSpanTime?.toISOString(),
455
+ trackingDurationMs: summary.trackingDurationMs,
456
+ timeWindows: this.config.enableTimeWindows
457
+ ? this.getTimeWindows().map((w) => ({
458
+ windowStart: w.windowStart.toISOString(),
459
+ windowEnd: w.windowEnd.toISOString(),
460
+ requestCount: w.requestCount,
461
+ errorCount: w.errorCount,
462
+ successRate: w.successRate,
463
+ throughput: w.throughput,
464
+ }))
465
+ : undefined,
466
+ };
467
+ }
468
+ /**
469
+ * Format cost as currency string
470
+ */
471
+ formatCost(cost, currency = "USD") {
472
+ return new Intl.NumberFormat("en-US", {
473
+ style: "currency",
474
+ currency,
475
+ minimumFractionDigits: 4,
476
+ }).format(cost);
477
+ }
478
+ /**
479
+ * Get a formatted summary string
480
+ */
481
+ getFormattedSummary() {
482
+ const summary = this.getSummary();
483
+ const latency = summary.latency;
484
+ const lines = [
485
+ "=== Metrics Summary ===",
486
+ "",
487
+ "Request Statistics:",
488
+ ` Total requests: ${summary.totalSpans.toLocaleString()}`,
489
+ ` Successful: ${summary.successfulSpans.toLocaleString()}`,
490
+ ` Failed: ${summary.failedSpans.toLocaleString()}`,
491
+ ` Success rate: ${(summary.successRate * 100).toFixed(2)}%`,
492
+ "",
493
+ "Latency (ms):",
494
+ ` Min: ${latency.min.toFixed(2)}`,
495
+ ` Max: ${latency.max.toFixed(2)}`,
496
+ ` Mean: ${latency.mean.toFixed(2)}`,
497
+ ` P50: ${latency.p50.toFixed(2)}`,
498
+ ` P95: ${latency.p95.toFixed(2)}`,
499
+ ` P99: ${latency.p99.toFixed(2)}`,
500
+ "",
501
+ "Token Usage:",
502
+ ` Input tokens: ${summary.tokens.totalInputTokens.toLocaleString()}`,
503
+ ` Output tokens: ${summary.tokens.totalOutputTokens.toLocaleString()}`,
504
+ ` Total tokens: ${summary.tokens.totalTokens.toLocaleString()}`,
505
+ "",
506
+ "Cost:",
507
+ ` Total: ${this.formatCost(summary.totalCost)}`,
508
+ ];
509
+ // Add cost by provider
510
+ if (summary.costByProvider.length > 0) {
511
+ lines.push("");
512
+ lines.push("Cost by Provider:");
513
+ for (const providerCost of summary.costByProvider) {
514
+ lines.push(` ${providerCost.provider}: ${this.formatCost(providerCost.totalCost)} (${providerCost.requestCount} requests)`);
515
+ }
516
+ }
517
+ // Add cost by model
518
+ if (summary.costByModel.length > 0) {
519
+ lines.push("");
520
+ lines.push("Cost by Model:");
521
+ for (const modelCost of summary.costByModel) {
522
+ lines.push(` ${modelCost.model}: ${this.formatCost(modelCost.totalCost)} (${modelCost.requestCount} requests)`);
523
+ }
524
+ }
525
+ // Add tracking duration
526
+ if (summary.trackingDurationMs) {
527
+ const durationSec = summary.trackingDurationMs / 1000;
528
+ const throughput = summary.totalSpans / durationSec;
529
+ lines.push("");
530
+ lines.push(`Tracking duration: ${durationSec.toFixed(1)}s (${throughput.toFixed(2)} req/s)`);
531
+ }
532
+ return lines.join("\n");
533
+ }
534
+ }
535
+ /**
536
+ * Global metrics aggregator instance (singleton pattern from main)
537
+ */
538
+ let globalMetricsAggregator = null;
539
+ /**
540
+ * Get the global metrics aggregator instance
541
+ */
542
+ export function getMetricsAggregator() {
543
+ if (!globalMetricsAggregator) {
544
+ globalMetricsAggregator = new MetricsAggregator();
545
+ }
546
+ return globalMetricsAggregator;
547
+ }
548
+ /**
549
+ * Reset the global metrics aggregator (for testing)
550
+ */
551
+ export function resetMetricsAggregator() {
552
+ if (globalMetricsAggregator) {
553
+ globalMetricsAggregator.reset();
554
+ }
555
+ globalMetricsAggregator = null;
556
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * OpenTelemetry Bridge
3
+ * Bidirectional context propagation between NeuroLink and OpenTelemetry
4
+ */
5
+ import { type SpanContext } from "@opentelemetry/api";
6
+ import type { SpanData } from "./types/spanTypes.js";
7
+ import { type SpanType } from "./types/spanTypes.js";
8
+ /**
9
+ * Bridge for bidirectional context propagation between
10
+ * NeuroLink's observability system and OpenTelemetry
11
+ */
12
+ export declare class OtelBridge {
13
+ private readonly tracer;
14
+ /**
15
+ * Extract trace context from incoming request headers
16
+ */
17
+ extractContext(headers: Record<string, string>): SpanContext | null;
18
+ /**
19
+ * Inject trace context into outgoing request headers
20
+ */
21
+ injectContext(headers: Record<string, string>): Record<string, string>;
22
+ /**
23
+ * Create a NeuroLink span from OpenTelemetry context
24
+ */
25
+ createSpanFromOtelContext(spanContext: SpanContext, type: SpanType, name: string): SpanData;
26
+ /**
27
+ * Wrap a function with OpenTelemetry tracing that also creates NeuroLink spans
28
+ */
29
+ wrapWithTracing<T>(name: string, type: SpanType, fn: (span: SpanData) => Promise<T>, onSpanEnd?: (span: SpanData) => void): Promise<T>;
30
+ /**
31
+ * Convert NeuroLink span to OpenTelemetry span and export
32
+ */
33
+ exportToOtel(span: SpanData): void;
34
+ /**
35
+ * Get current trace context for correlation
36
+ */
37
+ getCurrentTraceContext(): {
38
+ traceId: string;
39
+ spanId: string;
40
+ } | null;
41
+ /**
42
+ * Filter attributes to only include OTel-compatible types
43
+ */
44
+ private filterAttributes;
45
+ /**
46
+ * Filter event attributes
47
+ */
48
+ private filterEventAttributes;
49
+ }