@juspay/neurolink 9.41.0 → 9.42.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 (212) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +7 -1
  3. package/dist/auth/anthropicOAuth.d.ts +18 -3
  4. package/dist/auth/anthropicOAuth.js +149 -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 +354 -334
  11. package/dist/cli/commands/mcp.d.ts +6 -0
  12. package/dist/cli/commands/mcp.js +188 -181
  13. package/dist/cli/commands/proxy.d.ts +2 -1
  14. package/dist/cli/commands/proxy.js +713 -431
  15. package/dist/cli/commands/task.js +3 -0
  16. package/dist/cli/factories/commandFactory.d.ts +2 -0
  17. package/dist/cli/factories/commandFactory.js +38 -0
  18. package/dist/cli/parser.js +4 -3
  19. package/dist/client/aiSdkAdapter.js +3 -0
  20. package/dist/client/streamingClient.js +30 -10
  21. package/dist/core/baseProvider.d.ts +6 -1
  22. package/dist/core/baseProvider.js +208 -230
  23. package/dist/core/factory.d.ts +3 -0
  24. package/dist/core/factory.js +138 -188
  25. package/dist/core/modules/GenerationHandler.js +3 -2
  26. package/dist/core/redisConversationMemoryManager.js +7 -3
  27. package/dist/evaluation/BatchEvaluator.js +4 -1
  28. package/dist/evaluation/hooks/observabilityHooks.js +5 -3
  29. package/dist/evaluation/pipeline/evaluationPipeline.d.ts +3 -2
  30. package/dist/evaluation/pipeline/evaluationPipeline.js +24 -9
  31. package/dist/evaluation/pipeline/strategies/batchStrategy.js +6 -3
  32. package/dist/evaluation/pipeline/strategies/samplingStrategy.js +18 -10
  33. package/dist/evaluation/scorers/scorerRegistry.d.ts +3 -0
  34. package/dist/evaluation/scorers/scorerRegistry.js +353 -282
  35. package/dist/lib/auth/anthropicOAuth.d.ts +18 -3
  36. package/dist/lib/auth/anthropicOAuth.js +149 -4
  37. package/dist/lib/auth/providers/firebase.js +5 -1
  38. package/dist/lib/auth/providers/jwt.js +5 -1
  39. package/dist/lib/auth/providers/workos.js +5 -1
  40. package/dist/lib/auth/sessionManager.d.ts +1 -1
  41. package/dist/lib/auth/sessionManager.js +58 -27
  42. package/dist/lib/client/aiSdkAdapter.js +3 -0
  43. package/dist/lib/client/streamingClient.js +30 -10
  44. package/dist/lib/core/baseProvider.d.ts +6 -1
  45. package/dist/lib/core/baseProvider.js +208 -230
  46. package/dist/lib/core/factory.d.ts +3 -0
  47. package/dist/lib/core/factory.js +138 -188
  48. package/dist/lib/core/modules/GenerationHandler.js +3 -2
  49. package/dist/lib/core/redisConversationMemoryManager.js +7 -3
  50. package/dist/lib/evaluation/BatchEvaluator.js +4 -1
  51. package/dist/lib/evaluation/hooks/observabilityHooks.js +5 -3
  52. package/dist/lib/evaluation/pipeline/evaluationPipeline.d.ts +3 -2
  53. package/dist/lib/evaluation/pipeline/evaluationPipeline.js +24 -9
  54. package/dist/lib/evaluation/pipeline/strategies/batchStrategy.js +6 -3
  55. package/dist/lib/evaluation/pipeline/strategies/samplingStrategy.js +18 -10
  56. package/dist/lib/evaluation/scorers/scorerRegistry.d.ts +3 -0
  57. package/dist/lib/evaluation/scorers/scorerRegistry.js +353 -282
  58. package/dist/lib/mcp/toolRegistry.d.ts +2 -0
  59. package/dist/lib/mcp/toolRegistry.js +32 -31
  60. package/dist/lib/neurolink.d.ts +41 -2
  61. package/dist/lib/neurolink.js +1616 -1681
  62. package/dist/lib/observability/otelBridge.d.ts +2 -2
  63. package/dist/lib/observability/otelBridge.js +12 -3
  64. package/dist/lib/providers/amazonBedrock.js +2 -4
  65. package/dist/lib/providers/anthropic.d.ts +9 -5
  66. package/dist/lib/providers/anthropic.js +19 -14
  67. package/dist/lib/providers/anthropicBaseProvider.d.ts +3 -3
  68. package/dist/lib/providers/anthropicBaseProvider.js +5 -4
  69. package/dist/lib/providers/azureOpenai.d.ts +1 -1
  70. package/dist/lib/providers/azureOpenai.js +5 -4
  71. package/dist/lib/providers/googleAiStudio.js +30 -6
  72. package/dist/lib/providers/googleVertex.d.ts +10 -0
  73. package/dist/lib/providers/googleVertex.js +437 -423
  74. package/dist/lib/providers/huggingFace.d.ts +3 -3
  75. package/dist/lib/providers/huggingFace.js +6 -8
  76. package/dist/lib/providers/litellm.d.ts +1 -0
  77. package/dist/lib/providers/litellm.js +76 -55
  78. package/dist/lib/providers/mistral.js +2 -1
  79. package/dist/lib/providers/ollama.js +93 -23
  80. package/dist/lib/providers/openAI.d.ts +2 -0
  81. package/dist/lib/providers/openAI.js +141 -141
  82. package/dist/lib/providers/openRouter.js +2 -1
  83. package/dist/lib/providers/openaiCompatible.d.ts +4 -4
  84. package/dist/lib/providers/openaiCompatible.js +4 -4
  85. package/dist/lib/proxy/claudeFormat.d.ts +3 -2
  86. package/dist/lib/proxy/claudeFormat.js +27 -14
  87. package/dist/lib/proxy/cloaking/plugins/sessionIdentity.d.ts +2 -6
  88. package/dist/lib/proxy/cloaking/plugins/sessionIdentity.js +9 -33
  89. package/dist/lib/proxy/modelRouter.js +3 -0
  90. package/dist/lib/proxy/oauthFetch.d.ts +1 -1
  91. package/dist/lib/proxy/oauthFetch.js +289 -316
  92. package/dist/lib/proxy/proxyConfig.js +46 -24
  93. package/dist/lib/proxy/proxyEnv.d.ts +19 -0
  94. package/dist/lib/proxy/proxyEnv.js +73 -0
  95. package/dist/lib/proxy/proxyFetch.js +291 -217
  96. package/dist/lib/proxy/proxyTracer.d.ts +133 -0
  97. package/dist/lib/proxy/proxyTracer.js +645 -0
  98. package/dist/lib/proxy/rawStreamCapture.d.ts +10 -0
  99. package/dist/lib/proxy/rawStreamCapture.js +83 -0
  100. package/dist/lib/proxy/requestLogger.d.ts +32 -5
  101. package/dist/lib/proxy/requestLogger.js +503 -47
  102. package/dist/lib/proxy/sseInterceptor.d.ts +97 -0
  103. package/dist/lib/proxy/sseInterceptor.js +427 -0
  104. package/dist/lib/proxy/usageStats.d.ts +4 -3
  105. package/dist/lib/proxy/usageStats.js +25 -12
  106. package/dist/lib/rag/chunkers/MarkdownChunker.js +13 -5
  107. package/dist/lib/rag/chunking/markdownChunker.js +15 -6
  108. package/dist/lib/server/routes/claudeProxyRoutes.d.ts +17 -3
  109. package/dist/lib/server/routes/claudeProxyRoutes.js +3032 -1349
  110. package/dist/lib/services/server/ai/observability/instrumentation.d.ts +7 -1
  111. package/dist/lib/services/server/ai/observability/instrumentation.js +337 -161
  112. package/dist/lib/tasks/backends/bullmqBackend.d.ts +1 -0
  113. package/dist/lib/tasks/backends/bullmqBackend.js +35 -22
  114. package/dist/lib/tasks/store/redisTaskStore.d.ts +1 -0
  115. package/dist/lib/tasks/store/redisTaskStore.js +54 -39
  116. package/dist/lib/tasks/taskManager.d.ts +5 -0
  117. package/dist/lib/tasks/taskManager.js +158 -30
  118. package/dist/lib/telemetry/index.d.ts +2 -1
  119. package/dist/lib/telemetry/index.js +2 -1
  120. package/dist/lib/telemetry/telemetryService.d.ts +3 -0
  121. package/dist/lib/telemetry/telemetryService.js +69 -5
  122. package/dist/lib/types/cli.d.ts +10 -0
  123. package/dist/lib/types/proxyTypes.d.ts +160 -5
  124. package/dist/lib/types/streamTypes.d.ts +25 -3
  125. package/dist/lib/utils/messageBuilder.js +3 -2
  126. package/dist/lib/utils/providerHealth.d.ts +19 -0
  127. package/dist/lib/utils/providerHealth.js +279 -33
  128. package/dist/lib/utils/providerUtils.js +17 -22
  129. package/dist/lib/utils/toolChoice.d.ts +4 -0
  130. package/dist/lib/utils/toolChoice.js +7 -0
  131. package/dist/mcp/toolRegistry.d.ts +2 -0
  132. package/dist/mcp/toolRegistry.js +32 -31
  133. package/dist/neurolink.d.ts +41 -2
  134. package/dist/neurolink.js +1616 -1681
  135. package/dist/observability/otelBridge.d.ts +2 -2
  136. package/dist/observability/otelBridge.js +12 -3
  137. package/dist/providers/amazonBedrock.js +2 -4
  138. package/dist/providers/anthropic.d.ts +9 -5
  139. package/dist/providers/anthropic.js +19 -14
  140. package/dist/providers/anthropicBaseProvider.d.ts +3 -3
  141. package/dist/providers/anthropicBaseProvider.js +5 -4
  142. package/dist/providers/azureOpenai.d.ts +1 -1
  143. package/dist/providers/azureOpenai.js +5 -4
  144. package/dist/providers/googleAiStudio.js +30 -6
  145. package/dist/providers/googleVertex.d.ts +10 -0
  146. package/dist/providers/googleVertex.js +437 -423
  147. package/dist/providers/huggingFace.d.ts +3 -3
  148. package/dist/providers/huggingFace.js +6 -7
  149. package/dist/providers/litellm.d.ts +1 -0
  150. package/dist/providers/litellm.js +76 -55
  151. package/dist/providers/mistral.js +2 -1
  152. package/dist/providers/ollama.js +93 -23
  153. package/dist/providers/openAI.d.ts +2 -0
  154. package/dist/providers/openAI.js +141 -141
  155. package/dist/providers/openRouter.js +2 -1
  156. package/dist/providers/openaiCompatible.d.ts +4 -4
  157. package/dist/providers/openaiCompatible.js +4 -3
  158. package/dist/proxy/claudeFormat.d.ts +3 -2
  159. package/dist/proxy/claudeFormat.js +27 -14
  160. package/dist/proxy/cloaking/plugins/sessionIdentity.d.ts +2 -6
  161. package/dist/proxy/cloaking/plugins/sessionIdentity.js +9 -33
  162. package/dist/proxy/modelRouter.js +3 -0
  163. package/dist/proxy/oauthFetch.d.ts +1 -1
  164. package/dist/proxy/oauthFetch.js +289 -316
  165. package/dist/proxy/proxyConfig.js +46 -24
  166. package/dist/proxy/proxyEnv.d.ts +19 -0
  167. package/dist/proxy/proxyEnv.js +72 -0
  168. package/dist/proxy/proxyFetch.js +291 -217
  169. package/dist/proxy/proxyTracer.d.ts +133 -0
  170. package/dist/proxy/proxyTracer.js +644 -0
  171. package/dist/proxy/rawStreamCapture.d.ts +10 -0
  172. package/dist/proxy/rawStreamCapture.js +82 -0
  173. package/dist/proxy/requestLogger.d.ts +32 -5
  174. package/dist/proxy/requestLogger.js +503 -47
  175. package/dist/proxy/sseInterceptor.d.ts +97 -0
  176. package/dist/proxy/sseInterceptor.js +426 -0
  177. package/dist/proxy/usageStats.d.ts +4 -3
  178. package/dist/proxy/usageStats.js +25 -12
  179. package/dist/rag/chunkers/MarkdownChunker.js +13 -5
  180. package/dist/rag/chunking/markdownChunker.js +15 -6
  181. package/dist/server/routes/claudeProxyRoutes.d.ts +17 -3
  182. package/dist/server/routes/claudeProxyRoutes.js +3032 -1349
  183. package/dist/services/server/ai/observability/instrumentation.d.ts +7 -1
  184. package/dist/services/server/ai/observability/instrumentation.js +337 -161
  185. package/dist/tasks/backends/bullmqBackend.d.ts +1 -0
  186. package/dist/tasks/backends/bullmqBackend.js +35 -22
  187. package/dist/tasks/store/redisTaskStore.d.ts +1 -0
  188. package/dist/tasks/store/redisTaskStore.js +54 -39
  189. package/dist/tasks/taskManager.d.ts +5 -0
  190. package/dist/tasks/taskManager.js +158 -30
  191. package/dist/telemetry/index.d.ts +2 -1
  192. package/dist/telemetry/index.js +2 -1
  193. package/dist/telemetry/telemetryService.d.ts +3 -0
  194. package/dist/telemetry/telemetryService.js +69 -5
  195. package/dist/types/cli.d.ts +10 -0
  196. package/dist/types/proxyTypes.d.ts +160 -5
  197. package/dist/types/streamTypes.d.ts +25 -3
  198. package/dist/utils/messageBuilder.js +3 -2
  199. package/dist/utils/providerHealth.d.ts +19 -0
  200. package/dist/utils/providerHealth.js +279 -33
  201. package/dist/utils/providerUtils.js +18 -22
  202. package/dist/utils/toolChoice.d.ts +4 -0
  203. package/dist/utils/toolChoice.js +6 -0
  204. package/docs/assets/dashboards/neurolink-proxy-observability-dashboard.json +6609 -0
  205. package/docs/changelog.md +252 -0
  206. package/package.json +19 -2
  207. package/scripts/observability/check-proxy-telemetry.mjs +235 -0
  208. package/scripts/observability/docker-compose.proxy-observability.yaml +55 -0
  209. package/scripts/observability/import-openobserve-dashboard.mjs +240 -0
  210. package/scripts/observability/manage-local-openobserve.sh +215 -0
  211. package/scripts/observability/otel-collector.proxy-observability.yaml +78 -0
  212. package/scripts/observability/proxy-observability.env.example +23 -0
@@ -72,7 +72,7 @@ export class ProviderHealthChecker {
72
72
  };
73
73
  try {
74
74
  // 1. Check environment configuration
75
- await this.checkEnvironmentConfiguration(providerName, healthStatus);
75
+ await this.checkEnvironmentConfiguration(providerName, healthStatus, timeout);
76
76
  // 2. Check API key validity (basic format validation)
77
77
  await this.checkApiKeyValidity(providerName, healthStatus);
78
78
  // 3. Optional: Connectivity test
@@ -129,7 +129,7 @@ export class ProviderHealthChecker {
129
129
  /**
130
130
  * Check environment configuration for a provider
131
131
  */
132
- static async checkEnvironmentConfiguration(providerName, healthStatus) {
132
+ static async checkEnvironmentConfiguration(providerName, healthStatus, timeout) {
133
133
  const requiredEnvVars = this.getRequiredEnvironmentVariables(providerName);
134
134
  logger.debug(`[ProviderHealthChecker] Checking environment configuration for ${providerName}`, {
135
135
  requiredEnvVars,
@@ -160,7 +160,7 @@ export class ProviderHealthChecker {
160
160
  healthStatus.recommendations.push(`Set the following environment variables: ${missingVars.join(", ")}`);
161
161
  }
162
162
  // Provider-specific configuration checks
163
- await this.checkProviderSpecificConfig(providerName, healthStatus);
163
+ await this.checkProviderSpecificConfig(providerName, healthStatus, timeout);
164
164
  }
165
165
  /**
166
166
  * Check API key validity (format validation)
@@ -228,7 +228,8 @@ export class ProviderHealthChecker {
228
228
  }
229
229
  // Providers that don't use API keys directly
230
230
  if (providerName === AIProviderName.OLLAMA ||
231
- providerName === AIProviderName.BEDROCK) {
231
+ providerName === AIProviderName.BEDROCK ||
232
+ providerName === AIProviderName.LITELLM) {
232
233
  healthStatus.hasApiKey = true;
233
234
  return;
234
235
  }
@@ -260,30 +261,34 @@ export class ProviderHealthChecker {
260
261
  healthStatus.warning = "No connectivity test available for this provider";
261
262
  return;
262
263
  }
264
+ const headers = {
265
+ "User-Agent": "NeuroLink-HealthCheck/1.0",
266
+ ...this.getConnectivityHeaders(providerName),
267
+ };
263
268
  try {
264
269
  const controller = new AbortController();
265
270
  const timeoutId = setTimeout(() => controller.abort(), timeout);
266
- const proxyFetch = createProxyFetch();
267
- let response = await proxyFetch(endpoint, {
268
- method: "HEAD",
269
- signal: controller.signal,
270
- headers: {
271
- "User-Agent": "NeuroLink-HealthCheck/1.0",
272
- },
273
- });
274
- // Fallback to GET if HEAD returns 405 (Method Not Allowed) for restrictive gateways
275
- if (response.status === 405) {
276
- response = await proxyFetch(endpoint, {
277
- method: "GET",
271
+ try {
272
+ const proxyFetch = createProxyFetch();
273
+ let response = await proxyFetch(endpoint, {
274
+ method: "HEAD",
278
275
  signal: controller.signal,
279
- headers: {
280
- "User-Agent": "NeuroLink-HealthCheck/1.0",
281
- },
276
+ headers,
282
277
  });
278
+ // Fallback to GET if HEAD returns 405 (Method Not Allowed) for restrictive gateways
279
+ if (response.status === 405) {
280
+ response = await proxyFetch(endpoint, {
281
+ method: "GET",
282
+ signal: controller.signal,
283
+ headers,
284
+ });
285
+ }
286
+ if (!response.ok) {
287
+ healthStatus.configurationIssues.push(`Connectivity test failed: HTTP ${response.status}`);
288
+ }
283
289
  }
284
- clearTimeout(timeoutId);
285
- if (!response.ok) {
286
- healthStatus.configurationIssues.push(`Connectivity test failed: HTTP ${response.status}`);
290
+ finally {
291
+ clearTimeout(timeoutId);
287
292
  }
288
293
  }
289
294
  catch (error) {
@@ -319,6 +324,14 @@ export class ProviderHealthChecker {
319
324
  }
320
325
  }
321
326
  }
327
+ static getConnectivityHeaders(providerName) {
328
+ if (providerName === AIProviderName.LITELLM) {
329
+ return {
330
+ Authorization: `Bearer ${process.env.LITELLM_API_KEY || "sk-anything"}`,
331
+ };
332
+ }
333
+ return {};
334
+ }
322
335
  /**
323
336
  * Check model availability (if possible without making API calls)
324
337
  */
@@ -368,6 +381,8 @@ export class ProviderHealthChecker {
368
381
  return [];
369
382
  case AIProviderName.AZURE:
370
383
  return ["AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT"];
384
+ case AIProviderName.LITELLM:
385
+ return [];
371
386
  case AIProviderName.OLLAMA:
372
387
  return []; // Ollama typically doesn't require API keys
373
388
  default:
@@ -391,8 +406,10 @@ export class ProviderHealthChecker {
391
406
  return "AWS_ACCESS_KEY_ID";
392
407
  case AIProviderName.AZURE:
393
408
  return "AZURE_OPENAI_API_KEY";
409
+ case AIProviderName.LITELLM:
410
+ return "LITELLM_API_KEY";
394
411
  case AIProviderName.OLLAMA:
395
- return "OLLAMA_API_BASE";
412
+ return "OLLAMA_BASE_URL";
396
413
  default:
397
414
  return "";
398
415
  }
@@ -416,6 +433,8 @@ export class ProviderHealthChecker {
416
433
  return apiKey.length >= API_KEY_LENGTHS.AWS_ACCESS_KEY; // AWS access key length
417
434
  case AIProviderName.AZURE:
418
435
  return apiKey.length >= API_KEY_LENGTHS.AZURE_MIN; // Azure OpenAI API key length
436
+ case AIProviderName.LITELLM:
437
+ return apiKey.length > 0;
419
438
  case AIProviderName.OLLAMA:
420
439
  return true; // Ollama usually doesn't require specific format
421
440
  default:
@@ -437,8 +456,10 @@ export class ProviderHealthChecker {
437
456
  return null; // Complex authentication required
438
457
  case AIProviderName.BEDROCK:
439
458
  return null; // AWS endpoints vary by region
459
+ case AIProviderName.LITELLM:
460
+ return this.getLiteLLMModelsUrl();
440
461
  case AIProviderName.OLLAMA:
441
- return "http://localhost:11434/api/version";
462
+ return this.getOllamaTagsUrl();
442
463
  default:
443
464
  return null;
444
465
  }
@@ -446,7 +467,7 @@ export class ProviderHealthChecker {
446
467
  /**
447
468
  * Provider-specific configuration checks
448
469
  */
449
- static async checkProviderSpecificConfig(providerName, healthStatus) {
470
+ static async checkProviderSpecificConfig(providerName, healthStatus, timeout) {
450
471
  switch (providerName) {
451
472
  case AIProviderName.VERTEX:
452
473
  await this.checkVertexAIConfig(healthStatus);
@@ -457,8 +478,11 @@ export class ProviderHealthChecker {
457
478
  case AIProviderName.AZURE:
458
479
  await this.checkAzureConfig(healthStatus);
459
480
  break;
481
+ case AIProviderName.LITELLM:
482
+ await this.checkLiteLLMConfig(healthStatus, timeout);
483
+ break;
460
484
  case AIProviderName.OLLAMA:
461
- await this.checkOllamaConfig(healthStatus);
485
+ await this.checkOllamaConfig(healthStatus, timeout);
462
486
  break;
463
487
  }
464
488
  }
@@ -648,15 +672,180 @@ export class ProviderHealthChecker {
648
672
  healthStatus.recommendations.push("Set one of: AZURE_OPENAI_MODEL, AZURE_OPENAI_DEPLOYMENT, or AZURE_OPENAI_DEPLOYMENT_ID");
649
673
  }
650
674
  }
675
+ static getLiteLLMBaseUrl() {
676
+ return process.env.LITELLM_BASE_URL || "http://localhost:4000";
677
+ }
678
+ static getLiteLLMModelsUrl() {
679
+ return new URL("/v1/models", this.getLiteLLMBaseUrl()).toString();
680
+ }
681
+ static getConfiguredLiteLLMModel() {
682
+ return process.env.LITELLM_MODEL || "openai/gpt-4o-mini";
683
+ }
684
+ static getOllamaBaseUrl() {
685
+ return (process.env.OLLAMA_BASE_URL ||
686
+ process.env.OLLAMA_API_BASE ||
687
+ "http://localhost:11434");
688
+ }
689
+ static getOllamaTagsUrl() {
690
+ return new URL("/api/tags", this.getOllamaBaseUrl()).toString();
691
+ }
692
+ static getConfiguredOllamaModel() {
693
+ return process.env.OLLAMA_MODEL || "llama3.1:8b";
694
+ }
695
+ static async fetchJsonWithTimeout(url, options = {}) {
696
+ const controller = new AbortController();
697
+ const timeoutId = setTimeout(() => controller.abort(), options.timeout ?? this.DEFAULT_TIMEOUT);
698
+ try {
699
+ const proxyFetch = createProxyFetch();
700
+ const response = await proxyFetch(url, {
701
+ method: "GET",
702
+ headers: options.headers,
703
+ signal: controller.signal,
704
+ });
705
+ if (!response.ok) {
706
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
707
+ }
708
+ return await response.json();
709
+ }
710
+ finally {
711
+ clearTimeout(timeoutId);
712
+ }
713
+ }
714
+ static normalizeModelList(models) {
715
+ return models
716
+ .map((entry) => {
717
+ if (typeof entry === "string") {
718
+ return entry;
719
+ }
720
+ if (entry &&
721
+ typeof entry === "object" &&
722
+ "id" in entry &&
723
+ typeof entry.id === "string") {
724
+ return entry.id;
725
+ }
726
+ if (entry &&
727
+ typeof entry === "object" &&
728
+ "name" in entry &&
729
+ typeof entry.name === "string") {
730
+ return entry.name;
731
+ }
732
+ return null;
733
+ })
734
+ .filter((model) => typeof model === "string");
735
+ }
736
+ static hasRequestedModel(availableModels, requestedModel) {
737
+ const normalizedRequestedModel = requestedModel.trim();
738
+ const requiresExactMatch = /@/.test(normalizedRequestedModel);
739
+ return availableModels.some((model) => model === normalizedRequestedModel ||
740
+ (!requiresExactMatch &&
741
+ (model.startsWith(`${normalizedRequestedModel}:`) ||
742
+ model.startsWith(`${normalizedRequestedModel}@`))));
743
+ }
744
+ static async getOllamaAvailableModels(timeout = 2000) {
745
+ const payload = (await this.fetchJsonWithTimeout(this.getOllamaTagsUrl(), {
746
+ timeout,
747
+ }));
748
+ return this.normalizeModelList(payload.models ?? []);
749
+ }
750
+ static async getLiteLLMAvailableModels(timeout = 2000) {
751
+ const payload = (await this.fetchJsonWithTimeout(this.getLiteLLMModelsUrl(), {
752
+ timeout,
753
+ headers: {
754
+ Authorization: `Bearer ${process.env.LITELLM_API_KEY || "sk-anything"}`,
755
+ "Content-Type": "application/json",
756
+ },
757
+ }));
758
+ return this.normalizeModelList(payload.data ?? []);
759
+ }
760
+ static async checkOllamaAvailability(options) {
761
+ try {
762
+ const models = await this.getOllamaAvailableModels(options.timeout);
763
+ if (!this.hasRequestedModel(models, options.model)) {
764
+ return {
765
+ available: false,
766
+ reason: `Configured Ollama model '${options.model}' is not installed`,
767
+ models,
768
+ };
769
+ }
770
+ return { available: true, models };
771
+ }
772
+ catch (error) {
773
+ return {
774
+ available: false,
775
+ reason: error instanceof Error ? error.message : String(error),
776
+ models: [],
777
+ };
778
+ }
779
+ }
780
+ static async checkLiteLLMAvailability(options) {
781
+ try {
782
+ const models = await this.getLiteLLMAvailableModels(options.timeout);
783
+ if (models.length === 0) {
784
+ return {
785
+ available: false,
786
+ reason: "LiteLLM returned an empty model list",
787
+ models,
788
+ };
789
+ }
790
+ if (!this.hasRequestedModel(models, options.model)) {
791
+ return {
792
+ available: false,
793
+ reason: `Configured LiteLLM model '${options.model}' is not exposed by the proxy`,
794
+ models,
795
+ };
796
+ }
797
+ return { available: true, models };
798
+ }
799
+ catch (error) {
800
+ return {
801
+ available: false,
802
+ reason: error instanceof Error ? error.message : String(error),
803
+ models: [],
804
+ };
805
+ }
806
+ }
807
+ static async checkLiteLLMConfig(healthStatus, timeout = this.DEFAULT_TIMEOUT) {
808
+ const liteLLMBase = this.getLiteLLMBaseUrl();
809
+ if (!liteLLMBase.startsWith("http")) {
810
+ healthStatus.isConfigured = false;
811
+ healthStatus.configurationIssues.push("Invalid LITELLM_BASE_URL format");
812
+ healthStatus.recommendations.push("Set LITELLM_BASE_URL to a valid URL (e.g., http://localhost:4000)");
813
+ return;
814
+ }
815
+ const availability = await this.checkLiteLLMAvailability({
816
+ model: this.getConfiguredLiteLLMModel(),
817
+ timeout,
818
+ });
819
+ if (!availability.available) {
820
+ healthStatus.isConfigured = false;
821
+ healthStatus.configurationIssues.push(`LiteLLM runtime check failed: ${availability.reason ?? "unknown error"}`);
822
+ healthStatus.recommendations.push("Start the LiteLLM proxy and ensure the configured model is available from /v1/models");
823
+ return;
824
+ }
825
+ healthStatus.isConfigured = true;
826
+ }
651
827
  /**
652
828
  * Check Ollama configuration
653
829
  */
654
- static async checkOllamaConfig(healthStatus) {
655
- const ollamaBase = process.env.OLLAMA_API_BASE || "http://localhost:11434";
830
+ static async checkOllamaConfig(healthStatus, timeout = this.DEFAULT_TIMEOUT) {
831
+ const ollamaBase = this.getOllamaBaseUrl();
656
832
  if (!ollamaBase.startsWith("http")) {
657
- healthStatus.configurationIssues.push("Invalid OLLAMA_API_BASE format");
658
- healthStatus.recommendations.push("Set OLLAMA_API_BASE to a valid URL (e.g., http://localhost:11434)");
833
+ healthStatus.isConfigured = false;
834
+ healthStatus.configurationIssues.push("Invalid OLLAMA_BASE_URL format (OLLAMA_API_BASE is still accepted as a legacy alias)");
835
+ healthStatus.recommendations.push("Set OLLAMA_BASE_URL to a valid URL (e.g., http://localhost:11434). OLLAMA_API_BASE remains supported as a legacy alias.");
836
+ return;
659
837
  }
838
+ const availability = await this.checkOllamaAvailability({
839
+ model: this.getConfiguredOllamaModel(),
840
+ timeout,
841
+ });
842
+ if (!availability.available) {
843
+ healthStatus.isConfigured = false;
844
+ healthStatus.configurationIssues.push(`Ollama runtime check failed: ${availability.reason ?? "unknown error"}`);
845
+ healthStatus.recommendations.push("Start Ollama and install the configured model before using Ollama as a fallback provider");
846
+ return;
847
+ }
848
+ healthStatus.isConfigured = true;
660
849
  }
661
850
  /**
662
851
  * Get common models for a provider
@@ -703,8 +892,21 @@ export class ProviderHealthChecker {
703
892
  return [BedrockModels.CLAUDE_3_SONNET, BedrockModels.CLAUDE_3_HAIKU];
704
893
  case AIProviderName.AZURE:
705
894
  return [OpenAIModels.GPT_4O, OpenAIModels.GPT_4O_MINI, "gpt-35-turbo"];
706
- case AIProviderName.OLLAMA:
707
- return ["llama3.2:latest", "llama3.1:latest", "mistral:latest"];
895
+ case AIProviderName.LITELLM:
896
+ return [
897
+ "openai/gpt-4o-mini",
898
+ "anthropic/claude-3-haiku",
899
+ "google/gemini-2.5-flash",
900
+ ];
901
+ case AIProviderName.OLLAMA: {
902
+ const envModel = process.env.OLLAMA_MODEL;
903
+ const defaults = [
904
+ "llama3.2:latest",
905
+ "llama3.1:latest",
906
+ "mistral:latest",
907
+ ];
908
+ return envModel ? [envModel, ...defaults] : defaults;
909
+ }
708
910
  default:
709
911
  return [];
710
912
  }
@@ -1139,18 +1341,61 @@ export class ProviderHealthChecker {
1139
1341
  this.consecutiveFailures.clear();
1140
1342
  }
1141
1343
  }
1344
+ static async checkFallbackProviderAvailability(providerName, model) {
1345
+ const provider = providerName;
1346
+ if (provider === AIProviderName.OLLAMA) {
1347
+ const availability = await this.checkOllamaAvailability({
1348
+ model,
1349
+ timeout: 2000,
1350
+ });
1351
+ return {
1352
+ available: availability.available,
1353
+ reason: availability.reason,
1354
+ };
1355
+ }
1356
+ if (provider === AIProviderName.LITELLM) {
1357
+ const availability = await this.checkLiteLLMAvailability({
1358
+ model,
1359
+ timeout: 2000,
1360
+ });
1361
+ return {
1362
+ available: availability.available,
1363
+ reason: availability.reason,
1364
+ };
1365
+ }
1366
+ try {
1367
+ const health = await this.checkProviderHealth(provider, {
1368
+ includeConnectivityTest: false,
1369
+ cacheResults: true,
1370
+ maxCacheAge: 15_000,
1371
+ timeout: 2000,
1372
+ });
1373
+ return {
1374
+ available: health.isHealthy,
1375
+ reason: health.error || health.configurationIssues[0] || health.warning,
1376
+ };
1377
+ }
1378
+ catch (error) {
1379
+ return {
1380
+ available: false,
1381
+ reason: error instanceof Error ? error.message : String(error),
1382
+ };
1383
+ }
1384
+ }
1142
1385
  /**
1143
1386
  * Get the best healthy provider from a list of options (NON-BLOCKING)
1144
1387
  * Prioritizes healthy providers over configured but unhealthy ones
1145
1388
  * Uses fast, cached health checks to avoid blocking initialization
1146
1389
  */
1147
1390
  static async getBestHealthyProvider(preferredProviders = [
1391
+ "litellm",
1392
+ "ollama",
1148
1393
  "openai",
1149
1394
  "anthropic",
1150
1395
  "vertex",
1396
+ "google-ai",
1151
1397
  "bedrock",
1152
1398
  "azure",
1153
- "google-ai",
1154
1399
  ]) {
1155
1400
  const healthStatuses = await this.checkAllProvidersHealth({
1156
1401
  includeConnectivityTest: false, // Quick config check only
@@ -1191,6 +1436,7 @@ export class ProviderHealthChecker {
1191
1436
  AIProviderName.OPENAI,
1192
1437
  AIProviderName.BEDROCK,
1193
1438
  AIProviderName.AZURE,
1439
+ AIProviderName.LITELLM,
1194
1440
  AIProviderName.OLLAMA,
1195
1441
  ];
1196
1442
  const healthChecks = providers.map((provider) => this.checkProviderHealth(provider, options));
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { AIProviderFactory } from "../core/factory.js";
6
6
  import { logger } from "./logger.js";
7
+ import { AIProviderName } from "../constants/enums.js";
7
8
  import { ProviderHealthChecker } from "./providerHealth.js";
8
9
  import { API_KEY_FORMATS, API_KEY_LENGTHS, PROJECT_ID_FORMAT, } from "./providerConfig.js";
9
10
  /**
@@ -49,7 +50,8 @@ export async function getBestProvider(requestedProvider) {
49
50
  return process.env.DEFAULT_PROVIDER;
50
51
  }
51
52
  // Special case for Ollama - prioritize local when available
52
- if (process.env.OLLAMA_BASE_URL && process.env.OLLAMA_MODEL) {
53
+ if ((process.env.OLLAMA_BASE_URL || process.env.OLLAMA_API_BASE) &&
54
+ process.env.OLLAMA_MODEL) {
53
55
  try {
54
56
  if (await isProviderAvailable("ollama")) {
55
57
  logger.debug(`[getBestProvider] Prioritizing working local Ollama`);
@@ -62,15 +64,18 @@ export async function getBestProvider(requestedProvider) {
62
64
  }
63
65
  /**
64
66
  * Provider priority order rationale:
65
- * - Vertex (Google Cloud AI) is prioritized first for its enterprise-grade reliability and advanced model capabilities.
66
- * - Google AI follows as second priority for comprehensive Google AI ecosystem support.
67
+ * - LiteLLM and Ollama are prioritized first for local/self-hosted deployments,
68
+ * avoiding unnecessary dependence on external providers during fallback scenarios.
69
+ * - Vertex (Google Cloud AI) follows for enterprise-grade reliability.
70
+ * - Google AI follows as second cloud priority for comprehensive Google AI ecosystem support.
67
71
  * - OpenAI maintains high priority due to its consistent reliability and broad model support.
68
- * - Other providers are ordered based on a combination of reliability, feature set, and historical performance in our use cases.
69
- * - Ollama is kept as a fallback for local deployments when available.
72
+ * - Other providers are ordered based on a combination of reliability, feature set, and historical performance.
70
73
  * Please update this comment if the order is changed in the future, and document the rationale for maintainability.
71
74
  */
72
75
  const providers = [
73
- "vertex", // Prioritize Google Cloud AI (Vertex) first
76
+ "litellm", // Prioritize self-hosted proxy deployments first
77
+ "ollama", // Local models when the configured runtime target is installed
78
+ "vertex", // Google Cloud AI (enterprise)
74
79
  "google-ai", // Google AI ecosystem support
75
80
  "openai", // Reliable with broad model support
76
81
  "anthropic",
@@ -78,7 +83,6 @@ export async function getBestProvider(requestedProvider) {
78
83
  "azure",
79
84
  "mistral",
80
85
  "huggingface",
81
- "ollama", // Keep as fallback
82
86
  ];
83
87
  for (const provider of providers) {
84
88
  if (await isProviderAvailable(provider)) {
@@ -98,22 +102,13 @@ async function isProviderAvailable(providerName) {
98
102
  if (!hasProviderEnvVars(providerName) && providerName !== "ollama") {
99
103
  return false;
100
104
  }
105
+ if (providerName === "litellm") {
106
+ const availability = await ProviderHealthChecker.checkFallbackProviderAvailability(AIProviderName.LITELLM, process.env.LITELLM_MODEL || "openai/gpt-4o-mini");
107
+ return availability.available;
108
+ }
101
109
  if (providerName === "ollama") {
102
- try {
103
- const response = await fetch("http://localhost:11434/api/tags", {
104
- method: "GET",
105
- signal: AbortSignal.timeout(2000),
106
- });
107
- if (response.ok) {
108
- const { models } = await response.json();
109
- const defaultOllamaModel = "llama3.2:latest";
110
- return models.some((m) => m.name === defaultOllamaModel);
111
- }
112
- return false;
113
- }
114
- catch {
115
- return false;
116
- }
110
+ const availability = await ProviderHealthChecker.checkFallbackProviderAvailability(AIProviderName.OLLAMA, process.env.OLLAMA_MODEL || "llama3.1:8b");
111
+ return availability.available;
117
112
  }
118
113
  try {
119
114
  const provider = await AIProviderFactory.createProvider(providerName);
@@ -413,6 +408,7 @@ export function getAvailableProviders() {
413
408
  "anthropic",
414
409
  "azure",
415
410
  "google-ai",
411
+ "litellm",
416
412
  "huggingface",
417
413
  "ollama",
418
414
  "mistral",
@@ -0,0 +1,4 @@
1
+ import type { Tool, ToolChoice } from "ai";
2
+ export declare function resolveToolChoice(options: {
3
+ toolChoice?: ToolChoice<Record<string, Tool>>;
4
+ }, tools: Record<string, Tool> | undefined, shouldUseTools: boolean): ToolChoice<Record<string, Tool>> | "none";
@@ -0,0 +1,6 @@
1
+ export function resolveToolChoice(options, tools, shouldUseTools) {
2
+ if (!shouldUseTools || !tools || Object.keys(tools).length === 0) {
3
+ return "none";
4
+ }
5
+ return options.toolChoice ?? "auto";
6
+ }