@juspay/neurolink 11.0.0 → 11.1.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 (103) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/browser/neurolink.min.js +524 -524
  3. package/dist/cli/commands/setup.d.ts +4 -0
  4. package/dist/cli/commands/setup.js +19 -33
  5. package/dist/cli/factories/commandFactory.d.ts +1157 -1
  6. package/dist/cli/factories/commandFactory.js +20 -53
  7. package/dist/factories/providerDescriptors.d.ts +18 -0
  8. package/dist/factories/providerDescriptors.js +536 -0
  9. package/dist/factories/providerFactory.d.ts +25 -14
  10. package/dist/factories/providerFactory.js +46 -23
  11. package/dist/factories/providerRegistry.js +31 -30
  12. package/dist/index.d.ts +10 -1
  13. package/dist/index.js +13 -3
  14. package/dist/lib/factories/providerDescriptors.d.ts +18 -0
  15. package/dist/lib/factories/providerDescriptors.js +537 -0
  16. package/dist/lib/factories/providerFactory.d.ts +25 -14
  17. package/dist/lib/factories/providerFactory.js +46 -23
  18. package/dist/lib/factories/providerRegistry.js +31 -30
  19. package/dist/lib/index.d.ts +10 -1
  20. package/dist/lib/index.js +13 -3
  21. package/dist/lib/neurolink.js +25 -37
  22. package/dist/lib/providers/amazonBedrock/client.js +34 -16
  23. package/dist/lib/providers/anthropic/client.js +39 -34
  24. package/dist/lib/providers/azureOpenai.js +17 -15
  25. package/dist/lib/providers/cloudflare.js +12 -21
  26. package/dist/lib/providers/cohere.js +31 -25
  27. package/dist/lib/providers/deepseek.js +23 -26
  28. package/dist/lib/providers/fireworks.js +12 -21
  29. package/dist/lib/providers/googleAiStudio/client.d.ts +2 -0
  30. package/dist/lib/providers/googleAiStudio/client.js +39 -17
  31. package/dist/lib/providers/googleVertex/client.js +107 -99
  32. package/dist/lib/providers/groq.js +19 -22
  33. package/dist/lib/providers/huggingFace/client.js +26 -24
  34. package/dist/lib/providers/litellm/client.js +54 -39
  35. package/dist/lib/providers/llamaCpp.js +21 -18
  36. package/dist/lib/providers/lmStudio.js +22 -19
  37. package/dist/lib/providers/mistral.js +12 -24
  38. package/dist/lib/providers/nvidiaNim/client.js +37 -34
  39. package/dist/lib/providers/ollama/client.js +50 -35
  40. package/dist/lib/providers/openAI/client.js +37 -40
  41. package/dist/lib/providers/openRouter/client.js +53 -47
  42. package/dist/lib/providers/openaiChatCompletionsBase.js +40 -16
  43. package/dist/lib/providers/openaiCompatible/client.js +36 -32
  44. package/dist/lib/providers/perplexity.js +12 -21
  45. package/dist/lib/providers/togetherAi.js +12 -21
  46. package/dist/lib/providers/xai.js +17 -26
  47. package/dist/lib/server/errors.d.ts +1 -1
  48. package/dist/lib/server/errors.js +2 -2
  49. package/dist/lib/server/index.d.ts +2 -2
  50. package/dist/lib/server/index.js +5 -3
  51. package/dist/lib/server/middleware/rateLimit.d.ts +0 -4
  52. package/dist/lib/server/middleware/rateLimit.js +0 -4
  53. package/dist/lib/types/errors.d.ts +35 -0
  54. package/dist/lib/types/providers.d.ts +64 -0
  55. package/dist/lib/utils/errorClassifier.d.ts +30 -0
  56. package/dist/lib/utils/errorClassifier.js +94 -0
  57. package/dist/lib/utils/fileDetector.js +6 -43
  58. package/dist/lib/utils/providerHealth.d.ts +42 -7
  59. package/dist/lib/utils/providerHealth.js +115 -122
  60. package/dist/lib/utils/providerUtils.js +21 -63
  61. package/dist/neurolink.js +25 -37
  62. package/dist/providers/amazonBedrock/client.js +34 -16
  63. package/dist/providers/amazonSagemaker.d.ts +1 -1
  64. package/dist/providers/anthropic/client.js +39 -34
  65. package/dist/providers/azureOpenai.js +17 -15
  66. package/dist/providers/cloudflare.js +12 -21
  67. package/dist/providers/cohere.js +31 -25
  68. package/dist/providers/deepseek.js +23 -26
  69. package/dist/providers/fireworks.js +12 -21
  70. package/dist/providers/googleAiStudio/client.d.ts +2 -0
  71. package/dist/providers/googleAiStudio/client.js +39 -17
  72. package/dist/providers/googleVertex/client.js +107 -99
  73. package/dist/providers/groq.js +19 -22
  74. package/dist/providers/huggingFace/client.js +26 -24
  75. package/dist/providers/litellm/client.js +54 -39
  76. package/dist/providers/llamaCpp.js +21 -18
  77. package/dist/providers/lmStudio.js +22 -19
  78. package/dist/providers/mistral.js +12 -24
  79. package/dist/providers/nvidiaNim/client.js +37 -34
  80. package/dist/providers/ollama/client.js +50 -35
  81. package/dist/providers/openAI/client.js +37 -40
  82. package/dist/providers/openRouter/client.js +53 -47
  83. package/dist/providers/openaiChatCompletionsBase.js +40 -16
  84. package/dist/providers/openaiCompatible/client.js +36 -32
  85. package/dist/providers/perplexity.js +12 -21
  86. package/dist/providers/sagemaker/language-model.d.ts +2 -2
  87. package/dist/providers/togetherAi.js +12 -21
  88. package/dist/providers/xai.js +17 -26
  89. package/dist/server/errors.d.ts +1 -1
  90. package/dist/server/errors.js +2 -2
  91. package/dist/server/index.d.ts +2 -2
  92. package/dist/server/index.js +5 -3
  93. package/dist/server/middleware/rateLimit.d.ts +0 -4
  94. package/dist/server/middleware/rateLimit.js +0 -4
  95. package/dist/types/errors.d.ts +35 -0
  96. package/dist/types/providers.d.ts +64 -0
  97. package/dist/utils/errorClassifier.d.ts +30 -0
  98. package/dist/utils/errorClassifier.js +93 -0
  99. package/dist/utils/fileDetector.js +6 -43
  100. package/dist/utils/providerHealth.d.ts +42 -7
  101. package/dist/utils/providerHealth.js +115 -122
  102. package/dist/utils/providerUtils.js +21 -63
  103. package/package.json +15 -70
@@ -13,6 +13,7 @@ import { isSchemaComplexityError } from "../../core/modules/structuredOutputPoli
13
13
  import { redactUrlForError, stringifyContentSafe, } from "../../utils/logSanitize.js";
14
14
  import { createProxyFetch } from "../../proxy/proxyFetch.js";
15
15
  import { AuthenticationError, InvalidModelError, NetworkError, ProviderError, RateLimitError, } from "../../types/index.js";
16
+ import { classifyProviderError } from "../../utils/errorClassifier.js";
16
17
  import { ERROR_CODES, NeuroLinkError } from "../../utils/errorHandling.js";
17
18
  import { applyVertexAnthropicCacheBreakpoints } from "../../utils/anthropicCacheBreakpoints.js";
18
19
  import { FileDetector } from "../../utils/fileDetector.js";
@@ -6098,110 +6099,117 @@ export class GoogleVertexProvider extends BaseProvider {
6098
6099
  }
6099
6100
  formatProviderError(error) {
6100
6101
  const errorRecord = error;
6101
- if (typeof errorRecord?.name === "string" &&
6102
- errorRecord.name === "TimeoutError") {
6103
- return new NetworkError(`Google Vertex AI request timed out. Consider increasing timeout or using a lighter model.`, this.providerName);
6104
- }
6105
- const message = typeof errorRecord?.message === "string"
6106
- ? errorRecord.message
6107
- : "Unknown error occurred";
6108
6102
  const statusCode = typeof errorRecord?.status === "number"
6109
6103
  ? errorRecord.status
6110
6104
  : typeof errorRecord?.statusCode === "number"
6111
6105
  ? errorRecord.statusCode
6112
6106
  : undefined;
6113
- // Authentication and permission errors
6114
- if (message.includes("PERMISSION_DENIED") ||
6115
- message.includes("UNAUTHENTICATED") ||
6116
- message.includes("Invalid API key") ||
6117
- statusCode === 401 ||
6118
- statusCode === 403) {
6119
- return new AuthenticationError(`Google Vertex AI Permission Denied. Your Google Cloud credentials don't have permission to access Vertex AI. ` +
6120
- `Required Steps: 1. Ensure your service account has Vertex AI User role ` +
6121
- `2. Check if Vertex AI API is enabled in your project ` +
6122
- `3. Verify your project ID is correct ` +
6123
- `4. Confirm your location/region has Vertex AI available`, this.providerName);
6124
- }
6125
- // Model not found errors
6126
- if (message.includes("NOT_FOUND") ||
6127
- message.includes("model not found") ||
6128
- message.includes("Model not found") ||
6129
- statusCode === 404) {
6130
- const modelSuggestions = this.getModelSuggestions(this.modelName);
6131
- return new InvalidModelError(`Model '${this.modelName}' is not available in region ${this.location}. ` +
6132
- `Suggested alternatives: ${modelSuggestions}. ` +
6133
- `Troubleshooting: 1. Check model name spelling and format ` +
6134
- `2. Verify model is available in your region ` +
6135
- `3. Ensure your project has access to the model ` +
6136
- `4. For Claude models, enable Anthropic integration in Google Cloud Console`, this.providerName);
6137
- }
6138
- // Rate limit / quota / capacity errors. Anthropic-on-Vertex capacity
6139
- // exhaustion surfaces as overloaded_error (HTTP 529) — same operational
6140
- // meaning as a 429, so classify it here instead of the generic 5xx branch.
6141
- if (message.includes("QUOTA_EXCEEDED") ||
6142
- message.includes("RATE_LIMIT_EXCEEDED") ||
6143
- message.includes("rate limit") ||
6144
- message.includes("429") ||
6145
- statusCode === 429 ||
6146
- statusCode === 529 ||
6147
- /overloaded/i.test(message)) {
6148
- // Surface retry guidance when the SDK error carries it. @google/genai
6149
- // ApiError nests RetryInfo inside the JSON error body's details array,
6150
- // so fall back to scraping retryDelay out of the raw message.
6151
- const retryDelay = typeof errorRecord?.retryDelay === "string"
6152
- ? errorRecord.retryDelay
6153
- : (/["']?retryDelay["']?\s*[:=]\s*["']?(\d+(?:\.\d+)?s)/.exec(message)?.[1] ?? undefined);
6154
- // Prefer the per-request context the native catches attach to the
6155
- // error (this.modelName can be stale when options.model overrides the
6156
- // instance default). Gemini models are force-routed to the "global"
6157
- // endpoint regardless of configured location — report the region the
6158
- // request actually hit.
6159
- const requestModel = typeof errorRecord?.requestModel === "string"
6160
- ? errorRecord.requestModel
6161
- : this.modelName;
6162
- const effectiveRegion = typeof errorRecord?.requestRegion === "string"
6163
- ? errorRecord.requestRegion
6164
- : resolveVertexRegionForModel(requestModel, this.location);
6165
- return new RateLimitError(`Google Vertex AI rate limit / shared-capacity exhausted (429 RESOURCE_EXHAUSTED / overloaded) ` +
6166
- `for model '${requestModel}' in region '${effectiveRegion}'.` +
6167
- (retryDelay
6168
- ? ` Upstream suggests retrying after ${retryDelay}.`
6169
- : "") +
6170
- ` Solutions: 1. Retry with backoff ` +
6171
- `2. Check your Vertex AI quotas in Google Cloud Console (shared-capacity 429s can occur below quota) ` +
6172
- `3. Try a different region or model ` +
6173
- `4. Request provisioned throughput for sustained load`, this.providerName);
6174
- }
6175
- // Network connectivity errors
6176
- if (message.includes("ECONNRESET") ||
6177
- message.includes("ENOTFOUND") ||
6178
- message.includes("ETIMEDOUT") ||
6179
- message.includes("ECONNREFUSED") ||
6180
- message.includes("network") ||
6181
- message.includes("connection")) {
6182
- return new NetworkError(`Connection error: ${message}`, this.providerName);
6183
- }
6184
- // Server errors (5xx)
6185
- if (message.includes("500") ||
6186
- message.includes("502") ||
6187
- message.includes("503") ||
6188
- message.includes("504") ||
6189
- message.includes("server error") ||
6190
- message.includes("Internal Server Error") ||
6191
- message.includes("INTERNAL") ||
6192
- message.includes("UNAVAILABLE") ||
6193
- (statusCode && statusCode >= 500 && statusCode < 600)) {
6194
- return new ProviderError(`Google Vertex AI server error: ${message}. Please try again later.`, this.providerName);
6195
- }
6196
- // Invalid argument errors
6197
- if (message.includes("INVALID_ARGUMENT")) {
6198
- return new ProviderError(`Google Vertex AI Invalid Request: ${message}. ` +
6199
- `Check: 1. Request parameters are within model limits ` +
6200
- `2. Input text is properly formatted ` +
6201
- `3. Temperature and other settings are valid ` +
6202
- `4. Model supports your request type`, this.providerName);
6203
- }
6204
- return new ProviderError(`Google Vertex AI error: ${message}`, this.providerName);
6107
+ const rules = [
6108
+ {
6109
+ // Duck-typed on .name rather than `instanceof TimeoutError` —
6110
+ // Vertex's own `withTimeout` (../../utils/async/index.js) throws a
6111
+ // TimeoutError class distinct from the one classifyProviderError's
6112
+ // built-in fast path checks (../../utils/timeout.js), so that fast
6113
+ // path never fires for a real Vertex timeout. This rule preserves
6114
+ // the pre-migration duck-typed match (both classes set
6115
+ // `.name = "TimeoutError"`) and the original Vertex-specific
6116
+ // message see task-4-report.md for the full writeup.
6117
+ match: (ctx) => ctx.errorName === "TimeoutError",
6118
+ errorClass: NetworkError,
6119
+ message: "Google Vertex AI request timed out. Consider increasing timeout or using a lighter model.",
6120
+ },
6121
+ {
6122
+ match: (ctx) => /PERMISSION_DENIED|UNAUTHENTICATED|Invalid API key/i.test(ctx.message) ||
6123
+ statusCode === 401 ||
6124
+ statusCode === 403,
6125
+ errorClass: AuthenticationError,
6126
+ message: () => `Google Vertex AI Permission Denied. Your Google Cloud credentials don't have permission to access Vertex AI. ` +
6127
+ `Required Steps: 1. Ensure your service account has Vertex AI User role ` +
6128
+ `2. Check if Vertex AI API is enabled in your project ` +
6129
+ `3. Verify your project ID is correct ` +
6130
+ `4. Confirm your location/region has Vertex AI available`,
6131
+ },
6132
+ {
6133
+ match: (ctx) => /NOT_FOUND|model not found|Model not found/i.test(ctx.message) ||
6134
+ statusCode === 404,
6135
+ errorClass: InvalidModelError,
6136
+ message: () => {
6137
+ const modelSuggestions = this.getModelSuggestions(this.modelName);
6138
+ return (`Model '${this.modelName}' is not available in region ${this.location}. ` +
6139
+ `Suggested alternatives: ${modelSuggestions}. ` +
6140
+ `Troubleshooting: 1. Check model name spelling and format ` +
6141
+ `2. Verify model is available in your region ` +
6142
+ `3. Ensure your project has access to the model ` +
6143
+ `4. For Claude models, enable Anthropic integration in Google Cloud Console`);
6144
+ },
6145
+ },
6146
+ {
6147
+ // Rate limit / quota / capacity errors. Anthropic-on-Vertex capacity
6148
+ // exhaustion surfaces as overloaded_error (HTTP 529) same
6149
+ // operational meaning as a 429, so classify it here instead of the
6150
+ // generic 5xx branch below.
6151
+ match: (ctx) => /QUOTA_EXCEEDED|RATE_LIMIT_EXCEEDED|rate limit|429/i.test(ctx.message) ||
6152
+ statusCode === 429 ||
6153
+ statusCode === 529 ||
6154
+ /overloaded/i.test(ctx.message),
6155
+ errorClass: RateLimitError,
6156
+ message: (ctx) => {
6157
+ // Surface retry guidance when the SDK error carries it.
6158
+ // @google/genai ApiError nests RetryInfo inside the JSON error
6159
+ // body's details array, so fall back to scraping retryDelay out
6160
+ // of the raw message.
6161
+ const retryDelay = typeof errorRecord?.retryDelay === "string"
6162
+ ? errorRecord.retryDelay
6163
+ : (/["']?retryDelay["']?\s*[:=]\s*["']?(\d+(?:\.\d+)?s)/.exec(ctx.message)?.[1] ?? undefined);
6164
+ // Prefer the per-request context the native catches attach to the
6165
+ // error (this.modelName can be stale when options.model overrides
6166
+ // the instance default). Gemini models are force-routed to the
6167
+ // "global" endpoint regardless of configured location — report
6168
+ // the region the request actually hit.
6169
+ const requestModel = typeof errorRecord?.requestModel === "string"
6170
+ ? errorRecord.requestModel
6171
+ : this.modelName;
6172
+ const effectiveRegion = typeof errorRecord?.requestRegion === "string"
6173
+ ? errorRecord.requestRegion
6174
+ : resolveVertexRegionForModel(requestModel, this.location);
6175
+ return (`Google Vertex AI rate limit / shared-capacity exhausted (429 RESOURCE_EXHAUSTED / overloaded) ` +
6176
+ `for model '${requestModel}' in region '${effectiveRegion}'.` +
6177
+ (retryDelay
6178
+ ? ` Upstream suggests retrying after ${retryDelay}.`
6179
+ : "") +
6180
+ ` Solutions: 1. Retry with backoff ` +
6181
+ `2. Check your Vertex AI quotas in Google Cloud Console (shared-capacity 429s can occur below quota) ` +
6182
+ `3. Try a different region or model ` +
6183
+ `4. Request provisioned throughput for sustained load`);
6184
+ },
6185
+ },
6186
+ {
6187
+ match: (ctx) => /ECONNRESET|ENOTFOUND|ETIMEDOUT|ECONNREFUSED|network|connection/i.test(ctx.message),
6188
+ errorClass: NetworkError,
6189
+ message: (ctx) => `Connection error: ${ctx.message}`,
6190
+ },
6191
+ {
6192
+ match: (ctx) => /500|502|503|504|server error|Internal Server Error|INTERNAL|UNAVAILABLE/i.test(ctx.message) ||
6193
+ (statusCode !== undefined && statusCode >= 500 && statusCode < 600),
6194
+ errorClass: ProviderError,
6195
+ message: (ctx) => `Google Vertex AI server error: ${ctx.message}. Please try again later.`,
6196
+ },
6197
+ {
6198
+ match: (ctx) => /INVALID_ARGUMENT/i.test(ctx.message),
6199
+ errorClass: ProviderError,
6200
+ message: (ctx) => `Google Vertex AI Invalid Request: ${ctx.message}. ` +
6201
+ `Check: 1. Request parameters are within model limits ` +
6202
+ `2. Input text is properly formatted ` +
6203
+ `3. Temperature and other settings are valid ` +
6204
+ `4. Model supports your request type`,
6205
+ },
6206
+ {
6207
+ match: () => true,
6208
+ errorClass: ProviderError,
6209
+ message: (ctx) => `Google Vertex AI error: ${ctx.message}`,
6210
+ },
6211
+ ];
6212
+ return classifyProviderError(error, rules, this.providerName, this.modelName);
6205
6213
  }
6206
6214
  /**
6207
6215
  * Memory-safe cache management for model configurations
@@ -1,7 +1,8 @@
1
1
  import { GroqModels } from "../constants/enums.js";
2
- import { AuthenticationError, InvalidModelError, ProviderError, RateLimitError, } from "../types/index.js";
2
+ import { AuthenticationError, InvalidModelError, ProviderError, } from "../types/index.js";
3
3
  import { logger } from "../utils/logger.js";
4
4
  import { redactUrlCredentials } from "../utils/logSanitize.js";
5
+ import { classifyProviderError, DEFAULT_ERROR_RULES, } from "../utils/errorClassifier.js";
5
6
  import { createGroqConfig, getProviderModel, validateApiKey, } from "../utils/providerConfig.js";
6
7
  import { TimeoutError } from "../utils/timeout.js";
7
8
  import { OpenAIChatCompletionsProvider } from "./openaiChatCompletionsBase.js";
@@ -57,30 +58,26 @@ export class GroqProvider extends OpenAIChatCompletionsProvider {
57
58
  ];
58
59
  }
59
60
  formatProviderError(error) {
61
+ // Groq's TimeoutError maps to plain ProviderError (not NetworkError, the
62
+ // classifier's built-in default) — intercept before delegating.
60
63
  if (error instanceof TimeoutError) {
61
64
  return new ProviderError(`Groq request timed out: ${error.message}`, "groq");
62
65
  }
63
- const errorRecord = error;
64
- const message = typeof errorRecord?.message === "string"
65
- ? errorRecord.message
66
- : "Unknown error";
67
- if (message.includes("Invalid API key") ||
68
- message.includes("Authentication") ||
69
- message.includes("401") ||
70
- message.includes("invalid_api_key")) {
71
- return new AuthenticationError("Invalid Groq API key. Check GROQ_API_KEY. Get one at https://console.groq.com/keys", "groq");
72
- }
73
- if (message.includes("rate limit") || message.includes("429")) {
74
- return new RateLimitError("Groq rate limit exceeded. Free tier limits are tight; consider upgrading or backing off.", "groq");
75
- }
76
- if (message.includes("model_not_found") ||
77
- message.includes("404") ||
78
- message.includes("model_decommissioned")) {
79
- return new InvalidModelError(message.includes("model_decommissioned")
80
- ? `Groq model '${this.modelName}' was decommissioned. Pick a current model from https://console.groq.com/docs/models.`
81
- : `Groq model '${this.modelName}' not found. See https://console.groq.com/docs/models for the current catalog.`, "groq");
82
- }
83
- return new ProviderError(`Groq error: ${message}`, "groq");
66
+ const rules = [
67
+ {
68
+ match: (ctx) => ctx.statusCode === 401 ||
69
+ /Invalid API key|Authentication|invalid_api_key/i.test(ctx.message),
70
+ errorClass: AuthenticationError,
71
+ message: "Invalid Groq API key. Check GROQ_API_KEY. Get one at https://console.groq.com/keys",
72
+ },
73
+ {
74
+ match: (ctx) => /model_decommissioned/i.test(ctx.message),
75
+ errorClass: InvalidModelError,
76
+ message: (ctx) => `Groq model '${ctx.modelName}' was decommissioned. Pick a current model from https://console.groq.com/docs/models.`,
77
+ },
78
+ ...DEFAULT_ERROR_RULES,
79
+ ];
80
+ return classifyProviderError(error, rules, "groq", this.modelName);
84
81
  }
85
82
  }
86
83
  //# sourceMappingURL=groq.js.map
@@ -1,8 +1,8 @@
1
- import { AuthenticationError, InvalidModelError, NetworkError, ProviderError, RateLimitError, } from "../../types/index.js";
1
+ import { AuthenticationError, InvalidModelError, ProviderError, RateLimitError, } from "../../types/index.js";
2
+ import { classifyProviderError, DEFAULT_ERROR_RULES, } from "../../utils/errorClassifier.js";
2
3
  import { logger } from "../../utils/logger.js";
3
4
  import { redactUrlCredentials } from "../../utils/logSanitize.js";
4
5
  import { createHuggingFaceConfig, getProviderModel, validateApiKey, } from "../../utils/providerConfig.js";
5
- import { TimeoutError } from "../../utils/timeout.js";
6
6
  import { OpenAIChatCompletionsProvider } from "../openaiChatCompletionsBase.js";
7
7
  const HUGGINGFACE_DEFAULT_BASE_URL = "https://router.huggingface.co/v1";
8
8
  const getHuggingFaceApiKey = () => validateApiKey(createHuggingFaceConfig());
@@ -74,28 +74,30 @@ export class HuggingFaceProvider extends OpenAIChatCompletionsProvider {
74
74
  return toolCapableModels.some((capable) => modelName.includes(capable));
75
75
  }
76
76
  formatProviderError(error) {
77
- if (error instanceof TimeoutError) {
78
- return new NetworkError(`Request timed out: ${error.message}`, "huggingface");
79
- }
80
- const errorObj = error;
81
- const message = errorObj?.message && typeof errorObj.message === "string"
82
- ? errorObj.message
83
- : "Unknown error";
84
- // Enhanced error messages with tool calling context
85
- if (message.includes("API_TOKEN_INVALID") ||
86
- message.includes("Invalid token")) {
87
- return new AuthenticationError("Invalid HuggingFace API token. Please check your HUGGINGFACE_API_KEY environment variable.", "huggingface");
88
- }
89
- if (message.includes("rate limit")) {
90
- return new RateLimitError("HuggingFace rate limit exceeded. Consider using a paid plan or try again later.", "huggingface");
91
- }
92
- if (message.includes("model") && message.includes("not found")) {
93
- return new InvalidModelError(`HuggingFace model '${this.modelName}' not found.\n\nSuggestions:\n1. Check model name spelling\n2. Ensure model exists on HuggingFace Hub\n3. For tool calling, use: Llama-3.1-8B-Instruct, Hermes-3-Llama-3.2-3B, or CodeLlama-34b-Instruct-hf`, "huggingface");
94
- }
95
- if (message.includes("function") || message.includes("tool")) {
96
- return new ProviderError(`HuggingFace tool calling error: ${message}\n\nNotes:\n1. Ensure you're using a tool-capable model (Llama-3.1+, Hermes-3+, CodeLlama)\n2. Check that your model supports function calling\n3. Verify tool schema format is correct`, "huggingface");
97
- }
98
- return new ProviderError(`HuggingFace Provider Error: ${message}`, "huggingface");
77
+ const rules = [
78
+ {
79
+ match: (ctx) => /API_TOKEN_INVALID|Invalid token/.test(ctx.message),
80
+ errorClass: AuthenticationError,
81
+ message: "Invalid HuggingFace API token. Please check your HUGGINGFACE_API_KEY environment variable.",
82
+ },
83
+ {
84
+ match: (ctx) => /rate limit/.test(ctx.message),
85
+ errorClass: RateLimitError,
86
+ message: "HuggingFace rate limit exceeded. Consider using a paid plan or try again later.",
87
+ },
88
+ {
89
+ match: (ctx) => /model/.test(ctx.message) && /not found/.test(ctx.message),
90
+ errorClass: InvalidModelError,
91
+ message: () => `HuggingFace model '${this.modelName}' not found.\n\nSuggestions:\n1. Check model name spelling\n2. Ensure model exists on HuggingFace Hub\n3. For tool calling, use: Llama-3.1-8B-Instruct, Hermes-3-Llama-3.2-3B, or CodeLlama-34b-Instruct-hf`,
92
+ },
93
+ {
94
+ match: (ctx) => /function|tool/.test(ctx.message),
95
+ errorClass: ProviderError,
96
+ message: (ctx) => `HuggingFace tool calling error: ${ctx.message}\n\nNotes:\n1. Ensure you're using a tool-capable model (Llama-3.1+, Hermes-3+, CodeLlama)\n2. Check that your model supports function calling\n3. Verify tool schema format is correct`,
97
+ },
98
+ ...DEFAULT_ERROR_RULES,
99
+ ];
100
+ return classifyProviderError(error, rules, "huggingface", this.modelName);
99
101
  }
100
102
  }
101
103
  //# sourceMappingURL=client.js.map
@@ -2,13 +2,14 @@ import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
2
2
  import { registerRuntimeContextWindow, registerRuntimeOutputCeiling, } from "../../constants/contextWindows.js";
3
3
  import { createProxyFetch } from "../../proxy/proxyFetch.js";
4
4
  import { AuthenticationError, InvalidModelError, ModelAccessDeniedError, NetworkError, ProviderError, RateLimitError, isModelAccessDeniedMessage, parseAllowedModels, } from "../../types/index.js";
5
+ import { classifyProviderError, DEFAULT_ERROR_RULES, } from "../../utils/errorClassifier.js";
5
6
  import { isAbortError } from "../../utils/errorHandling.js";
6
7
  import { logger } from "../../utils/logger.js";
7
8
  import { redactUrlCredentials } from "../../utils/logSanitize.js";
8
9
  import { isGemini25Model as isCanonicalGemini25Model } from "../../utils/modelDetection.js";
9
10
  import { calculateCost } from "../../utils/pricing.js";
10
11
  import { getProviderModel } from "../../utils/providerConfig.js";
11
- import { createTimeoutController, TimeoutError } from "../../utils/timeout.js";
12
+ import { createTimeoutController } from "../../utils/timeout.js";
12
13
  import { stripTrailingSlash } from "../openaiChatCompletionsClient.js";
13
14
  import { OpenAIChatCompletionsProvider } from "../openaiChatCompletionsBase.js";
14
15
  const streamTracer = trace.getTracer("neurolink.provider.litellm");
@@ -320,46 +321,60 @@ export class LiteLLMProvider extends OpenAIChatCompletionsProvider {
320
321
  };
321
322
  }
322
323
  formatProviderError(error) {
323
- if (error instanceof TimeoutError) {
324
- return new NetworkError(`Request timed out: ${error.message}`, this.providerName);
325
- }
324
+ // Curator P1-1: detect "team not allowed to access model" responses and
325
+ // surface as ModelAccessDeniedError with the allowed_models array parsed
326
+ // from the body. Must run before classification (not just before the
327
+ // "API key" rule) because ModelAccessDeniedError's constructor takes an
328
+ // `{ provider, requestedModel, allowedModels }` options object rather
329
+ // than the `(message, provider?)` shape ProviderErrorRule expects, so it
330
+ // can't be expressed as a declarative rule. No realistic overlap with the
331
+ // timeout/ECONNREFUSED checks below (disjoint wording), so running this
332
+ // first is behaviorally identical to the original nesting order.
326
333
  const errorRecord = error;
327
- if (errorRecord?.name === "TimeoutError" ||
328
- (typeof errorRecord?.message === "string" &&
329
- errorRecord.message.toLowerCase().includes("timeout"))) {
330
- return new NetworkError(`Request timed out: ${errorRecord?.message || "Unknown timeout"}`, this.providerName);
331
- }
332
- if (typeof errorRecord?.message === "string") {
333
- if (errorRecord.message.includes("ECONNREFUSED") ||
334
- errorRecord.message.includes("Failed to fetch")) {
335
- return new NetworkError("LiteLLM proxy server not available. Please start the LiteLLM proxy server at " +
336
- `${process.env.LITELLM_BASE_URL || "http://localhost:4000"}`, this.providerName);
337
- }
338
- // Curator P1-1: detect "team not allowed to access model" responses and
339
- // surface as ModelAccessDeniedError with the allowed_models array parsed
340
- // from the body. Must run before the generic "API key" check because
341
- // LiteLLM phrases this as a 403 distinct from auth.
342
- if (isModelAccessDeniedMessage(errorRecord.message)) {
343
- return new ModelAccessDeniedError(errorRecord.message, {
344
- provider: this.providerName,
345
- requestedModel: this.modelName,
346
- allowedModels: parseAllowedModels(errorRecord.message),
347
- });
348
- }
349
- if (errorRecord.message.includes("API_KEY_INVALID") ||
350
- errorRecord.message.includes("Invalid API key")) {
351
- return new AuthenticationError("Invalid LiteLLM configuration. Please check your LITELLM_API_KEY environment variable.", this.providerName);
352
- }
353
- if (errorRecord.message.toLowerCase().includes("rate limit")) {
354
- return new RateLimitError("LiteLLM rate limit exceeded. Please try again later.", this.providerName);
355
- }
356
- if (errorRecord.message.toLowerCase().includes("model") &&
357
- errorRecord.message.toLowerCase().includes("not found")) {
358
- return new InvalidModelError(`Model '${this.modelName}' not available in LiteLLM proxy. ` +
359
- "Please check your LiteLLM configuration and ensure the model is configured.", this.providerName);
360
- }
334
+ if (typeof errorRecord?.message === "string" &&
335
+ isModelAccessDeniedMessage(errorRecord.message)) {
336
+ return new ModelAccessDeniedError(errorRecord.message, {
337
+ provider: this.providerName,
338
+ requestedModel: this.modelName,
339
+ allowedModels: parseAllowedModels(errorRecord.message),
340
+ });
361
341
  }
362
- return new ProviderError(`LiteLLM error: ${errorRecord?.message || "Unknown error"}`, this.providerName);
342
+ const rules = [
343
+ // Duck-typed timeout detection (name === "TimeoutError" OR message
344
+ // contains "timeout") distinct from the `instanceof TimeoutError` check
345
+ // classifyProviderError already performs first — preserved because
346
+ // some rejection paths produce a plain object/Error with that shape
347
+ // rather than a real TimeoutError instance.
348
+ {
349
+ match: (ctx) => ctx.errorName === "TimeoutError" || /timeout/i.test(ctx.message),
350
+ errorClass: NetworkError,
351
+ message: (ctx) => `Request timed out: ${ctx.message}`,
352
+ },
353
+ {
354
+ match: (ctx) => /ECONNREFUSED|Failed to fetch/.test(ctx.message),
355
+ errorClass: NetworkError,
356
+ message: () => "LiteLLM proxy server not available. Please start the LiteLLM proxy server at " +
357
+ `${process.env.LITELLM_BASE_URL || "http://localhost:4000"}`,
358
+ },
359
+ {
360
+ match: (ctx) => /API_KEY_INVALID|Invalid API key/.test(ctx.message),
361
+ errorClass: AuthenticationError,
362
+ message: "Invalid LiteLLM configuration. Please check your LITELLM_API_KEY environment variable.",
363
+ },
364
+ {
365
+ match: (ctx) => /rate limit/i.test(ctx.message),
366
+ errorClass: RateLimitError,
367
+ message: "LiteLLM rate limit exceeded. Please try again later.",
368
+ },
369
+ {
370
+ match: (ctx) => /model/i.test(ctx.message) && /not found/i.test(ctx.message),
371
+ errorClass: InvalidModelError,
372
+ message: () => `Model '${this.modelName}' not available in LiteLLM proxy. ` +
373
+ "Please check your LiteLLM configuration and ensure the model is configured.",
374
+ },
375
+ ...DEFAULT_ERROR_RULES,
376
+ ];
377
+ return classifyProviderError(error, rules, this.providerName, this.modelName);
363
378
  }
364
379
  /**
365
380
  * Get available models from LiteLLM proxy `/v1/models` endpoint.
@@ -1,7 +1,7 @@
1
1
  import { NetworkError, ProviderError } from "../types/index.js";
2
+ import { classifyProviderError, DEFAULT_ERROR_RULES, } from "../utils/errorClassifier.js";
2
3
  import { logger } from "../utils/logger.js";
3
4
  import { redactUrlCredentials } from "../utils/logSanitize.js";
4
- import { TimeoutError } from "../utils/timeout.js";
5
5
  import { OpenAIChatCompletionsProvider } from "./openaiChatCompletionsBase.js";
6
6
  const LLAMACPP_DEFAULT_BASE_URL = "http://localhost:8080/v1";
7
7
  const LLAMACPP_PLACEHOLDER_KEY = "llamacpp";
@@ -52,26 +52,29 @@ export class LlamaCppProvider extends OpenAIChatCompletionsProvider {
52
52
  return ["loaded-model"];
53
53
  }
54
54
  formatProviderError(error) {
55
- if (error instanceof TimeoutError) {
56
- return new NetworkError(`Request timed out: ${error.message}`, "llamacpp");
57
- }
55
+ // `code`/`cause.code` aren't part of ProviderErrorContext, so they're read
56
+ // off the raw error here (mirrors ollama's `responseBody` extraction) for
57
+ // the ECONNREFUSED rule below, which the pre-migration code also checked
58
+ // via a duck-typed error code in addition to the message text.
58
59
  const errorRecord = error;
59
- const message = typeof errorRecord?.message === "string"
60
- ? errorRecord.message
61
- : "Unknown error";
62
60
  const cause = errorRecord?.cause ?? {};
63
61
  const code = (errorRecord?.code ?? cause?.code);
64
- if (code === "ECONNREFUSED" ||
65
- message.includes("ECONNREFUSED") ||
66
- message.includes("Failed to fetch") ||
67
- message.includes("fetch failed")) {
68
- return new NetworkError(`llama.cpp server not reachable at ${redactUrlCredentials(this.config.baseURL)}. ` +
69
- "Start it with: ./llama-server -m model.gguf --port 8080", "llamacpp");
70
- }
71
- if (message.includes("400")) {
72
- return new ProviderError("llama.cpp rejected the request. Common cause: model doesn't support tools (start llama-server with --jinja for tool support).", "llamacpp");
73
- }
74
- return new ProviderError(`llama.cpp error: ${message}`, "llamacpp");
62
+ const rules = [
63
+ {
64
+ match: (ctx) => code === "ECONNREFUSED" ||
65
+ /ECONNREFUSED|Failed to fetch|fetch failed/.test(ctx.message),
66
+ errorClass: NetworkError,
67
+ message: () => `llama.cpp server not reachable at ${redactUrlCredentials(this.config.baseURL)}. ` +
68
+ "Start it with: ./llama-server -m model.gguf --port 8080",
69
+ },
70
+ {
71
+ match: (ctx) => /400/.test(ctx.message),
72
+ errorClass: ProviderError,
73
+ message: "llama.cpp rejected the request. Common cause: model doesn't support tools (start llama-server with --jinja for tool support).",
74
+ },
75
+ ...DEFAULT_ERROR_RULES,
76
+ ];
77
+ return classifyProviderError(error, rules, "llamacpp", this.modelName);
75
78
  }
76
79
  }
77
80
  //# sourceMappingURL=llamaCpp.js.map
@@ -1,7 +1,7 @@
1
- import { InvalidModelError, NetworkError, ProviderError, } from "../types/index.js";
1
+ import { InvalidModelError, NetworkError } from "../types/index.js";
2
+ import { classifyProviderError, DEFAULT_ERROR_RULES, } from "../utils/errorClassifier.js";
2
3
  import { logger } from "../utils/logger.js";
3
4
  import { redactUrlCredentials } from "../utils/logSanitize.js";
4
- import { TimeoutError } from "../utils/timeout.js";
5
5
  import { OpenAIChatCompletionsProvider } from "./openaiChatCompletionsBase.js";
6
6
  const LM_STUDIO_DEFAULT_BASE_URL = "http://localhost:1234/v1";
7
7
  const LM_STUDIO_PLACEHOLDER_KEY = "lm-studio";
@@ -47,26 +47,29 @@ export class LMStudioProvider extends OpenAIChatCompletionsProvider {
47
47
  return FALLBACK_MODEL;
48
48
  }
49
49
  formatProviderError(error) {
50
- if (error instanceof TimeoutError) {
51
- return new NetworkError(`Request timed out: ${error.message}`, "lm-studio");
52
- }
50
+ // `code`/`cause.code` aren't part of ProviderErrorContext, so they're read
51
+ // off the raw error here (mirrors ollama's `responseBody` extraction) for
52
+ // the ECONNREFUSED rule below, which the pre-migration code also checked
53
+ // via a duck-typed error code in addition to the message text.
53
54
  const errorRecord = error;
54
- const message = typeof errorRecord?.message === "string"
55
- ? errorRecord.message
56
- : "Unknown error";
57
55
  const cause = errorRecord?.cause ?? {};
58
56
  const code = (errorRecord?.code ?? cause?.code);
59
- if (code === "ECONNREFUSED" ||
60
- message.includes("ECONNREFUSED") ||
61
- message.includes("Failed to fetch") ||
62
- message.includes("fetch failed")) {
63
- return new NetworkError(`LM Studio server not reachable at ${redactUrlCredentials(this.config.baseURL)}. ` +
64
- `Open the LM Studio app, load a model, and click "Start Server".`, "lm-studio");
65
- }
66
- if (message.includes("model_not_found") || message.includes("404")) {
67
- return new InvalidModelError(`LM Studio model '${this.modelName}' is not loaded. Load it in the LM Studio app first.`, "lm-studio");
68
- }
69
- return new ProviderError(`LM Studio error: ${message}`, "lm-studio");
57
+ const rules = [
58
+ {
59
+ match: (ctx) => code === "ECONNREFUSED" ||
60
+ /ECONNREFUSED|Failed to fetch|fetch failed/.test(ctx.message),
61
+ errorClass: NetworkError,
62
+ message: () => `LM Studio server not reachable at ${redactUrlCredentials(this.config.baseURL)}. ` +
63
+ `Open the LM Studio app, load a model, and click "Start Server".`,
64
+ },
65
+ {
66
+ match: (ctx) => /model_not_found|404/.test(ctx.message),
67
+ errorClass: InvalidModelError,
68
+ message: () => `LM Studio model '${this.modelName}' is not loaded. Load it in the LM Studio app first.`,
69
+ },
70
+ ...DEFAULT_ERROR_RULES,
71
+ ];
72
+ return classifyProviderError(error, rules, "lm-studio", this.modelName);
70
73
  }
71
74
  async validateConfiguration() {
72
75
  return this.probeModelsEndpoint(this.config.apiKey && this.config.apiKey !== LM_STUDIO_PLACEHOLDER_KEY