@juspay/neurolink 11.0.0 → 11.1.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 (121) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/browser/neurolink.min.js +510 -510
  3. package/dist/cli/commands/setup.d.ts +8 -1
  4. package/dist/cli/commands/setup.js +62 -46
  5. package/dist/cli/factories/commandFactory.d.ts +1157 -1
  6. package/dist/cli/factories/commandFactory.js +20 -53
  7. package/dist/constants/networkErrorCodes.d.ts +14 -0
  8. package/dist/constants/networkErrorCodes.js +21 -0
  9. package/dist/factories/providerDescriptors.d.ts +18 -0
  10. package/dist/factories/providerDescriptors.js +546 -0
  11. package/dist/factories/providerFactory.d.ts +25 -14
  12. package/dist/factories/providerFactory.js +46 -23
  13. package/dist/factories/providerRegistry.js +31 -30
  14. package/dist/index.d.ts +10 -1
  15. package/dist/index.js +13 -3
  16. package/dist/lib/constants/networkErrorCodes.d.ts +14 -0
  17. package/dist/lib/constants/networkErrorCodes.js +22 -0
  18. package/dist/lib/factories/providerDescriptors.d.ts +18 -0
  19. package/dist/lib/factories/providerDescriptors.js +547 -0
  20. package/dist/lib/factories/providerFactory.d.ts +25 -14
  21. package/dist/lib/factories/providerFactory.js +46 -23
  22. package/dist/lib/factories/providerRegistry.js +31 -30
  23. package/dist/lib/index.d.ts +10 -1
  24. package/dist/lib/index.js +13 -3
  25. package/dist/lib/neurolink.js +25 -37
  26. package/dist/lib/processors/base/BaseFileProcessor.d.ts +17 -0
  27. package/dist/lib/processors/base/BaseFileProcessor.js +40 -0
  28. package/dist/lib/processors/document/OpenDocumentProcessor.js +12 -2
  29. package/dist/lib/providers/amazonBedrock/client.js +34 -16
  30. package/dist/lib/providers/anthropic/client.js +39 -34
  31. package/dist/lib/providers/azureOpenai.js +17 -15
  32. package/dist/lib/providers/cloudflare.js +12 -21
  33. package/dist/lib/providers/cohere.js +31 -25
  34. package/dist/lib/providers/deepseek.js +23 -26
  35. package/dist/lib/providers/fireworks.js +12 -21
  36. package/dist/lib/providers/googleAiStudio/client.d.ts +2 -0
  37. package/dist/lib/providers/googleAiStudio/client.js +39 -17
  38. package/dist/lib/providers/googleVertex/client.js +107 -99
  39. package/dist/lib/providers/groq.js +19 -22
  40. package/dist/lib/providers/huggingFace/client.js +26 -24
  41. package/dist/lib/providers/litellm/client.js +54 -39
  42. package/dist/lib/providers/llamaCpp.js +21 -18
  43. package/dist/lib/providers/lmStudio.js +22 -19
  44. package/dist/lib/providers/mistral.js +12 -24
  45. package/dist/lib/providers/nvidiaNim/client.js +37 -34
  46. package/dist/lib/providers/ollama/client.js +50 -35
  47. package/dist/lib/providers/openAI/client.js +37 -40
  48. package/dist/lib/providers/openRouter/client.js +53 -47
  49. package/dist/lib/providers/openaiChatCompletionsBase.js +40 -16
  50. package/dist/lib/providers/openaiCompatible/client.js +36 -32
  51. package/dist/lib/providers/perplexity.js +12 -21
  52. package/dist/lib/providers/togetherAi.js +12 -21
  53. package/dist/lib/providers/xai.js +17 -26
  54. package/dist/lib/proxy/proxyFetch.js +1 -9
  55. package/dist/lib/server/errors.d.ts +1 -1
  56. package/dist/lib/server/errors.js +2 -2
  57. package/dist/lib/server/index.d.ts +2 -2
  58. package/dist/lib/server/index.js +5 -3
  59. package/dist/lib/server/middleware/rateLimit.d.ts +0 -4
  60. package/dist/lib/server/middleware/rateLimit.js +0 -4
  61. package/dist/lib/types/cli.d.ts +2 -0
  62. package/dist/lib/types/errors.d.ts +35 -0
  63. package/dist/lib/types/providers.d.ts +79 -0
  64. package/dist/lib/utils/errorClassifier.d.ts +30 -0
  65. package/dist/lib/utils/errorClassifier.js +183 -0
  66. package/dist/lib/utils/fileDetector.js +6 -43
  67. package/dist/lib/utils/providerConfig.d.ts +16 -0
  68. package/dist/lib/utils/providerConfig.js +23 -0
  69. package/dist/lib/utils/providerHealth.d.ts +53 -12
  70. package/dist/lib/utils/providerHealth.js +125 -131
  71. package/dist/lib/utils/providerUtils.js +22 -64
  72. package/dist/neurolink.js +25 -37
  73. package/dist/processors/base/BaseFileProcessor.d.ts +17 -0
  74. package/dist/processors/base/BaseFileProcessor.js +40 -0
  75. package/dist/processors/document/OpenDocumentProcessor.js +12 -2
  76. package/dist/providers/amazonBedrock/client.js +34 -16
  77. package/dist/providers/amazonSagemaker.d.ts +1 -1
  78. package/dist/providers/anthropic/client.js +39 -34
  79. package/dist/providers/azureOpenai.js +17 -15
  80. package/dist/providers/cloudflare.js +12 -21
  81. package/dist/providers/cohere.js +31 -25
  82. package/dist/providers/deepseek.js +23 -26
  83. package/dist/providers/fireworks.js +12 -21
  84. package/dist/providers/googleAiStudio/client.d.ts +2 -0
  85. package/dist/providers/googleAiStudio/client.js +39 -17
  86. package/dist/providers/googleVertex/client.js +107 -99
  87. package/dist/providers/groq.js +19 -22
  88. package/dist/providers/huggingFace/client.js +26 -24
  89. package/dist/providers/litellm/client.js +54 -39
  90. package/dist/providers/llamaCpp.js +21 -18
  91. package/dist/providers/lmStudio.js +22 -19
  92. package/dist/providers/mistral.js +12 -24
  93. package/dist/providers/nvidiaNim/client.js +37 -34
  94. package/dist/providers/ollama/client.js +50 -35
  95. package/dist/providers/openAI/client.js +37 -40
  96. package/dist/providers/openRouter/client.js +53 -47
  97. package/dist/providers/openaiChatCompletionsBase.js +40 -16
  98. package/dist/providers/openaiCompatible/client.js +36 -32
  99. package/dist/providers/perplexity.js +12 -21
  100. package/dist/providers/sagemaker/language-model.d.ts +2 -2
  101. package/dist/providers/togetherAi.js +12 -21
  102. package/dist/providers/xai.js +17 -26
  103. package/dist/proxy/proxyFetch.js +1 -9
  104. package/dist/server/errors.d.ts +1 -1
  105. package/dist/server/errors.js +2 -2
  106. package/dist/server/index.d.ts +2 -2
  107. package/dist/server/index.js +5 -3
  108. package/dist/server/middleware/rateLimit.d.ts +0 -4
  109. package/dist/server/middleware/rateLimit.js +0 -4
  110. package/dist/types/cli.d.ts +2 -0
  111. package/dist/types/errors.d.ts +35 -0
  112. package/dist/types/providers.d.ts +79 -0
  113. package/dist/utils/errorClassifier.d.ts +30 -0
  114. package/dist/utils/errorClassifier.js +182 -0
  115. package/dist/utils/fileDetector.js +6 -43
  116. package/dist/utils/providerConfig.d.ts +16 -0
  117. package/dist/utils/providerConfig.js +23 -0
  118. package/dist/utils/providerHealth.d.ts +53 -12
  119. package/dist/utils/providerHealth.js +125 -131
  120. package/dist/utils/providerUtils.js +22 -64
  121. package/package.json +15 -70
@@ -4,10 +4,11 @@
4
4
  */
5
5
  import { logger } from "./logger.js";
6
6
  import { AIProviderName, OpenAIModels, GoogleAIModels, AnthropicModels, BedrockModels, } from "../constants/enums.js";
7
- import { API_KEY_LENGTHS, PROJECT_ID_FORMAT } from "./providerConfig.js";
7
+ import { PROJECT_ID_FORMAT, satisfiesFallbacks } from "./providerConfig.js";
8
8
  import { basename } from "path";
9
9
  import { createProxyFetch } from "../proxy/proxyFetch.js";
10
10
  import { DEFAULT_OLLAMA_MODEL } from "../providers/ollama/constants.js";
11
+ import { ProviderFactory } from "../factories/providerFactory.js";
11
12
  export class ProviderHealthChecker {
12
13
  static healthCache = new Map();
13
14
  static DEFAULT_TIMEOUT = 5000; // 5 seconds
@@ -172,55 +173,36 @@ export class ProviderHealthChecker {
172
173
  logger.debug("Vertex AI authentication check starting", {
173
174
  providerName,
174
175
  });
175
- // Method 1: Check GOOGLE_APPLICATION_CREDENTIALS (file-based)
176
- const credentialsFile = process.env.GOOGLE_APPLICATION_CREDENTIALS;
177
- let fileBasedAuthValid = false;
178
- if (credentialsFile) {
179
- logger.debug("Checking GOOGLE_APPLICATION_CREDENTIALS file");
180
- try {
181
- const { promises: fs } = await import("fs");
182
- try {
183
- await fs.access(credentialsFile);
184
- fileBasedAuthValid = true;
185
- }
186
- catch {
187
- fileBasedAuthValid = false;
188
- }
189
- logger.debug("File auth check result", {
190
- fileExists: fileBasedAuthValid,
191
- });
192
- }
193
- catch (error) {
194
- logger.debug("File auth check error", {
195
- error: String(error),
196
- });
197
- fileBasedAuthValid = false;
198
- }
199
- }
200
- // Method 2: Check individual environment variables
201
- const hasIndividualAuth = !!(process.env.GOOGLE_AUTH_CLIENT_EMAIL &&
202
- process.env.GOOGLE_AUTH_PRIVATE_KEY);
203
- logger.debug("Individual auth check", {
204
- hasClientEmail: !!process.env.GOOGLE_AUTH_CLIENT_EMAIL,
205
- hasPrivateKey: !!process.env.GOOGLE_AUTH_PRIVATE_KEY,
206
- hasIndividualAuth,
207
- });
208
- // Vertex is valid if EITHER auth method works
209
- const hasValidAuth = fileBasedAuthValid || hasIndividualAuth;
210
- logger.debug("Vertex auth final result", {
211
- fileBasedAuthValid,
212
- hasIndividualAuth,
213
- hasValidAuth,
214
- });
176
+ // Derive auth-var acceptance from the vertex descriptor instead of a
177
+ // hand-maintained var list, so this stays in sync with the real
178
+ // gating logic (hasGoogleCredentials() in googleVertex/client.ts and
179
+ // googleVertex/utils.ts): extraRequired.every(...) ||
180
+ // extraRequiredFallbacks.some(...), the same pattern used by
181
+ // hasProviderEnvVars() (providerUtils.ts), setup.ts, and
182
+ // environmentManager.ts. The old two-method check here only
183
+ // recognized GOOGLE_APPLICATION_CREDENTIALS or the
184
+ // GOOGLE_AUTH_CLIENT_EMAIL + GOOGLE_AUTH_PRIVATE_KEY pair — missing
185
+ // GOOGLE_SERVICE_ACCOUNT_KEY and, most importantly,
186
+ // GOOGLE_APPLICATION_CREDENTIALS_NEUROLINK, the descriptor's
187
+ // documented first-priority fallback. A user configured via that
188
+ // var alone had a fully working Vertex provider but was reported
189
+ // unhealthy here. Note: this is a presence-only check — it no
190
+ // longer verifies the GOOGLE_APPLICATION_CREDENTIALS file actually
191
+ // exists on disk (the old file-based method did via fs.access),
192
+ // matching the presence-only semantics already used by the other
193
+ // three callers of this pattern.
194
+ const descriptor = ProviderFactory.getDescriptor(AIProviderName.VERTEX);
195
+ const { extraRequired, extraRequiredFallbacks } = descriptor?.envVars ?? {};
196
+ const hasValidAuth = (extraRequired ?? []).every((v) => !!process.env[v]) ||
197
+ satisfiesFallbacks(extraRequiredFallbacks, process.env);
198
+ logger.debug("Vertex auth final result", { hasValidAuth });
215
199
  if (hasValidAuth) {
216
200
  healthStatus.hasApiKey = true;
217
- logger.debug("Vertex auth SUCCESS", {
218
- authMethod: fileBasedAuthValid ? "file-based" : "individual-env-vars",
219
- });
201
+ logger.debug("Vertex auth SUCCESS");
220
202
  }
221
203
  else {
222
204
  healthStatus.hasApiKey = false;
223
- healthStatus.configurationIssues.push(`Vertex AI authentication not found: neither GOOGLE_APPLICATION_CREDENTIALS file nor individual credentials (GOOGLE_AUTH_CLIENT_EMAIL + GOOGLE_AUTH_PRIVATE_KEY) are properly configured`);
205
+ healthStatus.configurationIssues.push("Vertex AI authentication not found: set GOOGLE_APPLICATION_CREDENTIALS, GOOGLE_APPLICATION_CREDENTIALS_NEUROLINK, GOOGLE_SERVICE_ACCOUNT_KEY, or both GOOGLE_AUTH_CLIENT_EMAIL and GOOGLE_AUTH_PRIVATE_KEY");
224
206
  logger.debug("Vertex auth FAILED", {
225
207
  reason: "No valid auth method found",
226
208
  });
@@ -360,103 +342,108 @@ export class ProviderHealthChecker {
360
342
  }
361
343
  }
362
344
  /**
363
- * Get required environment variables for a provider
345
+ * Get required environment variables for a provider.
346
+ *
347
+ * Returns `[]` for providers with `descriptor.credentialsResolvedExternally
348
+ * === true` (Vertex, Bedrock, LiteLLM) — the check in
349
+ * checkEnvironmentConfiguration() below ANDs every entry in the returned
350
+ * list together, which can't express Vertex's file-OR-individual-creds-
351
+ * OR-service-account auth, Bedrock's AWS SDK default provider chain
352
+ * (profile / IAM role, no env vars at all), or LiteLLM's documented
353
+ * zero-config local proxy. Their real requirement is validated by
354
+ * checkProviderSpecificConfig()'s dedicated per-provider checks instead.
355
+ * Naively deriving [apiKey, ...extraRequired] from the descriptor for
356
+ * these three (as for the other 27 providers) would make
357
+ * checkEnvironmentConfiguration() push a false "missing environment
358
+ * variables" issue — and therefore isHealthy=false — for legitimate
359
+ * fallback-based Vertex auth, AWS_PROFILE/IAM-role Bedrock auth, and
360
+ * unauthenticated local LiteLLM proxies. See hasProviderEnvVars() in
361
+ * providerUtils.ts for the equivalent OR-aware check used for
362
+ * auto-select gating. See `ProviderDescriptor.credentialsResolvedExternally`
363
+ * (types/providers.ts) for the field's full documentation.
364
364
  */
365
365
  static getRequiredEnvironmentVariables(providerName) {
366
- switch (providerName) {
367
- case AIProviderName.ANTHROPIC:
368
- return ["ANTHROPIC_API_KEY"];
369
- case AIProviderName.OPENAI:
370
- return ["OPENAI_API_KEY"];
371
- case AIProviderName.VERTEX:
372
- // Vertex AI requires authentication, but not via a single environment variable.
373
- // Authentication can be provided via a credential file or individual credentials + project.
374
- // The required authentication is checked in checkProviderSpecificConfig instead of here.
375
- // Returning an empty array here does NOT mean authentication is not required.
376
- return [];
377
- case AIProviderName.GOOGLE_AI:
378
- return ["GOOGLE_AI_API_KEY"];
379
- case AIProviderName.BEDROCK:
380
- // Bedrock credentials are resolved via AWS SDK default provider chain.
381
- // Region/auth validated in provider-specific checks.
382
- return [];
383
- case AIProviderName.AZURE:
384
- return ["AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT"];
385
- case AIProviderName.LITELLM:
386
- return [];
387
- case AIProviderName.OLLAMA:
388
- return []; // Ollama typically doesn't require API keys
389
- default:
390
- return [];
391
- }
366
+ // Resolve the descriptor FIRST so the field check below is keyed on
367
+ // the canonical, alias-resolved `descriptor.credentialsResolvedExternally`
368
+ // — not the raw, possibly-aliased `providerName` argument. Checking the
369
+ // raw input here would let documented aliases (e.g. "googleVertex" for
370
+ // vertex, "aws" for bedrock) skip the delegation and fall through to
371
+ // the naive [apiKey, ...extraRequired] derivation below, reproducing
372
+ // the exact false "missing environment variables" regression this
373
+ // field exists to prevent.
374
+ const descriptor = ProviderFactory.getDescriptor(providerName);
375
+ if (!descriptor) {
376
+ return [];
377
+ }
378
+ if (descriptor.credentialsResolvedExternally) {
379
+ return [];
380
+ }
381
+ const { apiKey, extraRequired } = descriptor.envVars;
382
+ return [...(apiKey ? [apiKey] : []), ...(extraRequired ?? [])];
392
383
  }
393
384
  /**
394
- * Get API key environment variable for a provider
385
+ * Get API key environment variable for a provider.
386
+ *
387
+ * Dead code at its sole call site today (checkApiKeyValidity() early-
388
+ * returns for VERTEX/OLLAMA/BEDROCK/LITELLM before reaching this), but
389
+ * now `static` (public) so a future direct caller is plausible — hence
390
+ * the two intentional diffs from the original hand-written switch are
391
+ * documented here rather than left silent:
392
+ * - vertex: old switch returned "GOOGLE_APPLICATION_CREDENTIALS"
393
+ * (one of several valid auth vars); the descriptor's `envVars.apiKey`
394
+ * is "GOOGLE_CLOUD_PROJECT_ID" instead — the actual identity var for
395
+ * the provider, not one specific credential-supply mechanism.
396
+ * - ollama: old switch returned "OLLAMA_BASE_URL"; the descriptor has
397
+ * no `envVars.apiKey` for ollama (it's a local, unauthenticated
398
+ * runtime), so this now returns "".
399
+ * Both are kept deliberately (semantically truer identity vars) rather
400
+ * than reproduced byte-for-byte from the old switch.
395
401
  */
396
402
  static getApiKeyEnvironmentVariable(providerName) {
397
- switch (providerName) {
398
- case AIProviderName.ANTHROPIC:
399
- return "ANTHROPIC_API_KEY";
400
- case AIProviderName.OPENAI:
401
- return "OPENAI_API_KEY";
402
- case AIProviderName.VERTEX:
403
- return "GOOGLE_APPLICATION_CREDENTIALS";
404
- case AIProviderName.GOOGLE_AI:
405
- return "GOOGLE_AI_API_KEY";
406
- case AIProviderName.BEDROCK:
407
- return "AWS_ACCESS_KEY_ID";
408
- case AIProviderName.AZURE:
409
- return "AZURE_OPENAI_API_KEY";
410
- case AIProviderName.LITELLM:
411
- return "LITELLM_API_KEY";
412
- case AIProviderName.OLLAMA:
413
- return "OLLAMA_BASE_URL";
414
- default:
415
- return "";
416
- }
403
+ return ProviderFactory.getDescriptor(providerName)?.envVars.apiKey ?? "";
417
404
  }
418
405
  /**
419
406
  * Validate API key format for a provider
420
407
  */
421
408
  static validateApiKeyFormat(providerName, apiKey) {
422
- switch (providerName) {
423
- case AIProviderName.ANTHROPIC:
424
- return (apiKey.startsWith("sk-ant-") &&
425
- apiKey.length >= API_KEY_LENGTHS.ANTHROPIC_MIN);
426
- case AIProviderName.OPENAI:
427
- return (apiKey.startsWith("sk-") &&
428
- apiKey.length >= API_KEY_LENGTHS.OPENAI_MIN);
429
- case AIProviderName.GOOGLE_AI:
430
- return apiKey.length >= API_KEY_LENGTHS.GOOGLE_AI_EXACT; // Basic length check
431
- case AIProviderName.VERTEX:
432
- return apiKey.endsWith(".json") || apiKey.includes("type"); // JSON key format
433
- case AIProviderName.BEDROCK:
434
- return apiKey.length >= API_KEY_LENGTHS.AWS_ACCESS_KEY; // AWS access key length
435
- case AIProviderName.AZURE:
436
- return apiKey.length >= API_KEY_LENGTHS.AZURE_MIN; // Azure OpenAI API key length
437
- case AIProviderName.LITELLM:
438
- return apiKey.length > 0;
439
- case AIProviderName.OLLAMA:
440
- return true; // Ollama usually doesn't require specific format
441
- default:
442
- return true; // Default to true for unknown providers
409
+ // Vertex's "API key" is really a credentials file path (or an inline
410
+ // JSON blob via GOOGLE_SERVICE_ACCOUNT_KEY) — never governed by the
411
+ // shared API_KEY_FORMATS regexes. Preserved exactly from the original
412
+ // switch even though checkApiKeyValidity() early-returns before ever
413
+ // reaching this call for Vertex today; kept correct for any other/
414
+ // future caller.
415
+ if (providerName === AIProviderName.VERTEX) {
416
+ return apiKey.endsWith(".json") || apiKey.includes("type");
417
+ }
418
+ const descriptor = ProviderFactory.getDescriptor(providerName);
419
+ if (!descriptor?.apiKeyFormatPattern) {
420
+ // Providers with no documented format (or local runtimes / providers
421
+ // usable with zero configuration) are accepted as-is — matches the
422
+ // old switch's per-provider `default: true`/no-format-check branches
423
+ // (ollama, litellm).
424
+ if (descriptor?.localRuntime || descriptor?.envVars.optional) {
425
+ return true;
426
+ }
427
+ return apiKey.length > 0;
443
428
  }
429
+ return descriptor.apiKeyFormatPattern.test(apiKey);
444
430
  }
445
431
  /**
446
432
  * Get health check endpoint for connectivity testing
447
433
  */
448
434
  static getProviderHealthEndpoint(providerName) {
449
- switch (providerName) {
450
- case AIProviderName.ANTHROPIC:
451
- return null; // Anthropic doesn't have a public health endpoint
435
+ const descriptor = ProviderFactory.getDescriptor(providerName);
436
+ if (descriptor?.healthCheck !== "models-probe") {
437
+ return null;
438
+ }
439
+ // OpenAI/LiteLLM/Ollama each need a provider-specific URL builder that
440
+ // isn't pure descriptor data (LiteLLM/Ollama depend on the configured
441
+ // base URL) — keep the existing per-provider dispatch for the 3
442
+ // models-probe providers, now gated on the descriptor instead of a
443
+ // hardcoded name list.
444
+ switch (descriptor.name) {
452
445
  case AIProviderName.OPENAI:
453
446
  return "https://api.openai.com/v1/models";
454
- case AIProviderName.GOOGLE_AI:
455
- return null; // No public health endpoint
456
- case AIProviderName.VERTEX:
457
- return null; // Complex authentication required
458
- case AIProviderName.BEDROCK:
459
- return null; // AWS endpoints vary by region
460
447
  case AIProviderName.LITELLM:
461
448
  return this.getLiteLLMModelsUrl();
462
449
  case AIProviderName.OLLAMA:
@@ -525,11 +512,11 @@ export class ProviderHealthChecker {
525
512
  hasValidAuth = await this.checkGoogleApplicationCredentials(healthStatus);
526
513
  }
527
514
  if (!hasValidAuth) {
528
- hasValidAuth = this.checkIndividualGoogleCredentials(healthStatus);
515
+ hasValidAuth = this.checkExtraRequiredFallbackCredentials(healthStatus);
529
516
  }
530
517
  if (!hasValidAuth) {
531
518
  healthStatus.configurationIssues.push("Google Cloud authentication not configured or credentials file missing");
532
- healthStatus.recommendations.push("Set either GOOGLE_APPLICATION_CREDENTIALS (valid file path), GOOGLE_SERVICE_ACCOUNT_KEY (base64), or both GOOGLE_AUTH_CLIENT_EMAIL and GOOGLE_AUTH_PRIVATE_KEY");
519
+ healthStatus.recommendations.push("Set either GOOGLE_APPLICATION_CREDENTIALS (valid file path), GOOGLE_APPLICATION_CREDENTIALS_NEUROLINK, GOOGLE_SERVICE_ACCOUNT_KEY (base64), or both GOOGLE_AUTH_CLIENT_EMAIL and GOOGLE_AUTH_PRIVATE_KEY");
533
520
  }
534
521
  return hasValidAuth;
535
522
  }
@@ -561,13 +548,20 @@ export class ProviderHealthChecker {
561
548
  }
562
549
  }
563
550
  /**
564
- * Check individual Google credentials
551
+ * Check Vertex's non-file auth fallbacks (GOOGLE_APPLICATION_CREDENTIALS_
552
+ * NEUROLINK, GOOGLE_SERVICE_ACCOUNT_KEY, or the GOOGLE_AUTH_CLIENT_EMAIL +
553
+ * GOOGLE_AUTH_PRIVATE_KEY pair) via the descriptor's extraRequiredFallbacks
554
+ * instead of a hand-maintained env-var list, so this stays in sync with
555
+ * the real gating logic (hasGoogleCredentials()) the same way
556
+ * checkApiKeyValidity()'s vertex branch does. The previous hand-rolled
557
+ * version only recognized GOOGLE_SERVICE_ACCOUNT_KEY or the email+key
558
+ * pair — missing GOOGLE_APPLICATION_CREDENTIALS_NEUROLINK, the
559
+ * descriptor's documented first-priority fallback.
565
560
  */
566
- static checkIndividualGoogleCredentials(healthStatus) {
567
- const hasServiceAccountKey = !!process.env.GOOGLE_SERVICE_ACCOUNT_KEY;
568
- const hasIndividualCredentials = !!(process.env.GOOGLE_AUTH_CLIENT_EMAIL &&
569
- process.env.GOOGLE_AUTH_PRIVATE_KEY);
570
- if (hasServiceAccountKey || hasIndividualCredentials) {
561
+ static checkExtraRequiredFallbackCredentials(healthStatus) {
562
+ const descriptor = ProviderFactory.getDescriptor(AIProviderName.VERTEX);
563
+ const hasValidAuth = satisfiesFallbacks(descriptor?.envVars.extraRequiredFallbacks, process.env);
564
+ if (hasValidAuth) {
571
565
  healthStatus.hasApiKey = true;
572
566
  return true;
573
567
  }
@@ -6,8 +6,10 @@ import { AIProviderFactory } from "../core/factory.js";
6
6
  import { logger } from "./logger.js";
7
7
  import { AIProviderName } from "../constants/enums.js";
8
8
  import { ProviderHealthChecker } from "./providerHealth.js";
9
- import { API_KEY_FORMATS, API_KEY_LENGTHS, PROJECT_ID_FORMAT, } from "./providerConfig.js";
9
+ import { API_KEY_FORMATS, API_KEY_LENGTHS, PROJECT_ID_FORMAT, satisfiesFallbacks, } from "./providerConfig.js";
10
10
  import { DEFAULT_OLLAMA_MODEL } from "../providers/ollama/constants.js";
11
+ import { ProviderFactory } from "../factories/providerFactory.js";
12
+ import { PROVIDER_DESCRIPTORS } from "../factories/providerDescriptors.js";
11
13
  /**
12
14
  * Get the best available provider based on real-time availability checks
13
15
  * Enhanced version consolidated from providerUtils-fixed.ts
@@ -60,18 +62,9 @@ export async function getBestProvider(requestedProvider) {
60
62
  * - Other providers are ordered based on a combination of reliability, feature set, and historical performance.
61
63
  * Please update this comment if the order is changed in the future, and document the rationale for maintainability.
62
64
  */
63
- const providers = [
64
- "litellm", // Prioritize self-hosted proxy deployments first
65
- "ollama", // Local models when the configured runtime target is installed
66
- "vertex", // Google Cloud AI (enterprise)
67
- "google-ai", // Google AI ecosystem support
68
- "openai", // Reliable with broad model support
69
- "anthropic",
70
- "bedrock",
71
- "azure",
72
- "mistral",
73
- "huggingface",
74
- ];
65
+ const providers = PROVIDER_DESCRIPTORS.filter((d) => d.autoSelectPriority !== undefined)
66
+ .sort((a, b) => (a.autoSelectPriority ?? 0) - (b.autoSelectPriority ?? 0))
67
+ .map((d) => d.name);
75
68
  for (const provider of providers) {
76
69
  if (await isProviderAvailable(provider)) {
77
70
  logger.debug(`[getBestProvider] Selected provider: ${provider}`);
@@ -332,64 +325,29 @@ function isValidUrl(url) {
332
325
  * @returns True if the provider has required environment variables
333
326
  */
334
327
  export function hasProviderEnvVars(provider) {
335
- switch (provider.toLowerCase()) {
336
- case "bedrock":
337
- case "amazon":
338
- case "aws":
339
- return !!(process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY);
340
- case "vertex":
341
- case "googlevertex":
342
- case "google":
343
- case "gemini":
344
- return !!((process.env.GOOGLE_CLOUD_PROJECT_ID ||
345
- process.env.VERTEX_PROJECT_ID ||
346
- process.env.GOOGLE_VERTEX_PROJECT ||
347
- process.env.GOOGLE_CLOUD_PROJECT) &&
348
- (process.env.GOOGLE_APPLICATION_CREDENTIALS ||
349
- process.env.GOOGLE_SERVICE_ACCOUNT_KEY ||
350
- (process.env.GOOGLE_AUTH_CLIENT_EMAIL &&
351
- process.env.GOOGLE_AUTH_PRIVATE_KEY)));
352
- case "openai":
353
- case "gpt":
354
- return !!process.env.OPENAI_API_KEY;
355
- case "anthropic":
356
- case "claude":
357
- return !!process.env.ANTHROPIC_API_KEY;
358
- case "azure":
359
- case "azureopenai":
360
- return !!process.env.AZURE_OPENAI_API_KEY;
361
- case "google-ai":
362
- case "google-studio":
363
- return !!(process.env.GOOGLE_AI_API_KEY ||
364
- process.env.GOOGLE_GENERATIVE_AI_API_KEY);
365
- case "huggingface":
366
- case "hugging-face":
367
- case "hf":
368
- return !!(process.env.HUGGINGFACE_API_KEY || process.env.HF_TOKEN);
369
- case "ollama":
370
- case "local":
371
- case "local-ollama":
372
- // For Ollama, we check if the service is potentially available
373
- // This is a basic check - actual connectivity will be verified during usage
374
- return true; // Ollama doesn't require environment variables, just local service
375
- case "mistral":
376
- case "mistral-ai":
377
- case "mistralai":
378
- return !!process.env.MISTRAL_API_KEY;
379
- case "litellm":
380
- // LiteLLM requires a proxy server, which can be checked for availability
381
- // Default base URL is assumed, or can be configured via environment
382
- return true; // LiteLLM proxy availability will be checked during usage
383
- default:
384
- return false;
328
+ const descriptor = ProviderFactory.getDescriptor(provider);
329
+ if (!descriptor) {
330
+ return false;
331
+ }
332
+ if (descriptor.envVars.optional || descriptor.localRuntime) {
333
+ // Ollama / LiteLLM / LM Studio / llama.cpp: usable with defaults.
334
+ return true;
335
+ }
336
+ const { apiKey, fallbacks, extraRequired, extraRequiredFallbacks } = descriptor.envVars;
337
+ const hasPrimary = !!apiKey &&
338
+ (!!process.env[apiKey] || (fallbacks ?? []).some((v) => !!process.env[v]));
339
+ if (!hasPrimary) {
340
+ return false;
385
341
  }
342
+ return ((extraRequired ?? []).every((v) => !!process.env[v]) ||
343
+ satisfiesFallbacks(extraRequiredFallbacks, process.env));
386
344
  }
387
345
  /**
388
346
  * Get available provider names
389
347
  * @returns Array of available provider names
390
348
  */
391
349
  export function getAvailableProviders() {
392
- return Object.values(AIProviderName).filter((name) => name !== AIProviderName.AUTO);
350
+ return PROVIDER_DESCRIPTORS.map((d) => d.name);
393
351
  }
394
352
  /**
395
353
  * Validate provider name
package/dist/neurolink.js CHANGED
@@ -35,6 +35,7 @@ import { ToolRoutingCache } from "./core/toolRoutingCache.js";
35
35
  import { DEFAULT_RECENT_TURNS, KnowledgeGroundingEngine, } from "./knowledge/index.js";
36
36
  import { AIProviderFactory } from "./core/factory.js";
37
37
  import { createToolEventPayload } from "./core/toolEvents.js";
38
+ import { ProviderFactory } from "./factories/providerFactory.js";
38
39
  import { ProviderRegistry } from "./factories/providerRegistry.js";
39
40
  import { FileReferenceRegistry } from "./files/fileReferenceRegistry.js";
40
41
  import { createFileTools } from "./files/fileTools.js";
@@ -298,30 +299,24 @@ export function markStreamProviderEmittedGenerationEnd(options) {
298
299
  */
299
300
  export const NEUROLINK_BRAND = Symbol.for("@juspay/neurolink/sdk-brand");
300
301
  /**
301
- * Providers whose native tool-calling support is model-dependent or absent
302
- * i.e. every provider that overrides `supportsTools()` and can return false
303
- * (verified against src/lib/providers: ollama and openrouter are
304
- * model-dependent; huggingface is deployment-dependent; the rest are
305
- * image/embedding providers). Only these still receive the full tool listing
306
- * in the system prompt on the generate path, where no provider instance
307
- * exists yet to ask directly; every other provider gets tool definitions
308
- * natively via its `tools` parameter, so repeating them in the prompt was
309
- * pure token duplication. The stream path asks the provider instance
310
- * (`provider.supportsTools()`) instead of this list. BaseProvider resolves its
311
- * default through MODEL_REGISTRY's `modelSupports()` facade; keep this list in
312
- * sync with provider-specific `supportsTools()` overrides when adding providers.
302
+ * True when a provider needs tools described in the prompt rather than
303
+ * passed via the API's native tool-calling parameter (ollama/openrouter
304
+ * are model-dependent; huggingface is deployment-dependent; the
305
+ * image/embedding providers don't support tool calling at all). Only these
306
+ * still receive the full tool listing in the system prompt on the generate
307
+ * path, where no provider instance exists yet to ask directly; every other
308
+ * provider gets tool definitions natively via its `tools` parameter, so
309
+ * repeating them in the prompt was pure token duplication. The stream path
310
+ * asks the provider instance (`provider.supportsTools()`) instead of this
311
+ * check. Derived from ProviderDescriptor.toolSupport instead of a
312
+ * hand-maintained Set see PROVIDER_DESCRIPTORS in
313
+ * src/lib/factories/providerDescriptors.ts for the underlying data;
314
+ * BaseProvider resolves its own runtime default through MODEL_REGISTRY's
315
+ * `modelSupports()` facade.
313
316
  */
314
- const PROMPT_ONLY_TOOL_PROVIDERS = new Set([
315
- "ollama",
316
- "huggingface",
317
- "openrouter",
318
- "ideogram",
319
- "recraft",
320
- "replicate",
321
- "stability",
322
- "jina",
323
- "voyage",
324
- ]);
317
+ function isPromptOnlyToolProvider(providerName) {
318
+ return ProviderFactory.getDescriptor(providerName)?.toolSupport !== "native";
319
+ }
325
320
  /**
326
321
  * Type-guard for opaque values that should be a {@link NeuroLink} instance.
327
322
  *
@@ -5452,7 +5447,7 @@ Current user's request: ${currentInput}`;
5452
5447
  }
5453
5448
  // Providers with native tool calling receive full definitions via their
5454
5449
  // `tools` parameter; only prompt-based providers still get the listing.
5455
- const nativeToolSupport = !PROMPT_ONLY_TOOL_PROVIDERS.has(String(providerName).toLowerCase());
5450
+ const nativeToolSupport = !isPromptOnlyToolProvider(String(providerName).toLowerCase());
5456
5451
  const enhancedSystemPrompt = options.skipToolPromptInjection
5457
5452
  ? (options.systemPrompt || "") + circuitBreakerNote
5458
5453
  : this.createToolAwareSystemPrompt(options.systemPrompt, availableTools, nativeToolSupport) + circuitBreakerNote;
@@ -10351,19 +10346,12 @@ Current user's request: ${currentInput}`;
10351
10346
  // Keep references to prevent unused variable warnings
10352
10347
  void AIProviderFactory;
10353
10348
  void hasProviderEnvVars;
10354
- const providers = [
10355
- "openai",
10356
- "bedrock",
10357
- "vertex",
10358
- "googleVertex",
10359
- "anthropic",
10360
- "azure",
10361
- "google-ai",
10362
- "huggingface",
10363
- "ollama",
10364
- "mistral",
10365
- "litellm",
10366
- ];
10349
+ // Derived from the descriptor registry instead of a hand-maintained
10350
+ // list — covers all real providers (was previously hardcoded to 11,
10351
+ // including "googleVertex" as a duplicate entry alongside "vertex";
10352
+ // that alias remains resolvable via PROVIDER_ALIAS_INDEX/CLI choices,
10353
+ // it just no longer gets its own separate status-check entry).
10354
+ const providers = ProviderFactory.getAllDescriptors().map((d) => d.name);
10367
10355
  // Test providers with controlled concurrency
10368
10356
  // This reduces total time from 16s (sequential) to ~3s (parallel) while preventing resource exhaustion
10369
10357
  const limit = pLimit(SYSTEM_LIMITS.DEFAULT_CONCURRENCY_LIMIT);
@@ -45,6 +45,23 @@
45
45
  */
46
46
  import { FileErrorCode } from "../errors/index.js";
47
47
  import type { BatchProcessingSummary, FileInfo, FileProcessingError, ProcessorFileProcessingResult, FileProcessorConfig, ProcessorOperationResult, ProcessedFileBase, ProcessOptions } from "../../types/index.js";
48
+ /**
49
+ * Marker on the error raised when a processor refuses content for exceeding
50
+ * the size limit *after* the download — a ZIP entry that expands past the
51
+ * bound, say.
52
+ *
53
+ * Separate from DOWNLOAD_TOO_LARGE because the two fire at different stages,
54
+ * but it exists for the same reason. Without it a bound that fires reaches
55
+ * `buildProcessedResultWithResult` as a plain Error and becomes
56
+ * PROCESSING_FAILED, which is `retryable: true` — so a caller retries a
57
+ * deterministic refusal three times and gets the identical answer each time.
58
+ * FILE_TOO_LARGE is `retryable: false`, which is the truth about this failure.
59
+ */
60
+ export declare const CONTENT_TOO_LARGE_CODE = "CONTENT_TOO_LARGE";
61
+ /** An error that a processor's own size bound was exceeded. */
62
+ export declare function contentTooLargeError(message: string): Error;
63
+ /** Whether `error` is a processor size bound firing rather than a fault. */
64
+ export declare function isContentTooLarge(error: unknown): boolean;
48
65
  /**
49
66
  * Abstract base class for file processors.
50
67
  * Provides common download, validation, and error handling functionality.
@@ -72,6 +72,29 @@ function downloadTooLargeError(maxSizeMB, typeName) {
72
72
  function isDownloadTooLarge(error) {
73
73
  return (error?.code === DOWNLOAD_TOO_LARGE_CODE);
74
74
  }
75
+ /**
76
+ * Marker on the error raised when a processor refuses content for exceeding
77
+ * the size limit *after* the download — a ZIP entry that expands past the
78
+ * bound, say.
79
+ *
80
+ * Separate from DOWNLOAD_TOO_LARGE because the two fire at different stages,
81
+ * but it exists for the same reason. Without it a bound that fires reaches
82
+ * `buildProcessedResultWithResult` as a plain Error and becomes
83
+ * PROCESSING_FAILED, which is `retryable: true` — so a caller retries a
84
+ * deterministic refusal three times and gets the identical answer each time.
85
+ * FILE_TOO_LARGE is `retryable: false`, which is the truth about this failure.
86
+ */
87
+ export const CONTENT_TOO_LARGE_CODE = "CONTENT_TOO_LARGE";
88
+ /** An error that a processor's own size bound was exceeded. */
89
+ export function contentTooLargeError(message) {
90
+ const error = new Error(message);
91
+ error.code = CONTENT_TOO_LARGE_CODE;
92
+ return error;
93
+ }
94
+ /** Whether `error` is a processor size bound firing rather than a fault. */
95
+ export function isContentTooLarge(error) {
96
+ return (error?.code === CONTENT_TOO_LARGE_CODE);
97
+ }
75
98
  /** Node's signal that a zlib output bound was reached. */
76
99
  function isBufferTooLargeError(error) {
77
100
  return (error?.code === "ERR_BUFFER_TOO_LARGE");
@@ -360,6 +383,23 @@ export class BaseFileProcessor {
360
383
  return { success: true, data: result };
361
384
  }
362
385
  catch (error) {
386
+ // A size bound firing is a verdict, not a fault: PROCESSING_FAILED is
387
+ // retryable, and re-running a decompression that will hit the same
388
+ // ceiling is the one thing worth not doing three times.
389
+ if (isContentTooLarge(error)) {
390
+ return {
391
+ success: false,
392
+ // Same detail keys as the other FILE_TOO_LARGE sites, so anything
393
+ // reading them does not have to special-case where the verdict came
394
+ // from. `sizeMB` is absent on purpose: the decompressed size is the
395
+ // number this bound exists to never find out.
396
+ error: this.createError(FileErrorCode.FILE_TOO_LARGE, {
397
+ maxMB: this.config.maxSizeMB,
398
+ type: this.config.fileTypeName,
399
+ reason: error instanceof Error ? error.message : undefined,
400
+ }),
401
+ };
402
+ }
363
403
  return {
364
404
  success: false,
365
405
  error: this.createError(FileErrorCode.PROCESSING_FAILED, { fileType: this.config.fileTypeName }, error instanceof Error ? error : undefined),