@juspay/neurolink 9.41.0 → 9.42.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 (189) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +7 -1
  3. package/dist/auth/anthropicOAuth.d.ts +18 -3
  4. package/dist/auth/anthropicOAuth.js +137 -4
  5. package/dist/auth/providers/firebase.js +5 -1
  6. package/dist/auth/providers/jwt.js +5 -1
  7. package/dist/auth/providers/workos.js +5 -1
  8. package/dist/auth/sessionManager.d.ts +1 -1
  9. package/dist/auth/sessionManager.js +58 -27
  10. package/dist/browser/neurolink.min.js +337 -318
  11. package/dist/cli/commands/mcp.js +3 -0
  12. package/dist/cli/commands/proxy.d.ts +2 -1
  13. package/dist/cli/commands/proxy.js +279 -16
  14. package/dist/cli/commands/task.js +3 -0
  15. package/dist/cli/factories/commandFactory.d.ts +2 -0
  16. package/dist/cli/factories/commandFactory.js +38 -0
  17. package/dist/cli/parser.js +4 -3
  18. package/dist/client/aiSdkAdapter.js +3 -0
  19. package/dist/client/streamingClient.js +30 -10
  20. package/dist/core/modules/GenerationHandler.js +3 -2
  21. package/dist/core/redisConversationMemoryManager.js +7 -3
  22. package/dist/evaluation/BatchEvaluator.js +4 -1
  23. package/dist/evaluation/hooks/observabilityHooks.js +5 -3
  24. package/dist/evaluation/pipeline/evaluationPipeline.d.ts +3 -2
  25. package/dist/evaluation/pipeline/evaluationPipeline.js +20 -8
  26. package/dist/evaluation/pipeline/strategies/batchStrategy.js +6 -3
  27. package/dist/evaluation/pipeline/strategies/samplingStrategy.js +18 -10
  28. package/dist/lib/auth/anthropicOAuth.d.ts +18 -3
  29. package/dist/lib/auth/anthropicOAuth.js +137 -4
  30. package/dist/lib/auth/providers/firebase.js +5 -1
  31. package/dist/lib/auth/providers/jwt.js +5 -1
  32. package/dist/lib/auth/providers/workos.js +5 -1
  33. package/dist/lib/auth/sessionManager.d.ts +1 -1
  34. package/dist/lib/auth/sessionManager.js +58 -27
  35. package/dist/lib/client/aiSdkAdapter.js +3 -0
  36. package/dist/lib/client/streamingClient.js +30 -10
  37. package/dist/lib/core/modules/GenerationHandler.js +3 -2
  38. package/dist/lib/core/redisConversationMemoryManager.js +7 -3
  39. package/dist/lib/evaluation/BatchEvaluator.js +4 -1
  40. package/dist/lib/evaluation/hooks/observabilityHooks.js +5 -3
  41. package/dist/lib/evaluation/pipeline/evaluationPipeline.d.ts +3 -2
  42. package/dist/lib/evaluation/pipeline/evaluationPipeline.js +20 -8
  43. package/dist/lib/evaluation/pipeline/strategies/batchStrategy.js +6 -3
  44. package/dist/lib/evaluation/pipeline/strategies/samplingStrategy.js +18 -10
  45. package/dist/lib/neurolink.d.ts +3 -2
  46. package/dist/lib/neurolink.js +260 -494
  47. package/dist/lib/observability/otelBridge.d.ts +2 -2
  48. package/dist/lib/observability/otelBridge.js +12 -3
  49. package/dist/lib/providers/amazonBedrock.js +2 -4
  50. package/dist/lib/providers/anthropic.d.ts +9 -5
  51. package/dist/lib/providers/anthropic.js +19 -14
  52. package/dist/lib/providers/anthropicBaseProvider.d.ts +3 -3
  53. package/dist/lib/providers/anthropicBaseProvider.js +5 -4
  54. package/dist/lib/providers/azureOpenai.d.ts +1 -1
  55. package/dist/lib/providers/azureOpenai.js +5 -4
  56. package/dist/lib/providers/googleAiStudio.js +30 -1
  57. package/dist/lib/providers/googleVertex.js +28 -6
  58. package/dist/lib/providers/huggingFace.d.ts +3 -3
  59. package/dist/lib/providers/huggingFace.js +6 -8
  60. package/dist/lib/providers/litellm.js +41 -29
  61. package/dist/lib/providers/mistral.js +2 -1
  62. package/dist/lib/providers/ollama.js +80 -23
  63. package/dist/lib/providers/openAI.js +3 -2
  64. package/dist/lib/providers/openRouter.js +2 -1
  65. package/dist/lib/providers/openaiCompatible.d.ts +4 -4
  66. package/dist/lib/providers/openaiCompatible.js +4 -4
  67. package/dist/lib/proxy/claudeFormat.d.ts +3 -2
  68. package/dist/lib/proxy/claudeFormat.js +25 -20
  69. package/dist/lib/proxy/cloaking/plugins/sessionIdentity.d.ts +2 -6
  70. package/dist/lib/proxy/cloaking/plugins/sessionIdentity.js +9 -33
  71. package/dist/lib/proxy/modelRouter.js +3 -0
  72. package/dist/lib/proxy/oauthFetch.d.ts +1 -1
  73. package/dist/lib/proxy/oauthFetch.js +65 -72
  74. package/dist/lib/proxy/proxyConfig.js +44 -24
  75. package/dist/lib/proxy/proxyEnv.d.ts +19 -0
  76. package/dist/lib/proxy/proxyEnv.js +73 -0
  77. package/dist/lib/proxy/proxyFetch.js +50 -4
  78. package/dist/lib/proxy/proxyTracer.d.ts +133 -0
  79. package/dist/lib/proxy/proxyTracer.js +645 -0
  80. package/dist/lib/proxy/rawStreamCapture.d.ts +10 -0
  81. package/dist/lib/proxy/rawStreamCapture.js +83 -0
  82. package/dist/lib/proxy/requestLogger.d.ts +32 -5
  83. package/dist/lib/proxy/requestLogger.js +406 -37
  84. package/dist/lib/proxy/sseInterceptor.d.ts +97 -0
  85. package/dist/lib/proxy/sseInterceptor.js +402 -0
  86. package/dist/lib/proxy/usageStats.d.ts +4 -3
  87. package/dist/lib/proxy/usageStats.js +25 -12
  88. package/dist/lib/rag/chunkers/MarkdownChunker.js +13 -5
  89. package/dist/lib/rag/chunking/markdownChunker.js +15 -6
  90. package/dist/lib/server/routes/claudeProxyRoutes.d.ts +7 -2
  91. package/dist/lib/server/routes/claudeProxyRoutes.js +1737 -508
  92. package/dist/lib/services/server/ai/observability/instrumentation.d.ts +7 -1
  93. package/dist/lib/services/server/ai/observability/instrumentation.js +240 -40
  94. package/dist/lib/tasks/backends/bullmqBackend.d.ts +1 -0
  95. package/dist/lib/tasks/backends/bullmqBackend.js +14 -7
  96. package/dist/lib/tasks/store/redisTaskStore.d.ts +1 -0
  97. package/dist/lib/tasks/store/redisTaskStore.js +34 -26
  98. package/dist/lib/tasks/taskManager.d.ts +3 -0
  99. package/dist/lib/tasks/taskManager.js +63 -30
  100. package/dist/lib/telemetry/index.d.ts +2 -1
  101. package/dist/lib/telemetry/index.js +2 -1
  102. package/dist/lib/telemetry/telemetryService.d.ts +3 -0
  103. package/dist/lib/telemetry/telemetryService.js +65 -5
  104. package/dist/lib/types/cli.d.ts +10 -0
  105. package/dist/lib/types/proxyTypes.d.ts +37 -5
  106. package/dist/lib/types/streamTypes.d.ts +25 -3
  107. package/dist/lib/utils/messageBuilder.js +3 -2
  108. package/dist/lib/utils/providerHealth.d.ts +18 -0
  109. package/dist/lib/utils/providerHealth.js +240 -9
  110. package/dist/lib/utils/providerUtils.js +14 -8
  111. package/dist/lib/utils/toolChoice.d.ts +4 -0
  112. package/dist/lib/utils/toolChoice.js +7 -0
  113. package/dist/neurolink.d.ts +3 -2
  114. package/dist/neurolink.js +260 -494
  115. package/dist/observability/otelBridge.d.ts +2 -2
  116. package/dist/observability/otelBridge.js +12 -3
  117. package/dist/providers/amazonBedrock.js +2 -4
  118. package/dist/providers/anthropic.d.ts +9 -5
  119. package/dist/providers/anthropic.js +19 -14
  120. package/dist/providers/anthropicBaseProvider.d.ts +3 -3
  121. package/dist/providers/anthropicBaseProvider.js +5 -4
  122. package/dist/providers/azureOpenai.d.ts +1 -1
  123. package/dist/providers/azureOpenai.js +5 -4
  124. package/dist/providers/googleAiStudio.js +30 -1
  125. package/dist/providers/googleVertex.js +28 -6
  126. package/dist/providers/huggingFace.d.ts +3 -3
  127. package/dist/providers/huggingFace.js +6 -7
  128. package/dist/providers/litellm.js +41 -29
  129. package/dist/providers/mistral.js +2 -1
  130. package/dist/providers/ollama.js +80 -23
  131. package/dist/providers/openAI.js +3 -2
  132. package/dist/providers/openRouter.js +2 -1
  133. package/dist/providers/openaiCompatible.d.ts +4 -4
  134. package/dist/providers/openaiCompatible.js +4 -3
  135. package/dist/proxy/claudeFormat.d.ts +3 -2
  136. package/dist/proxy/claudeFormat.js +25 -20
  137. package/dist/proxy/cloaking/plugins/sessionIdentity.d.ts +2 -6
  138. package/dist/proxy/cloaking/plugins/sessionIdentity.js +9 -33
  139. package/dist/proxy/modelRouter.js +3 -0
  140. package/dist/proxy/oauthFetch.d.ts +1 -1
  141. package/dist/proxy/oauthFetch.js +65 -72
  142. package/dist/proxy/proxyConfig.js +44 -24
  143. package/dist/proxy/proxyEnv.d.ts +19 -0
  144. package/dist/proxy/proxyEnv.js +72 -0
  145. package/dist/proxy/proxyFetch.js +50 -4
  146. package/dist/proxy/proxyTracer.d.ts +133 -0
  147. package/dist/proxy/proxyTracer.js +644 -0
  148. package/dist/proxy/rawStreamCapture.d.ts +10 -0
  149. package/dist/proxy/rawStreamCapture.js +82 -0
  150. package/dist/proxy/requestLogger.d.ts +32 -5
  151. package/dist/proxy/requestLogger.js +406 -37
  152. package/dist/proxy/sseInterceptor.d.ts +97 -0
  153. package/dist/proxy/sseInterceptor.js +401 -0
  154. package/dist/proxy/usageStats.d.ts +4 -3
  155. package/dist/proxy/usageStats.js +25 -12
  156. package/dist/rag/chunkers/MarkdownChunker.js +13 -5
  157. package/dist/rag/chunking/markdownChunker.js +15 -6
  158. package/dist/server/routes/claudeProxyRoutes.d.ts +7 -2
  159. package/dist/server/routes/claudeProxyRoutes.js +1737 -508
  160. package/dist/services/server/ai/observability/instrumentation.d.ts +7 -1
  161. package/dist/services/server/ai/observability/instrumentation.js +240 -40
  162. package/dist/tasks/backends/bullmqBackend.d.ts +1 -0
  163. package/dist/tasks/backends/bullmqBackend.js +14 -7
  164. package/dist/tasks/store/redisTaskStore.d.ts +1 -0
  165. package/dist/tasks/store/redisTaskStore.js +34 -26
  166. package/dist/tasks/taskManager.d.ts +3 -0
  167. package/dist/tasks/taskManager.js +63 -30
  168. package/dist/telemetry/index.d.ts +2 -1
  169. package/dist/telemetry/index.js +2 -1
  170. package/dist/telemetry/telemetryService.d.ts +3 -0
  171. package/dist/telemetry/telemetryService.js +65 -5
  172. package/dist/types/cli.d.ts +10 -0
  173. package/dist/types/proxyTypes.d.ts +37 -5
  174. package/dist/types/streamTypes.d.ts +25 -3
  175. package/dist/utils/messageBuilder.js +3 -2
  176. package/dist/utils/providerHealth.d.ts +18 -0
  177. package/dist/utils/providerHealth.js +240 -9
  178. package/dist/utils/providerUtils.js +14 -8
  179. package/dist/utils/toolChoice.d.ts +4 -0
  180. package/dist/utils/toolChoice.js +6 -0
  181. package/docs/assets/dashboards/neurolink-proxy-observability-dashboard.json +6609 -0
  182. package/docs/changelog.md +252 -0
  183. package/package.json +17 -1
  184. package/scripts/observability/check-proxy-telemetry.mjs +235 -0
  185. package/scripts/observability/docker-compose.proxy-observability.yaml +55 -0
  186. package/scripts/observability/import-openobserve-dashboard.mjs +240 -0
  187. package/scripts/observability/manage-local-openobserve.sh +184 -0
  188. package/scripts/observability/otel-collector.proxy-observability.yaml +78 -0
  189. package/scripts/observability/proxy-observability.env.example +23 -0
@@ -70,15 +70,35 @@ export class TaskManager {
70
70
  store: this.store.type,
71
71
  });
72
72
  }
73
+ getStore() {
74
+ if (!this.store) {
75
+ throw TaskError.create("BACKEND_NOT_INITIALIZED", "[TaskManager] Store not initialized. Call initialize() first.");
76
+ }
77
+ return this.store;
78
+ }
79
+ getBackend() {
80
+ if (!this.backend) {
81
+ throw TaskError.create("BACKEND_NOT_INITIALIZED", "[TaskManager] Backend not initialized. Call initialize() first.");
82
+ }
83
+ return this.backend;
84
+ }
85
+ getExecutor() {
86
+ if (!this.executor) {
87
+ throw TaskError.create("BACKEND_NOT_INITIALIZED", "[TaskManager] Executor not initialized. Call initialize() first.");
88
+ }
89
+ return this.executor;
90
+ }
73
91
  // ── Public API ────────────────────────────────────────
74
92
  async create(definition) {
75
93
  if (this.config.enabled === false) {
76
94
  throw TaskError.create("TASK_DISABLED", "TaskManager is disabled. Set tasks.enabled to true in config.");
77
95
  }
78
96
  await this.ensureInitialized();
97
+ const store = this.getStore();
98
+ const backend = this.getBackend();
79
99
  // Enforce maximum task limit to prevent unbounded task creation
80
100
  const maxTasks = this.config.maxTasks ?? TASK_DEFAULTS.maxTasks;
81
- const existingTasks = await this.store.list();
101
+ const existingTasks = await store.list();
82
102
  if (existingTasks.length >= maxTasks) {
83
103
  throw TaskError.create("TASK_LIMIT_REACHED", `Task limit reached (${maxTasks}). Delete existing tasks or increase maxTasks config.`);
84
104
  }
@@ -124,7 +144,7 @@ export class TaskManager {
124
144
  task.sessionId = `session_${nanoid(12)}`;
125
145
  }
126
146
  // Save to store
127
- await this.store.save(task);
147
+ await store.save(task);
128
148
  // Register callbacks (in-memory only)
129
149
  if (definition.onSuccess || definition.onError || definition.onComplete) {
130
150
  this.callbacks.set(task.id, {
@@ -135,10 +155,10 @@ export class TaskManager {
135
155
  }
136
156
  // Schedule
137
157
  try {
138
- await this.backend.schedule(task, (t) => this.onTaskTick(t));
158
+ await backend.schedule(task, (t) => this.onTaskTick(t));
139
159
  }
140
160
  catch (err) {
141
- await this.store.delete(task.id);
161
+ await store.delete(task.id);
142
162
  throw err;
143
163
  }
144
164
  this.emit("task:created", task);
@@ -152,15 +172,17 @@ export class TaskManager {
152
172
  }
153
173
  async get(taskId) {
154
174
  await this.ensureInitialized();
155
- return this.store.get(taskId);
175
+ return this.getStore().get(taskId);
156
176
  }
157
177
  async list(filter) {
158
178
  await this.ensureInitialized();
159
- return this.store.list(filter);
179
+ return this.getStore().list(filter);
160
180
  }
161
181
  async update(taskId, updates) {
162
182
  await this.ensureInitialized();
163
- const existing = await this.store.get(taskId);
183
+ const store = this.getStore();
184
+ const backend = this.getBackend();
185
+ const existing = await store.get(taskId);
164
186
  if (!existing) {
165
187
  throw TaskError.create("TASK_NOT_FOUND", `Task not found: ${taskId}`);
166
188
  }
@@ -194,21 +216,21 @@ export class TaskManager {
194
216
  }
195
217
  else if (updates.mode !== "continuation") {
196
218
  taskUpdates.sessionId = undefined;
197
- await this.store.clearHistory(taskId);
219
+ await store.clearHistory(taskId);
198
220
  }
199
221
  }
200
- const updated = await this.store.update(taskId, taskUpdates);
222
+ const updated = await store.update(taskId, taskUpdates);
201
223
  // Re-schedule if schedule changed and task is active
202
224
  if (updates.schedule && updated.status === "active") {
203
- await this.backend.cancel(taskId);
204
- await this.backend.schedule(updated, (t) => this.onTaskTick(t));
225
+ await backend.cancel(taskId);
226
+ await backend.schedule(updated, (t) => this.onTaskTick(t));
205
227
  }
206
228
  return updated;
207
229
  }
208
230
  /** Run a task immediately (outside of its schedule) */
209
231
  async run(taskId) {
210
232
  await this.ensureInitialized();
211
- const task = await this.store.get(taskId);
233
+ const task = await this.getStore().get(taskId);
212
234
  if (!task) {
213
235
  throw TaskError.create("TASK_NOT_FOUND", `Task not found: ${taskId}`);
214
236
  }
@@ -216,42 +238,48 @@ export class TaskManager {
216
238
  }
217
239
  async pause(taskId) {
218
240
  await this.ensureInitialized();
219
- const task = await this.store.get(taskId);
241
+ const store = this.getStore();
242
+ const backend = this.getBackend();
243
+ const task = await store.get(taskId);
220
244
  if (!task) {
221
245
  throw TaskError.create("TASK_NOT_FOUND", `Task not found: ${taskId}`);
222
246
  }
223
247
  if (task.status !== "active") {
224
248
  throw TaskError.create("INVALID_TASK_STATUS", `Cannot pause task with status: ${task.status}`);
225
249
  }
226
- await this.backend.pause(taskId);
227
- const updated = await this.store.update(taskId, { status: "paused" });
250
+ await backend.pause(taskId);
251
+ const updated = await store.update(taskId, { status: "paused" });
228
252
  this.emit("task:paused", updated);
229
253
  return updated;
230
254
  }
231
255
  async resume(taskId) {
232
256
  await this.ensureInitialized();
233
- const task = await this.store.get(taskId);
257
+ const store = this.getStore();
258
+ const backend = this.getBackend();
259
+ const task = await store.get(taskId);
234
260
  if (!task) {
235
261
  throw TaskError.create("TASK_NOT_FOUND", `Task not found: ${taskId}`);
236
262
  }
237
263
  if (task.status !== "paused") {
238
264
  throw TaskError.create("INVALID_TASK_STATUS", `Cannot resume task with status: ${task.status}`);
239
265
  }
240
- const updated = await this.store.update(taskId, { status: "active" });
241
- await this.backend.schedule(updated, (t) => this.onTaskTick(t));
266
+ const updated = await store.update(taskId, { status: "active" });
267
+ await backend.schedule(updated, (t) => this.onTaskTick(t));
242
268
  this.emit("task:resumed", updated);
243
269
  return updated;
244
270
  }
245
271
  async delete(taskId) {
246
272
  await this.ensureInitialized();
247
- await this.backend.cancel(taskId);
248
- await this.store.delete(taskId);
273
+ const backend = this.getBackend();
274
+ const store = this.getStore();
275
+ await backend.cancel(taskId);
276
+ await store.delete(taskId);
249
277
  this.callbacks.delete(taskId);
250
278
  this.emit("task:deleted", taskId);
251
279
  }
252
280
  async runs(taskId, options) {
253
281
  await this.ensureInitialized();
254
- return this.store.getRuns(taskId, options);
282
+ return this.getStore().getRuns(taskId, options);
255
283
  }
256
284
  async shutdown() {
257
285
  if (this.backend) {
@@ -279,8 +307,11 @@ export class TaskManager {
279
307
  */
280
308
  async onTaskTick(task) {
281
309
  this.emit("task:started", task);
310
+ const store = this.getStore();
311
+ const backend = this.getBackend();
312
+ const executor = this.getExecutor();
282
313
  // Re-read latest task state (may have been updated/paused since scheduling)
283
- const current = await this.store.get(task.id);
314
+ const current = await store.get(task.id);
284
315
  if (!current || current.status !== "active") {
285
316
  logger.debug("[TaskManager] Skipping tick for non-active task", {
286
317
  taskId: task.id,
@@ -295,9 +326,9 @@ export class TaskManager {
295
326
  timestamp: new Date().toISOString(),
296
327
  };
297
328
  }
298
- const result = await this.executor.execute(current);
329
+ const result = await executor.execute(current);
299
330
  // Log the run
300
- await this.store.appendRun(task.id, result);
331
+ await store.appendRun(task.id, result);
301
332
  // Update task tracking
302
333
  const updates = {
303
334
  runCount: current.runCount + 1,
@@ -306,18 +337,18 @@ export class TaskManager {
306
337
  // Check if task should complete
307
338
  if (current.maxRuns && current.runCount + 1 >= current.maxRuns) {
308
339
  updates.status = "completed";
309
- await this.backend.cancel(task.id);
340
+ await backend.cancel(task.id);
310
341
  }
311
342
  // Mark successful once tasks as completed
312
343
  if (result.status === "success" && current.schedule.type === "once") {
313
344
  updates.status = "completed";
314
- await this.backend.cancel(task.id);
345
+ await backend.cancel(task.id);
315
346
  }
316
347
  // Mark as failed on permanent error
317
348
  if (result.status === "error" && current.schedule.type === "once") {
318
349
  updates.status = "failed";
319
350
  }
320
- await this.store.update(task.id, updates);
351
+ await store.update(task.id, updates);
321
352
  // Fire callbacks
322
353
  const cbs = this.callbacks.get(task.id);
323
354
  if (cbs) {
@@ -337,7 +368,7 @@ export class TaskManager {
337
368
  });
338
369
  }
339
370
  if (updates.status === "completed" || updates.status === "failed") {
340
- const finalTask = await this.store.get(task.id);
371
+ const finalTask = await store.get(task.id);
341
372
  if (finalTask && cbs.onComplete) {
342
373
  await cbs.onComplete(finalTask);
343
374
  }
@@ -364,10 +395,12 @@ export class TaskManager {
364
395
  * Called on initialization to handle process restarts.
365
396
  */
366
397
  async rescheduleActiveTasks() {
367
- const activeTasks = await this.store.list({ status: "active" });
398
+ const store = this.getStore();
399
+ const backend = this.getBackend();
400
+ const activeTasks = await store.list({ status: "active" });
368
401
  for (const task of activeTasks) {
369
402
  try {
370
- await this.backend.schedule(task, (t) => this.onTaskTick(t));
403
+ await backend.schedule(task, (t) => this.onTaskTick(t));
371
404
  logger.debug("[TaskManager] Re-scheduled task", {
372
405
  taskId: task.id,
373
406
  name: task.name,
@@ -4,7 +4,8 @@ export { withSpan, withClientSpan, type SpanOptions } from "./withSpan.js";
4
4
  export { ATTR } from "./attributes.js";
5
5
  /**
6
6
  * Initialize telemetry for NeuroLink
7
- * OPTIONAL - Only works when NEUROLINK_TELEMETRY_ENABLED=true
7
+ * Reuses an existing global TracerProvider when one is already registered,
8
+ * otherwise bootstraps Neurolink telemetry when an exporter endpoint is configured.
8
9
  */
9
10
  export declare function initializeTelemetry(): Promise<import("./telemetryService.js").TelemetryService>;
10
11
  /**
@@ -6,7 +6,8 @@ export { ATTR } from "./attributes.js";
6
6
  import { logger } from "../utils/logger.js";
7
7
  /**
8
8
  * Initialize telemetry for NeuroLink
9
- * OPTIONAL - Only works when NEUROLINK_TELEMETRY_ENABLED=true
9
+ * Reuses an existing global TracerProvider when one is already registered,
10
+ * otherwise bootstraps Neurolink telemetry when an exporter endpoint is configured.
10
11
  */
11
12
  export async function initializeTelemetry() {
12
13
  const { TelemetryService } = await import("./telemetryService.js");
@@ -11,6 +11,7 @@ export declare class TelemetryService {
11
11
  private tracerProvider?;
12
12
  private enabled;
13
13
  private initialized;
14
+ private usingExternalTracerProvider;
14
15
  private meter?;
15
16
  private tracer?;
16
17
  private aiRequestCounter?;
@@ -29,6 +30,8 @@ export declare class TelemetryService {
29
30
  private constructor();
30
31
  static getInstance(): TelemetryService;
31
32
  private isTelemetryEnabled;
33
+ private hasExternalTracerProvider;
34
+ private adoptExternalTracerProvider;
32
35
  private initializeTelemetry;
33
36
  private initializeMetrics;
34
37
  initialize(): Promise<void>;
@@ -9,6 +9,7 @@ export class TelemetryService {
9
9
  tracerProvider;
10
10
  enabled = false;
11
11
  initialized = false;
12
+ usingExternalTracerProvider = false;
12
13
  meter;
13
14
  tracer;
14
15
  // Optional Metrics (only created when enabled)
@@ -43,11 +44,48 @@ export class TelemetryService {
43
44
  return TelemetryService.instance;
44
45
  }
45
46
  isTelemetryEnabled() {
46
- return (process.env.NEUROLINK_TELEMETRY_ENABLED === "true" ||
47
+ return (this.hasExternalTracerProvider() ||
48
+ process.env.NEUROLINK_TELEMETRY_ENABLED === "true" ||
47
49
  process.env.OTEL_EXPORTER_OTLP_ENDPOINT !== undefined);
48
50
  }
51
+ hasExternalTracerProvider() {
52
+ try {
53
+ const provider = trace.getTracerProvider();
54
+ if (!provider) {
55
+ return false;
56
+ }
57
+ const delegateName = provider._delegate?.constructor?.name || "";
58
+ if (delegateName && delegateName !== "NoopTracerProvider") {
59
+ return true;
60
+ }
61
+ const providerName = provider.constructor?.name || "";
62
+ return (providerName !== "ProxyTracerProvider" &&
63
+ providerName !== "NoopTracerProvider");
64
+ }
65
+ catch (error) {
66
+ logger.warn("[Telemetry] Failed checking for external TracerProvider", {
67
+ error: error instanceof Error ? error.message : String(error),
68
+ });
69
+ return false;
70
+ }
71
+ }
72
+ adoptExternalTracerProvider(reason) {
73
+ this.usingExternalTracerProvider = true;
74
+ this.tracerProvider = undefined;
75
+ this.meter = metrics.getMeter("neurolink-ai");
76
+ this.tracer = trace.getTracer("neurolink-ai");
77
+ this.initializeMetrics();
78
+ logger.debug("[Telemetry] Reusing externally managed TracerProvider", {
79
+ reason,
80
+ endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
81
+ });
82
+ }
49
83
  initializeTelemetry() {
50
84
  try {
85
+ if (this.hasExternalTracerProvider()) {
86
+ this.adoptExternalTracerProvider("global tracer provider already registered");
87
+ return;
88
+ }
51
89
  const resource = resourceFromAttributes({
52
90
  [ATTR_SERVICE_NAME]: process.env.OTEL_SERVICE_NAME || "neurolink-ai",
53
91
  [ATTR_SERVICE_VERSION]: process.env.OTEL_SERVICE_VERSION || "3.0.1",
@@ -61,13 +99,23 @@ export class TelemetryService {
61
99
  resource,
62
100
  spanProcessors: [new BatchSpanProcessor(exporter)],
63
101
  });
64
- trace.setGlobalTracerProvider(this.tracerProvider);
65
102
  this.meter = metrics.getMeter("neurolink-ai");
66
- this.tracer = trace.getTracer("neurolink-ai");
103
+ this.tracer = this.tracerProvider.getTracer("neurolink-ai");
67
104
  this.initializeMetrics();
68
- logger.debug("[Telemetry] Initialized with endpoint:", process.env.OTEL_EXPORTER_OTLP_ENDPOINT);
105
+ logger.debug("[Telemetry] Initialized local telemetry exporter", {
106
+ endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
107
+ globalTracerProviderOwnedBy: "observability/instrumentation",
108
+ });
69
109
  }
70
110
  catch (error) {
111
+ const errorMessage = error instanceof Error ? error.message : String(error);
112
+ const isDuplicateRegistration = errorMessage.includes("duplicate registration") ||
113
+ errorMessage.includes("already registered") ||
114
+ errorMessage.includes("already set");
115
+ if (isDuplicateRegistration && this.hasExternalTracerProvider()) {
116
+ this.adoptExternalTracerProvider("duplicate global tracer registration detected");
117
+ return;
118
+ }
71
119
  logger.error("[Telemetry] Failed to initialize:", error);
72
120
  this.enabled = false;
73
121
  }
@@ -105,6 +153,16 @@ export class TelemetryService {
105
153
  if (!this.enabled) {
106
154
  return;
107
155
  }
156
+ if (this.usingExternalTracerProvider) {
157
+ this.initialized = true;
158
+ logger.debug("[Telemetry] External TracerProvider already initialized by host");
159
+ return;
160
+ }
161
+ if (!this.tracerProvider) {
162
+ this.initialized = true;
163
+ logger.debug("[Telemetry] Tracer provider already prepared during constructor");
164
+ return;
165
+ }
108
166
  try {
109
167
  // Register AsyncLocalStorage context manager for proper parent-child
110
168
  // span relationships across async boundaries (required for startActiveSpan)
@@ -308,7 +366,9 @@ export class TelemetryService {
308
366
  }
309
367
  // Cleanup
310
368
  async shutdown() {
311
- if (this.enabled && this.tracerProvider) {
369
+ if (this.enabled &&
370
+ this.tracerProvider &&
371
+ !this.usingExternalTracerProvider) {
312
372
  try {
313
373
  await this.tracerProvider.shutdown();
314
374
  this.initialized = false;
@@ -763,6 +763,8 @@ export type ProxyStartArgs = {
763
763
  quiet?: boolean;
764
764
  debug?: boolean;
765
765
  config?: string;
766
+ envFile?: string;
767
+ passthrough?: boolean;
766
768
  };
767
769
  /** Arguments accepted by `neurolink proxy status` */
768
770
  export type ProxyStatusArgs = {
@@ -779,6 +781,11 @@ export type ProxyGuardArgs = {
779
781
  pollIntervalMs?: number;
780
782
  quiet?: boolean;
781
783
  };
784
+ /** Arguments accepted by `neurolink proxy telemetry <subcommand>` */
785
+ export type ProxyTelemetryArgs = {
786
+ action?: "setup" | "start" | "stop" | "status" | "logs" | "import-dashboard";
787
+ quiet?: boolean;
788
+ };
782
789
  /** A fallback chain entry (serialisable subset of FallbackEntry) */
783
790
  export type FallbackInfo = {
784
791
  provider: string;
@@ -791,12 +798,15 @@ export type ProxyState = {
791
798
  host: string;
792
799
  strategy: string;
793
800
  startTime: string;
801
+ envFile?: string;
794
802
  /** Fallback chain from proxy config (persisted at start time) */
795
803
  fallbackChain?: FallbackInfo[];
796
804
  /** Optional fail-open guard PID that reverts Claude settings if proxy dies */
797
805
  guardPid?: number;
798
806
  /** How the proxy was launched — "launchd" if installed as service, "manual" otherwise */
799
807
  managedBy?: "launchd" | "manual";
808
+ /** Whether the proxy is running in transparent passthrough mode */
809
+ passthrough?: boolean;
800
810
  };
801
811
  /** Stored credentials for an authenticated provider. */
802
812
  export type StoredCredentials = {
@@ -239,10 +239,11 @@ export type ParsedClaudeRequest = {
239
239
  role: string;
240
240
  content: string;
241
241
  }>;
242
- /** Tools in NeuroLink-compatible shape (name -> { description, parameters }). */
242
+ /** Tools translated to AI SDK-compatible shape for provider fallback. */
243
243
  tools: Record<string, {
244
- description: string;
245
- parameters: Record<string, unknown>;
244
+ description?: string;
245
+ inputSchema?: unknown;
246
+ execute?: (...args: unknown[]) => unknown;
246
247
  }>;
247
248
  /**
248
249
  * Tool choice mapping from Claude format.
@@ -421,21 +422,52 @@ export type RequestLogEntry = {
421
422
  errorMessage?: string;
422
423
  inputTokens?: number;
423
424
  outputTokens?: number;
425
+ cacheCreationTokens?: number;
426
+ cacheReadTokens?: number;
427
+ /** OTel trace ID for correlation with distributed traces */
428
+ traceId?: string;
429
+ /** OTel span ID for correlation with distributed traces */
430
+ spanId?: string;
431
+ };
432
+ export type RequestAttemptLogEntry = {
433
+ timestamp: string;
434
+ requestId: string;
435
+ attempt: number;
436
+ method: string;
437
+ path: string;
438
+ model: string;
439
+ stream: boolean;
440
+ toolCount: number;
441
+ account: string;
442
+ accountType: string;
443
+ responseStatus: number;
444
+ responseTimeMs: number;
445
+ errorType?: string;
446
+ errorMessage?: string;
447
+ inputTokens?: number;
448
+ outputTokens?: number;
449
+ cacheCreationTokens?: number;
450
+ cacheReadTokens?: number;
451
+ /** OTel trace ID for correlation with distributed traces */
452
+ traceId?: string;
453
+ /** OTel span ID for correlation with distributed traces */
454
+ spanId?: string;
424
455
  };
425
456
  export type AccountStats = {
426
457
  label: string;
427
458
  type: string;
428
- requestCount: number;
459
+ attemptCount: number;
429
460
  successCount: number;
430
461
  errorCount: number;
431
462
  rateLimitCount: number;
432
- lastRequestAt: number;
463
+ lastAttemptAt: number;
433
464
  lastErrorAt?: number;
434
465
  currentBackoffLevel: number;
435
466
  coolingUntil?: number;
436
467
  };
437
468
  export type ProxyStats = {
438
469
  startedAt: number;
470
+ totalAttempts: number;
439
471
  totalRequests: number;
440
472
  totalSuccess: number;
441
473
  totalErrors: number;
@@ -1,16 +1,16 @@
1
- import type { Tool } from "ai";
1
+ import type { LanguageModel, StepResult, Tool, ToolChoice } from "ai";
2
2
  import type { AIProviderName } from "../constants/enums.js";
3
3
  import type { EvaluationData } from "../index.js";
4
4
  import type { RAGConfig } from "../rag/types.js";
5
5
  import type { AnalyticsData, ToolExecutionEvent, ToolExecutionSummary } from "../types/index.js";
6
- import type { MiddlewareFactoryOptions, OnFinishCallback, OnErrorCallback, OnChunkCallback } from "../types/middlewareTypes.js";
6
+ import type { MiddlewareFactoryOptions, OnChunkCallback, OnErrorCallback, OnFinishCallback } from "../types/middlewareTypes.js";
7
7
  import type { TokenUsage } from "./analytics.js";
8
8
  import type { JsonValue, UnknownRecord } from "./common.js";
9
9
  import type { Content, ImageWithAltText } from "./content.js";
10
10
  import type { ChatMessage } from "./conversation.js";
11
+ import type { AdditionalMemoryUser } from "./generateTypes.js";
11
12
  import type { AIModelProviderConfig } from "./providers.js";
12
13
  import type { TTSChunk, TTSOptions } from "./ttsTypes.js";
13
- import type { AdditionalMemoryUser } from "./generateTypes.js";
14
14
  import type { StandardRecord, ValidationSchema } from "./typeAliases.js";
15
15
  /**
16
16
  * Progress tracking and metadata for streaming operations
@@ -326,12 +326,34 @@ export type StreamOptions = {
326
326
  abortSignal?: AbortSignal;
327
327
  disableTools?: boolean;
328
328
  maxSteps?: number;
329
+ /**
330
+ * Tool choice configuration for streaming generation.
331
+ * Mirrors generate() so translated/fallback requests can preserve forced tool use.
332
+ */
333
+ toolChoice?: ToolChoice<Record<string, Tool>>;
334
+ /**
335
+ * Optional callback that runs before each stream step in a multi-step generation.
336
+ */
337
+ prepareStep?: (options: {
338
+ steps: StepResult<Record<string, Tool>>[];
339
+ stepNumber: number;
340
+ maxSteps: number;
341
+ model: LanguageModel;
342
+ }) => PromiseLike<{
343
+ toolChoice?: ToolChoice<Record<string, Tool>>;
344
+ activeTools?: Record<string, Tool>;
345
+ } | undefined>;
329
346
  /** Include only these tools by name (whitelist). If set, only matching tools are available. */
330
347
  toolFilter?: string[];
331
348
  /** Exclude these tools by name (blacklist). Applied after toolFilter. */
332
349
  excludeTools?: string[];
333
350
  /** Disable tool result caching for this request (overrides global mcp.cache.enabled) */
334
351
  disableToolCache?: boolean;
352
+ /**
353
+ * Disable NeuroLink's internal provider fallback for this request.
354
+ * Used by the Claude proxy so the proxy itself can own fallback order.
355
+ */
356
+ disableInternalFallback?: boolean;
335
357
  /**
336
358
  * Skip injecting tool schemas into the system prompt.
337
359
  * When true, tools are ONLY passed natively via the provider's `tools` parameter,
@@ -775,6 +775,7 @@ async function processUnifiedFilesArray(options, maxSize, provider) {
775
775
  return;
776
776
  }
777
777
  const totalFiles = options.input.files.length;
778
+ const files = options.input.files;
778
779
  return withSpan({
779
780
  name: "neurolink.file.process_all",
780
781
  tracer: tracers.file,
@@ -787,8 +788,8 @@ async function processUnifiedFilesArray(options, maxSize, provider) {
787
788
  options.input.text = options.input.text || "";
788
789
  let includedCount = 0;
789
790
  const fileRegistry = options.fileRegistry;
790
- for (let fileIdx = 0; fileIdx < options.input.files.length; fileIdx++) {
791
- const file = options.input.files[fileIdx];
791
+ for (let fileIdx = 0; fileIdx < files.length; fileIdx++) {
792
+ const file = files[fileIdx];
792
793
  const filename = extractFilename(file, fileIdx);
793
794
  try {
794
795
  // ─── Lazy file registration path ──────────────────────────────
@@ -98,6 +98,20 @@ export declare class ProviderHealthChecker {
98
98
  * Check Azure OpenAI configuration
99
99
  */
100
100
  private static checkAzureConfig;
101
+ private static getLiteLLMBaseUrl;
102
+ private static getLiteLLMModelsUrl;
103
+ private static getConfiguredLiteLLMModel;
104
+ private static getOllamaBaseUrl;
105
+ private static getOllamaTagsUrl;
106
+ private static getConfiguredOllamaModel;
107
+ private static fetchJsonWithTimeout;
108
+ private static normalizeModelList;
109
+ private static hasRequestedModel;
110
+ private static getOllamaAvailableModels;
111
+ private static getLiteLLMAvailableModels;
112
+ private static checkOllamaAvailability;
113
+ private static checkLiteLLMAvailability;
114
+ private static checkLiteLLMConfig;
101
115
  /**
102
116
  * Check Ollama configuration
103
117
  */
@@ -169,6 +183,10 @@ export declare class ProviderHealthChecker {
169
183
  * Clear health cache for a provider or all providers
170
184
  */
171
185
  static clearHealthCache(providerName?: AIProviderName): void;
186
+ static checkFallbackProviderAvailability(providerName: string, model: string): Promise<{
187
+ available: boolean;
188
+ reason?: string;
189
+ }>;
172
190
  /**
173
191
  * Get the best healthy provider from a list of options (NON-BLOCKING)
174
192
  * Prioritizes healthy providers over configured but unhealthy ones