@juspay/neurolink 12.58.1 → 13.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/CHANGELOG.md +14 -3
  2. package/dist/auth/anthropicOAuth.js +3 -3
  3. package/dist/auth/middleware/AuthMiddleware.d.ts +1 -1
  4. package/dist/auth/sessionManager.js +4 -4
  5. package/dist/auth/tokenStore.js +4 -4
  6. package/dist/browser/neurolink.min.js +474 -473
  7. package/dist/cli/commands/observability.d.ts +1 -6
  8. package/dist/cli/commands/observability.js +0 -106
  9. package/dist/cli/commands/proxy.js +9 -18
  10. package/dist/cli/commands/telemetry.d.ts +3 -13
  11. package/dist/cli/commands/telemetry.js +7 -354
  12. package/dist/cli/loop/session.js +1 -1
  13. package/dist/client/httpClient.js +1 -1
  14. package/dist/client/sseClient.js +1 -1
  15. package/dist/client/streamingClient.js +2 -2
  16. package/dist/client/wsClient.js +1 -1
  17. package/dist/constants/langfuse.d.ts +25 -0
  18. package/dist/constants/langfuse.js +38 -0
  19. package/dist/constants/telemetry.d.ts +20 -0
  20. package/dist/constants/telemetry.js +34 -0
  21. package/dist/core/baseProvider.d.ts +2 -1
  22. package/dist/core/baseProvider.js +4 -11
  23. package/dist/core/conversationMemoryManager.js +5 -8
  24. package/dist/core/factory.js +1 -1
  25. package/dist/core/geminiLoopAdapter.d.ts +8 -1
  26. package/dist/core/geminiLoopAdapter.js +25 -1
  27. package/dist/core/loopEngine.js +69 -40
  28. package/dist/core/modules/MessageBuilder.js +2 -2
  29. package/dist/core/modules/StreamHandler.js +2 -2
  30. package/dist/core/modules/TelemetryHandler.d.ts +2 -18
  31. package/dist/core/modules/TelemetryHandler.js +1 -66
  32. package/dist/core/modules/ToolsManager.js +40 -31
  33. package/dist/core/nativeGenerateLoop.js +78 -8
  34. package/dist/core/redisConversationMemoryManager.js +8 -15
  35. package/dist/core/toolExecutionGuards.js +1 -13
  36. package/dist/index.d.ts +5 -12
  37. package/dist/index.js +5 -37
  38. package/dist/mcp/batching/requestBatcher.js +1 -1
  39. package/dist/mcp/externalServerManager.js +3 -21
  40. package/dist/mcp/toolDiscoveryService.js +4 -16
  41. package/dist/mcp/toolRegistry.js +2 -5
  42. package/dist/memory/memoryRetrievalTools.js +1 -1
  43. package/dist/neurolink.d.ts +16 -27
  44. package/dist/neurolink.js +788 -792
  45. package/dist/observability/exporterSpanProcessor.d.ts +50 -0
  46. package/dist/observability/exporterSpanProcessor.js +181 -0
  47. package/dist/observability/exporters/langfuseExporter.d.ts +13 -34
  48. package/dist/observability/exporters/langfuseExporter.js +101 -154
  49. package/dist/observability/index.d.ts +1 -0
  50. package/dist/observability/index.js +1 -0
  51. package/dist/observability/langfuseMasking.d.ts +8 -0
  52. package/dist/observability/langfuseMasking.js +47 -0
  53. package/dist/observability/otlpEncoding.d.ts +20 -0
  54. package/dist/observability/otlpEncoding.js +85 -0
  55. package/dist/providers/amazonBedrock/client.js +142 -176
  56. package/dist/providers/amazonBedrock/loopAdapter.js +14 -1
  57. package/dist/providers/amazonSagemaker.js +118 -80
  58. package/dist/providers/anthropic/client.js +3 -52
  59. package/dist/providers/anthropic/loopAdapter.js +24 -1
  60. package/dist/providers/anthropic/rateLimitCapture.js +9 -12
  61. package/dist/providers/googleAiStudio/client.d.ts +4 -5
  62. package/dist/providers/googleAiStudio/client.js +38 -40
  63. package/dist/providers/googleVertex/client.d.ts +2 -7
  64. package/dist/providers/googleVertex/client.js +282 -378
  65. package/dist/providers/googleVertex/constants.js +2 -2
  66. package/dist/providers/litellm/client.d.ts +2 -9
  67. package/dist/providers/litellm/client.js +1 -63
  68. package/dist/providers/openAI/client.d.ts +1 -8
  69. package/dist/providers/openAI/client.js +0 -65
  70. package/dist/providers/openaiChatCompletionsBase.d.ts +1 -0
  71. package/dist/providers/openaiChatCompletionsBase.js +61 -4
  72. package/dist/providers/replicate.js +22 -4
  73. package/dist/proxy/codexUsage.js +7 -0
  74. package/dist/proxy/proxyFetch.js +19 -25
  75. package/dist/proxy/proxyTracer.d.ts +14 -7
  76. package/dist/proxy/proxyTracer.js +129 -83
  77. package/dist/proxy/proxyTranslationEngine.js +0 -1
  78. package/dist/proxy/requestLogger.d.ts +1 -1
  79. package/dist/proxy/requestLogger.js +39 -45
  80. package/dist/server/middleware/common.js +1 -1
  81. package/dist/server/routes/agentRoutes.js +4 -4
  82. package/dist/server/routes/claudeProxyRoutes.js +99 -196
  83. package/dist/server/routes/codexProxyRoutes.js +621 -591
  84. package/dist/server/routes/healthRoutes.js +6 -6
  85. package/dist/server/routes/mcpRoutes.js +7 -7
  86. package/dist/server/routes/memoryRoutes.js +7 -7
  87. package/dist/server/routes/openApiRoutes.js +3 -3
  88. package/dist/server/routes/toolRoutes.js +5 -5
  89. package/dist/server/routes/transcribeRoutes.js +1 -1
  90. package/dist/server/utils/validation.d.ts +3 -3
  91. package/dist/services/server/ai/observability/instrumentation.d.ts +34 -201
  92. package/dist/services/server/ai/observability/instrumentation.js +220 -1267
  93. package/dist/services/server/ai/observability/langfuseSetup.d.ts +14 -0
  94. package/dist/services/server/ai/observability/langfuseSetup.js +55 -0
  95. package/dist/services/server/ai/observability/otlpSetup.d.ts +16 -0
  96. package/dist/services/server/ai/observability/otlpSetup.js +58 -0
  97. package/dist/session/globalSessionState.js +0 -11
  98. package/dist/telemetry/attributes.d.ts +23 -46
  99. package/dist/telemetry/attributes.js +76 -162
  100. package/dist/telemetry/chatMessages.d.ts +13 -0
  101. package/dist/telemetry/chatMessages.js +59 -0
  102. package/dist/telemetry/index.d.ts +5 -19
  103. package/dist/telemetry/index.js +5 -26
  104. package/dist/telemetry/observation.d.ts +37 -0
  105. package/dist/telemetry/observation.js +112 -0
  106. package/dist/telemetry/observationNames.d.ts +18 -0
  107. package/dist/telemetry/observationNames.js +24 -0
  108. package/dist/telemetry/spanSanitizer.d.ts +39 -0
  109. package/dist/telemetry/spanSanitizer.js +92 -0
  110. package/dist/telemetry/spanSerialization.d.ts +13 -0
  111. package/dist/telemetry/spanSerialization.js +46 -0
  112. package/dist/telemetry/tracers.d.ts +37 -18
  113. package/dist/telemetry/tracers.js +97 -19
  114. package/dist/telemetry/withSpan.d.ts +47 -25
  115. package/dist/telemetry/withSpan.js +158 -71
  116. package/dist/types/cli.d.ts +0 -11
  117. package/dist/types/decision.d.ts +3 -0
  118. package/dist/types/exporter.d.ts +54 -0
  119. package/dist/types/exporter.js +0 -4
  120. package/dist/types/generate.d.ts +4 -1
  121. package/dist/types/isolatedAgent.d.ts +5 -4
  122. package/dist/types/loopEngine.d.ts +18 -28
  123. package/dist/types/observability.d.ts +127 -160
  124. package/dist/types/providers.d.ts +15 -0
  125. package/dist/types/proxy.d.ts +41 -2
  126. package/dist/utils/anthropicTraceSanitizer.d.ts +2 -2
  127. package/dist/utils/anthropicTraceSanitizer.js +3 -1
  128. package/dist/utils/conversationMemory.js +4 -4
  129. package/dist/utils/fileDetector.js +1 -1
  130. package/dist/utils/messageBuilder.js +1 -1
  131. package/dist/utils/noOutputSentinel.d.ts +4 -16
  132. package/dist/utils/noOutputSentinel.js +12 -28
  133. package/dist/utils/providerRetry.js +4 -4
  134. package/dist/voice/livekit/brain.js +1 -1
  135. package/dist/voice/livekit/realtimeMemory.d.ts +1 -1
  136. package/dist/voice/livekit/realtimeMemory.js +66 -1
  137. package/dist/voice/livekit/realtimeVoiceAgent.js +1 -1
  138. package/docs-site/static/search-index.json +42 -63
  139. package/package.json +10 -4
  140. package/dist/telemetry/telemetryService.d.ts +0 -79
  141. package/dist/telemetry/telemetryService.js +0 -572
  142. package/dist/utils/observabilityHelpers.d.ts +0 -27
  143. package/dist/utils/observabilityHelpers.js +0 -47
@@ -1,572 +0,0 @@
1
- import { context, metrics, trace, } from "@opentelemetry/api";
2
- import { logger } from "../utils/logger.js";
3
- export class TelemetryService {
4
- static instance;
5
- tracerProvider;
6
- enabled = false;
7
- initialized = false;
8
- usingExternalTracerProvider = false;
9
- meter;
10
- tracer;
11
- // Optional Metrics (only created when enabled)
12
- aiRequestCounter;
13
- aiRequestDuration;
14
- aiTokensUsed;
15
- aiProviderErrors;
16
- aiCostUsd;
17
- mcpToolCalls;
18
- connectionCounter;
19
- responseTimeHistogram;
20
- // Runtime metrics tracking
21
- activeConnectionCount = 0;
22
- errorCount = 0;
23
- requestCount = 0;
24
- totalResponseTime = 0;
25
- responseTimeCount = 0;
26
- // Async-init readiness + buffering. Instruments above are constructed by
27
- // an async initializeTelemetry() that the constructor fires but does not
28
- // await (see getInstance()'s comment). A recording call can therefore
29
- // legitimately arrive while telemetry is enabled but the instruments
30
- // don't exist yet — that is NOT the same as telemetry being disabled, so
31
- // it must not be dropped silently. `pendingCalls` buffers the OTEL side
32
- // effect of such a call (never the whole method — see each recordX
33
- // method's emitX split) until `markReady()` drains it. The cap and
34
- // rate-limited warning below exist only for a pathological case (init
35
- // that never resolves); the common case drains within milliseconds.
36
- static MAX_PENDING_CALLS = 200;
37
- pendingCalls = [];
38
- isReady = false;
39
- droppedPendingCallWarned = false;
40
- /** The in-flight (or already-settled) async init. Never rejects — see
41
- * initializeTelemetry()'s catch block, which always resolves normally.
42
- * `initialize()`/`shutdown()` await it so they don't race construction. */
43
- readyPromise;
44
- constructor() {
45
- // Check if telemetry is enabled
46
- this.enabled = this.isTelemetryEnabled();
47
- if (this.enabled) {
48
- // initializeTelemetry() is async — it dynamically imports the OTel
49
- // SDK packages and constructs the tracer/meter/instruments — but it
50
- // is fired here WITHOUT an await. The constructor itself must stay
51
- // fully synchronous and non-yielding, because getInstance() below is
52
- // a plain, synchronous check-then-assign with no await in between.
53
- // That is only race-free as long as nothing here yields control back
54
- // to the event loop before `TelemetryService.instance` is assigned.
55
- // Awaiting anything in the constructor (or making getInstance()
56
- // async) would let two concurrent getInstance() callers both observe
57
- // "not yet assigned" and each construct their own instance — the
58
- // exact double-construction race that
59
- // observability/instrumentation.ts's initializeOpenTelemetry() already
60
- // hit and had to fix with a shared in-flight promise (see the
61
- // "Initialize OpenTelemetry once and let concurrent callers share the
62
- // work" comment there). Do not reintroduce it here.
63
- this.readyPromise = this.initializeTelemetry();
64
- }
65
- else {
66
- logger.debug("[Telemetry] Disabled - set NEUROLINK_TELEMETRY_ENABLED=true or configure OTEL_EXPORTER_OTLP_ENDPOINT to enable");
67
- }
68
- }
69
- static getInstance() {
70
- // Synchronous check-then-assign — see the constructor's comment above
71
- // for why this must never gain an await before the assignment.
72
- if (!TelemetryService.instance) {
73
- TelemetryService.instance = new TelemetryService();
74
- }
75
- return TelemetryService.instance;
76
- }
77
- isTelemetryEnabled() {
78
- return (this.hasExternalTracerProvider() ||
79
- process.env.NEUROLINK_TELEMETRY_ENABLED === "true" ||
80
- process.env.OTEL_EXPORTER_OTLP_ENDPOINT !== undefined);
81
- }
82
- hasExternalTracerProvider() {
83
- try {
84
- const provider = trace.getTracerProvider();
85
- if (!provider) {
86
- return false;
87
- }
88
- const providerName = provider.constructor?.name || "";
89
- if (providerName &&
90
- providerName !== "ProxyTracerProvider" &&
91
- providerName !== "NoopTracerProvider") {
92
- return true;
93
- }
94
- const delegate = typeof provider.getDelegate === "function"
95
- ? provider.getDelegate()
96
- : provider._delegate;
97
- const delegateName = delegate?.constructor?.name || "";
98
- return Boolean(delegateName && delegateName !== "NoopTracerProvider");
99
- }
100
- catch (error) {
101
- logger.warn("[Telemetry] Failed checking for external TracerProvider", {
102
- error: error instanceof Error ? error.message : String(error),
103
- });
104
- return false;
105
- }
106
- }
107
- adoptExternalTracerProvider(reason) {
108
- this.usingExternalTracerProvider = true;
109
- this.tracerProvider = undefined;
110
- this.meter = metrics.getMeter("neurolink-ai");
111
- this.tracer = trace.getTracer("neurolink-ai");
112
- this.initializeMetrics();
113
- logger.debug("[Telemetry] Reusing externally managed TracerProvider", {
114
- reason,
115
- endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
116
- });
117
- }
118
- async initializeTelemetry() {
119
- try {
120
- if (this.hasExternalTracerProvider()) {
121
- this.adoptExternalTracerProvider("global tracer provider already registered");
122
- this.markReady();
123
- return;
124
- }
125
- const [{ BasicTracerProvider, BatchSpanProcessor }, { OTLPTraceExporter }, { resourceFromAttributes }, { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION },] = await Promise.all([
126
- import("@opentelemetry/sdk-trace-base"),
127
- import("@opentelemetry/exporter-trace-otlp-http"),
128
- import("@opentelemetry/resources"),
129
- import("@opentelemetry/semantic-conventions"),
130
- ]);
131
- const resource = resourceFromAttributes({
132
- [ATTR_SERVICE_NAME]: process.env.OTEL_SERVICE_NAME || "neurolink-ai",
133
- [ATTR_SERVICE_VERSION]: process.env.OTEL_SERVICE_VERSION || "3.0.1",
134
- });
135
- const exporter = new OTLPTraceExporter({
136
- url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT
137
- ? `${process.env.OTEL_EXPORTER_OTLP_ENDPOINT}/v1/traces`
138
- : undefined,
139
- });
140
- this.tracerProvider = new BasicTracerProvider({
141
- resource,
142
- spanProcessors: [new BatchSpanProcessor(exporter)],
143
- });
144
- this.meter = metrics.getMeter("neurolink-ai");
145
- this.tracer = this.tracerProvider.getTracer("neurolink-ai");
146
- this.initializeMetrics();
147
- this.markReady();
148
- logger.debug("[Telemetry] Initialized local telemetry exporter", {
149
- endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
150
- globalTracerProviderOwnedBy: "observability/instrumentation",
151
- });
152
- }
153
- catch (error) {
154
- const errorMessage = error instanceof Error ? error.message : String(error);
155
- const isDuplicateRegistration = errorMessage.includes("duplicate registration") ||
156
- errorMessage.includes("already registered") ||
157
- errorMessage.includes("already set");
158
- if (isDuplicateRegistration && this.hasExternalTracerProvider()) {
159
- this.adoptExternalTracerProvider("duplicate global tracer registration detected");
160
- this.markReady();
161
- return;
162
- }
163
- logger.error("[Telemetry] Failed to initialize:", error);
164
- this.enabled = false;
165
- // No instruments exist and none ever will for this instance — there
166
- // is nothing left to drain a buffered call against, so the buffer is
167
- // cleared rather than drained. Every recording method's `!this.enabled`
168
- // guard now makes it a no-op anyway.
169
- this.discardPendingCalls();
170
- }
171
- }
172
- initializeMetrics() {
173
- if (!this.enabled || !this.meter) {
174
- return;
175
- }
176
- this.aiRequestCounter = this.meter.createCounter("ai_requests_total", {
177
- description: "Total number of AI requests",
178
- });
179
- this.aiRequestDuration = this.meter.createHistogram("ai_request_duration_ms", {
180
- description: "AI request duration in milliseconds",
181
- });
182
- this.aiTokensUsed = this.meter.createCounter("ai_tokens_used_total", {
183
- description: "Total number of AI tokens used",
184
- });
185
- this.aiCostUsd = this.meter.createCounter("ai_cost_usd_total", {
186
- description: "Total accumulated AI cost in USD",
187
- });
188
- this.aiProviderErrors = this.meter.createCounter("ai_provider_errors_total", {
189
- description: "Total number of AI provider errors",
190
- });
191
- this.mcpToolCalls = this.meter.createCounter("mcp_tool_calls_total", {
192
- description: "Total number of MCP tool calls",
193
- });
194
- this.connectionCounter = this.meter.createCounter("connections_total", {
195
- description: "Total number of connections",
196
- });
197
- this.responseTimeHistogram = this.meter.createHistogram("response_time_ms", {
198
- description: "Response time in milliseconds",
199
- });
200
- }
201
- // ============================================================
202
- // Pending-call buffer (see the field comment above pendingCalls)
203
- // ============================================================
204
- bufferPendingCall(call) {
205
- if (this.pendingCalls.length >= TelemetryService.MAX_PENDING_CALLS) {
206
- // Drop the oldest, not the newest — recent calls are more likely to
207
- // still matter to whoever is about to read a dashboard.
208
- this.pendingCalls.shift();
209
- if (!this.droppedPendingCallWarned) {
210
- this.droppedPendingCallWarned = true;
211
- logger.warn(`[Telemetry] Pending metric buffer exceeded ${TelemetryService.MAX_PENDING_CALLS} entries — dropping the oldest buffered call(s). Logged once, not per drop: telemetry initialization may be unusually slow or stalled.`);
212
- }
213
- }
214
- this.pendingCalls.push(call);
215
- }
216
- /** Instruments now exist — drain anything buffered while we waited, in
217
- * order, then flip fully sync for every call after this one. */
218
- markReady() {
219
- this.isReady = true;
220
- if (this.pendingCalls.length === 0) {
221
- return;
222
- }
223
- const buffered = this.pendingCalls;
224
- this.pendingCalls = [];
225
- for (const call of buffered) {
226
- try {
227
- call();
228
- }
229
- catch (error) {
230
- logger.warn("[Telemetry] A buffered metric call failed during drain:", error);
231
- }
232
- }
233
- }
234
- discardPendingCalls() {
235
- this.pendingCalls = [];
236
- }
237
- async initialize() {
238
- if (!this.enabled) {
239
- return;
240
- }
241
- // The tracer/meter/instruments are constructed asynchronously (see the
242
- // constructor's comment). Wait for that to settle before inspecting
243
- // usingExternalTracerProvider/tracerProvider below, so this method's
244
- // behavior doesn't depend on how many event-loop ticks have passed
245
- // since getInstance() was called — without this, a caller invoking
246
- // initialize() right after getInstance() would almost always observe
247
- // `!this.tracerProvider` and return early, silently skipping the
248
- // AsyncLocalStorageContextManager registration below.
249
- if (this.readyPromise) {
250
- await this.readyPromise;
251
- }
252
- if (this.usingExternalTracerProvider) {
253
- this.initialized = true;
254
- logger.debug("[Telemetry] External TracerProvider already initialized by host");
255
- return;
256
- }
257
- if (!this.tracerProvider) {
258
- this.initialized = true;
259
- logger.debug("[Telemetry] Tracer provider already prepared during constructor");
260
- return;
261
- }
262
- try {
263
- // Register AsyncLocalStorage context manager for proper parent-child
264
- // span relationships across async boundaries (required for startActiveSpan)
265
- try {
266
- const { AsyncLocalStorageContextManager } = await import("@opentelemetry/context-async-hooks");
267
- context.setGlobalContextManager(new AsyncLocalStorageContextManager().enable());
268
- }
269
- catch {
270
- // context-async-hooks not installed — context propagation
271
- // will use the default (noop) manager
272
- }
273
- this.initialized = true;
274
- logger.debug("[Telemetry] Tracer provider started successfully");
275
- }
276
- catch (error) {
277
- logger.error("[Telemetry] Failed to start:", error);
278
- this.enabled = false;
279
- this.initialized = false;
280
- }
281
- }
282
- // AI Operation Tracing (NO-OP when disabled)
283
- /**
284
- * @deprecated Vercel AI SDK's experimental_telemetry creates ai.generateText/ai.streamText
285
- * spans automatically via OpenTelemetry. Using this method would create duplicate spans.
286
- * Kept for potential future use with non-Vercel providers (e.g., Amazon Bedrock).
287
- * See: TelemetryHandler.getTelemetryConfig() for the active telemetry path.
288
- */
289
- async traceAIRequest(provider, operation, operationType = "generate_text") {
290
- if (!this.enabled || !this.tracer) {
291
- return await operation();
292
- }
293
- const span = this.tracer.startSpan(`ai.${provider}.${operationType}`, {
294
- attributes: {
295
- "ai.provider": provider,
296
- "ai.operation": operationType,
297
- },
298
- });
299
- try {
300
- const result = await operation();
301
- span.setStatus({ code: 1 }); // OK
302
- return result;
303
- }
304
- catch (error) {
305
- span.setStatus({
306
- code: 2,
307
- message: error instanceof Error ? error.message : "Unknown error",
308
- }); // ERROR
309
- span.recordException(error);
310
- throw error;
311
- }
312
- finally {
313
- span.end();
314
- }
315
- }
316
- // Metrics Recording (NO-OP when disabled)
317
- recordAIRequest(provider, model, tokens, duration, cost) {
318
- // Track runtime metrics
319
- this.requestCount++;
320
- this.totalResponseTime += duration;
321
- this.responseTimeCount++;
322
- if (!this.enabled) {
323
- return;
324
- }
325
- if (!this.isReady) {
326
- // Instruments aren't constructed yet — buffer the OTEL side effect
327
- // only (emitAIRequest), never this whole method: the runtime
328
- // counters above must fire exactly once per call, not again when the
329
- // buffer drains.
330
- this.bufferPendingCall(() => this.emitAIRequest(provider, model, tokens, duration, cost));
331
- return;
332
- }
333
- this.emitAIRequest(provider, model, tokens, duration, cost);
334
- }
335
- emitAIRequest(provider, model, tokens, duration, cost) {
336
- if (!this.aiRequestCounter) {
337
- return;
338
- }
339
- const labels = { provider, model };
340
- this.aiRequestCounter.add(1, labels);
341
- this.aiRequestDuration?.record(duration, labels);
342
- this.aiTokensUsed?.add(tokens, labels);
343
- if (cost !== undefined && Number.isFinite(cost) && cost > 0) {
344
- this.aiCostUsd?.add(cost, labels);
345
- }
346
- }
347
- recordAIError(provider, error) {
348
- // Track runtime metrics
349
- this.errorCount++;
350
- if (!this.enabled) {
351
- return;
352
- }
353
- if (!this.isReady) {
354
- this.bufferPendingCall(() => this.emitAIError(provider, error));
355
- return;
356
- }
357
- this.emitAIError(provider, error);
358
- }
359
- emitAIError(provider, error) {
360
- if (!this.aiProviderErrors) {
361
- return;
362
- }
363
- this.aiProviderErrors.add(1, {
364
- provider,
365
- error: error.name,
366
- message: error.message.substring(0, 100), // Limit message length
367
- });
368
- }
369
- recordMCPToolCall(toolName, duration, success) {
370
- if (!this.enabled) {
371
- return;
372
- }
373
- if (!this.isReady) {
374
- this.bufferPendingCall(() => this.emitMCPToolCall(toolName, duration, success));
375
- return;
376
- }
377
- this.emitMCPToolCall(toolName, duration, success);
378
- }
379
- emitMCPToolCall(toolName, duration, success) {
380
- if (!this.mcpToolCalls) {
381
- return;
382
- }
383
- this.mcpToolCalls.add(1, {
384
- tool: toolName,
385
- success: success.toString(),
386
- duration_bucket: this.getDurationBucket(duration),
387
- });
388
- }
389
- recordConnection(type) {
390
- // Track runtime metrics
391
- this.activeConnectionCount++;
392
- if (!this.enabled) {
393
- return;
394
- }
395
- if (!this.isReady) {
396
- this.bufferPendingCall(() => this.emitConnection(type));
397
- return;
398
- }
399
- this.emitConnection(type);
400
- }
401
- emitConnection(type) {
402
- if (!this.connectionCounter) {
403
- return;
404
- }
405
- this.connectionCounter.add(1, { connection_type: type });
406
- }
407
- recordConnectionClosed(type) {
408
- // Track runtime metrics
409
- this.activeConnectionCount = Math.max(0, this.activeConnectionCount - 1);
410
- if (!this.enabled) {
411
- return;
412
- }
413
- if (!this.isReady) {
414
- this.bufferPendingCall(() => this.emitConnectionClosed(type));
415
- return;
416
- }
417
- this.emitConnectionClosed(type);
418
- }
419
- emitConnectionClosed(type) {
420
- if (!this.connectionCounter) {
421
- return;
422
- }
423
- // Optionally record disconnection metrics if needed
424
- this.connectionCounter.add(-1, {
425
- connection_type: type,
426
- event: "disconnect",
427
- });
428
- }
429
- recordResponseTime(endpoint, method, duration) {
430
- // Track runtime metrics
431
- this.totalResponseTime += duration;
432
- this.responseTimeCount++;
433
- if (!this.enabled) {
434
- return;
435
- }
436
- if (!this.isReady) {
437
- this.bufferPendingCall(() => this.emitResponseTime(endpoint, method, duration));
438
- return;
439
- }
440
- this.emitResponseTime(endpoint, method, duration);
441
- }
442
- emitResponseTime(endpoint, method, duration) {
443
- if (!this.responseTimeHistogram) {
444
- return;
445
- }
446
- this.responseTimeHistogram.record(duration, {
447
- endpoint,
448
- method,
449
- status_bucket: this.getStatusBucket(duration),
450
- });
451
- }
452
- // Custom Metrics
453
- recordCustomMetric(name, value, labels) {
454
- if (!this.enabled) {
455
- return;
456
- }
457
- if (!this.isReady) {
458
- this.bufferPendingCall(() => this.emitCustomMetric(name, value, labels));
459
- return;
460
- }
461
- this.emitCustomMetric(name, value, labels);
462
- }
463
- emitCustomMetric(name, value, labels) {
464
- if (!this.meter) {
465
- return;
466
- }
467
- const counter = this.meter.createCounter(`custom_${name}`, {
468
- description: `Custom metric: ${name}`,
469
- });
470
- counter.add(value, labels || {});
471
- }
472
- recordCustomHistogram(name, value, labels) {
473
- if (!this.enabled) {
474
- return;
475
- }
476
- if (!this.isReady) {
477
- this.bufferPendingCall(() => this.emitCustomHistogram(name, value, labels));
478
- return;
479
- }
480
- this.emitCustomHistogram(name, value, labels);
481
- }
482
- emitCustomHistogram(name, value, labels) {
483
- if (!this.meter) {
484
- return;
485
- }
486
- const histogram = this.meter.createHistogram(`custom_${name}_histogram`, {
487
- description: `Custom histogram: ${name}`,
488
- });
489
- histogram.record(value, labels || {});
490
- }
491
- // Health Checks
492
- async getHealthMetrics() {
493
- const memoryUsage = process.memoryUsage();
494
- // Calculate error rate as percentage of errors vs total requests
495
- const errorRate = this.requestCount > 0 ? (this.errorCount / this.requestCount) * 100 : 0;
496
- // Calculate average response time
497
- const averageResponseTime = this.responseTimeCount > 0
498
- ? this.totalResponseTime / this.responseTimeCount
499
- : 0;
500
- return {
501
- timestamp: Date.now(),
502
- memoryUsage,
503
- uptime: process.uptime(),
504
- activeConnections: this.activeConnectionCount,
505
- errorRate: Math.round(errorRate * 100) / 100, // Round to 2 decimal places
506
- averageResponseTime: Math.round(averageResponseTime * 100) / 100, // Round to 2 decimal places
507
- };
508
- }
509
- // Telemetry Status
510
- isEnabled() {
511
- return this.enabled;
512
- }
513
- getStatus() {
514
- return {
515
- enabled: this.enabled,
516
- initialized: this.initialized,
517
- endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
518
- service: process.env.OTEL_SERVICE_NAME || "neurolink-ai",
519
- version: process.env.OTEL_SERVICE_VERSION || "3.0.1",
520
- };
521
- }
522
- // Helper methods
523
- getDurationBucket(duration) {
524
- if (duration < 100) {
525
- return "fast";
526
- }
527
- if (duration < 500) {
528
- return "medium";
529
- }
530
- if (duration < 1000) {
531
- return "slow";
532
- }
533
- return "very_slow";
534
- }
535
- getStatusBucket(duration) {
536
- if (duration < 200) {
537
- return "excellent";
538
- }
539
- if (duration < 500) {
540
- return "good";
541
- }
542
- if (duration < 1000) {
543
- return "acceptable";
544
- }
545
- return "poor";
546
- }
547
- // Cleanup
548
- async shutdown() {
549
- // See initialize()'s comment: wait for the async instrument
550
- // construction to settle before deciding whether there is a
551
- // tracerProvider to shut down. Without this, a shutdown() called
552
- // immediately after getInstance() would see no tracerProvider yet and
553
- // no-op, while construction — which is already in flight — finishes
554
- // moments later and leaves a live exporter (with its background flush
555
- // timers) behind with nothing left to ever shut it down.
556
- if (this.readyPromise) {
557
- await this.readyPromise;
558
- }
559
- if (this.enabled &&
560
- this.tracerProvider &&
561
- !this.usingExternalTracerProvider) {
562
- try {
563
- await this.tracerProvider.shutdown();
564
- this.initialized = false;
565
- logger.debug("[Telemetry] Tracer provider shutdown completed");
566
- }
567
- catch (error) {
568
- logger.error("[Telemetry] Error during shutdown:", error);
569
- }
570
- }
571
- }
572
- }
@@ -1,27 +0,0 @@
1
- /**
2
- * Utility for building observability configs from environment variables
3
- */
4
- import type { ObservabilityConfig } from "../types/index.js";
5
- /**
6
- * Build observability config from environment variables
7
- *
8
- * Reads Langfuse configuration from environment:
9
- * - LANGFUSE_ENABLED: Enable/disable Langfuse (must be "true")
10
- * - LANGFUSE_PUBLIC_KEY: Your Langfuse public key (required)
11
- * - LANGFUSE_SECRET_KEY: Your Langfuse secret key (required)
12
- * - LANGFUSE_BASE_URL: Langfuse server URL (default: https://cloud.langfuse.com)
13
- * - LANGFUSE_ENVIRONMENT: Environment name (default: dev)
14
- * - PUBLIC_APP_VERSION: Release/version identifier (default: v1.0.0)
15
- *
16
- * @returns ObservabilityConfig if all required env vars are set, undefined otherwise
17
- *
18
- * @example
19
- * ```typescript
20
- * import { NeuroLink, buildObservabilityConfigFromEnv } from '@juspay/neurolink';
21
- *
22
- * const neurolink = new NeuroLink({
23
- * observability: buildObservabilityConfigFromEnv()
24
- * });
25
- * ```
26
- */
27
- export declare function buildObservabilityConfigFromEnv(): ObservabilityConfig | undefined;
@@ -1,47 +0,0 @@
1
- /**
2
- * Utility for building observability configs from environment variables
3
- */
4
- /**
5
- * Build observability config from environment variables
6
- *
7
- * Reads Langfuse configuration from environment:
8
- * - LANGFUSE_ENABLED: Enable/disable Langfuse (must be "true")
9
- * - LANGFUSE_PUBLIC_KEY: Your Langfuse public key (required)
10
- * - LANGFUSE_SECRET_KEY: Your Langfuse secret key (required)
11
- * - LANGFUSE_BASE_URL: Langfuse server URL (default: https://cloud.langfuse.com)
12
- * - LANGFUSE_ENVIRONMENT: Environment name (default: dev)
13
- * - PUBLIC_APP_VERSION: Release/version identifier (default: v1.0.0)
14
- *
15
- * @returns ObservabilityConfig if all required env vars are set, undefined otherwise
16
- *
17
- * @example
18
- * ```typescript
19
- * import { NeuroLink, buildObservabilityConfigFromEnv } from '@juspay/neurolink';
20
- *
21
- * const neurolink = new NeuroLink({
22
- * observability: buildObservabilityConfigFromEnv()
23
- * });
24
- * ```
25
- */
26
- export function buildObservabilityConfigFromEnv() {
27
- const langfuseEnabled = process.env.LANGFUSE_ENABLED?.trim().toLowerCase() === "true";
28
- const publicKey = process.env.LANGFUSE_PUBLIC_KEY?.trim();
29
- const secretKey = process.env.LANGFUSE_SECRET_KEY?.trim();
30
- if (!langfuseEnabled || !publicKey || !secretKey) {
31
- return undefined;
32
- }
33
- return {
34
- langfuse: {
35
- enabled: langfuseEnabled,
36
- publicKey,
37
- secretKey,
38
- baseUrl: process.env.LANGFUSE_BASE_URL?.trim() || "https://cloud.langfuse.com",
39
- environment: process.env.LANGFUSE_ENVIRONMENT?.trim() ||
40
- process.env.PUBLIC_APP_ENVIRONMENT?.trim() ||
41
- "dev",
42
- release: process.env.PUBLIC_APP_VERSION?.trim() ||
43
- process.env.npm_package_version?.trim() ||
44
- "v1.0.0",
45
- },
46
- };
47
- }