@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
@@ -1,10 +1,10 @@
1
1
  import { AIProviderName } from "../../constants/enums.js";
2
2
  import { AuthenticationError, InvalidModelError, NetworkError, ProviderError, RateLimitError, } from "../../types/index.js";
3
3
  import { createProxyFetch } from "../../proxy/proxyFetch.js";
4
+ import { classifyProviderError, DEFAULT_ERROR_RULES, } from "../../utils/errorClassifier.js";
4
5
  import { isAbortError } from "../../utils/errorHandling.js";
5
6
  import { logger } from "../../utils/logger.js";
6
7
  import { redactUrlCredentials } from "../../utils/logSanitize.js";
7
- import { TimeoutError } from "../../utils/timeout.js";
8
8
  import { OpenAIChatCompletionsProvider } from "../openaiChatCompletionsBase.js";
9
9
  import { stripTrailingSlash } from "../openaiChatCompletionsClient.js";
10
10
  import { getDefaultOpenRouterModel } from "./utils.js";
@@ -87,63 +87,69 @@ export class OpenRouterProvider extends OpenAIChatCompletionsProvider {
87
87
  return getDefaultOpenRouterModel();
88
88
  }
89
89
  formatProviderError(error) {
90
- if (error instanceof TimeoutError) {
91
- return new NetworkError(`Request timed out: ${error.message}`, "openrouter");
92
- }
93
- // Check for timeout by error name and message as fallback
94
- const errorRecord = error;
95
- if (errorRecord?.name === "TimeoutError" ||
96
- (typeof errorRecord?.message === "string" &&
97
- errorRecord.message.includes("Timeout"))) {
98
- return new NetworkError(`Request timed out: ${errorRecord?.message || "Unknown timeout"}`, "openrouter");
99
- }
100
- if (typeof errorRecord?.message === "string") {
101
- if (errorRecord.message.includes("ECONNREFUSED") ||
102
- errorRecord.message.includes("Failed to fetch")) {
103
- return new NetworkError("OpenRouter API not available. Please check your network connection and try again.", "openrouter");
104
- }
105
- if (errorRecord.message.includes("API_KEY_INVALID") ||
106
- errorRecord.message.includes("Invalid API key") ||
107
- errorRecord.message.includes("invalid_api_key") ||
108
- errorRecord.message.includes("Unauthorized")) {
109
- return new AuthenticationError("Invalid OpenRouter API key. Please check your OPENROUTER_API_KEY environment variable. " +
110
- "Get your key at https://openrouter.ai/keys", "openrouter");
111
- }
112
- if (errorRecord.message.includes("rate limit")) {
113
- return new RateLimitError("OpenRouter rate limit exceeded. Please try again later or upgrade your account at https://openrouter.ai/credits", "openrouter");
114
- }
115
- if (errorRecord.message.includes("model") &&
116
- errorRecord.message.includes("not found")) {
117
- return new InvalidModelError(`Model '${this.modelName}' not available on OpenRouter. ` +
118
- "Browse available models at https://openrouter.ai/models", "openrouter");
119
- }
120
- if (errorRecord.message.includes("insufficient_credits")) {
121
- return new ProviderError("Insufficient OpenRouter credits. Add credits at https://openrouter.ai/credits", "openrouter");
122
- }
90
+ const rules = [
91
+ // Duck-typed timeout detection (name === "TimeoutError" OR message
92
+ // contains "Timeout", case-sensitive — matches the original's
93
+ // `.includes("Timeout")`) distinct from the `instanceof TimeoutError`
94
+ // check classifyProviderError already performs first.
95
+ {
96
+ match: (ctx) => ctx.errorName === "TimeoutError" || /Timeout/.test(ctx.message),
97
+ errorClass: NetworkError,
98
+ message: (ctx) => `Request timed out: ${ctx.message}`,
99
+ },
100
+ {
101
+ match: (ctx) => /ECONNREFUSED|Failed to fetch/.test(ctx.message),
102
+ errorClass: NetworkError,
103
+ message: "OpenRouter API not available. Please check your network connection and try again.",
104
+ },
105
+ {
106
+ match: (ctx) => /API_KEY_INVALID|Invalid API key|invalid_api_key|Unauthorized/.test(ctx.message),
107
+ errorClass: AuthenticationError,
108
+ message: "Invalid OpenRouter API key. Please check your OPENROUTER_API_KEY environment variable. " +
109
+ "Get your key at https://openrouter.ai/keys",
110
+ },
111
+ {
112
+ match: (ctx) => /rate limit/.test(ctx.message),
113
+ errorClass: RateLimitError,
114
+ message: "OpenRouter rate limit exceeded. Please try again later or upgrade your account at https://openrouter.ai/credits",
115
+ },
116
+ {
117
+ match: (ctx) => /model/.test(ctx.message) && /not found/.test(ctx.message),
118
+ errorClass: InvalidModelError,
119
+ message: () => `Model '${this.modelName}' not available on OpenRouter. ` +
120
+ "Browse available models at https://openrouter.ai/models",
121
+ },
122
+ {
123
+ match: (ctx) => /insufficient_credits/.test(ctx.message),
124
+ errorClass: ProviderError,
125
+ message: "Insufficient OpenRouter credits. Add credits at https://openrouter.ai/credits",
126
+ },
123
127
  // "No endpoints found" — model temporarily unavailable or unsupported
124
128
  // parameters. Distinct from tool errors: it can happen on any request
125
129
  // when the model has no available providers on OpenRouter.
126
- if (errorRecord.message.includes("No endpoints found")) {
127
- return new InvalidModelError(`No endpoints found for model '${this.modelName}' on OpenRouter. ` +
130
+ {
131
+ match: (ctx) => /No endpoints found/.test(ctx.message),
132
+ errorClass: InvalidModelError,
133
+ message: () => `No endpoints found for model '${this.modelName}' on OpenRouter. ` +
128
134
  "The model may be temporarily unavailable or does not support the requested parameters. " +
129
- "Try a different model or check availability at https://openrouter.ai/models", "openrouter");
130
- }
135
+ "Try a different model or check availability at https://openrouter.ai/models",
136
+ },
131
137
  // Tool/function calling errors
132
- if (errorRecord.message.includes("tool use") ||
133
- errorRecord.message.includes("tool_use") ||
134
- errorRecord.message.includes("function_call") ||
135
- errorRecord.message.includes("tools are not supported")) {
136
- return new ProviderError(`Model '${this.modelName}' does not support tool calling. ` +
138
+ {
139
+ match: (ctx) => /tool use|tool_use|function_call|tools are not supported/.test(ctx.message),
140
+ errorClass: ProviderError,
141
+ message: () => `Model '${this.modelName}' does not support tool calling. ` +
137
142
  "Use a tool-capable model like:\n" +
138
143
  " • google/gemini-2.0-flash-exp:free (free)\n" +
139
144
  " • meta-llama/llama-3.3-70b-instruct:free (free)\n" +
140
145
  " • anthropic/claude-3.7-sonnet (paid)\n" +
141
146
  " • openai/gpt-4o (paid)\n" +
142
147
  "Or use --disableTools flag. " +
143
- "See all tool-capable models at https://openrouter.ai/models?supported_parameters=tools", "openrouter");
144
- }
145
- }
146
- return new ProviderError(`OpenRouter error: ${errorRecord?.message || "Unknown error"}`, "openrouter");
148
+ "See all tool-capable models at https://openrouter.ai/models?supported_parameters=tools",
149
+ },
150
+ ...DEFAULT_ERROR_RULES,
151
+ ];
152
+ return classifyProviderError(error, rules, "openrouter", this.modelName);
147
153
  }
148
154
  // ===========================================================================
149
155
  // Optional hooks — provider-specific quirks
@@ -17,6 +17,7 @@
17
17
  * Nothing here imports from "ai" or "@ai-sdk/*". The base class is a
18
18
  * direct HTTP client + multi-step tool-execution loop driven by SSE.
19
19
  */
20
+ import { trace } from "@opentelemetry/api";
20
21
  import { getAvailableInputTokens, getRuntimeContextWindow, getRuntimeOutputCeiling, registerRuntimeContextWindow, } from "../constants/contextWindows.js";
21
22
  import { guardOpenAICompatConversation } from "../context/openaiCompatLoopGuard.js";
22
23
  import { isContextOverflowError, parseProviderOverflowDetails, } from "../context/errorDetection.js";
@@ -33,6 +34,7 @@ import { composeAbortSignalsScoped, createTimeoutController, mergeAbortSignals,
33
34
  import { emitToolEndFromStepFinish } from "../utils/toolEndEmitter.js";
34
35
  import { resolveToolChoice } from "../utils/toolChoice.js";
35
36
  import { transformToolExecutions } from "../utils/transformationUtils.js";
37
+ import { withProviderRetry } from "../utils/providerRetry.js";
36
38
  import { resolveDeferredTool } from "../tools/toolDiscovery.js";
37
39
  import { buildAPIError, buildBody, buildToolsForOpenAI, buildWireToolNameMaps, createChunkQueue, createDeferredAnalytics, ensureJsonWordInBody, estimateWireTokens, mapNeuroLinkToolChoice, mergeUsage, messageBuilderToOpenAI, parseSSEStream, stringifyToolOutput, stripTrailingSlash, v3ResponseFormatToOpenAI, v3ToolChoiceToOpenAI, v3ToolsToOpenAI, } from "./openaiChatCompletionsClient.js";
38
40
  /**
@@ -932,22 +934,44 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
932
934
  : {}),
933
935
  streaming: true,
934
936
  }), args.modelId));
935
- let res = await args.fetchImpl(args.url, {
936
- method: "POST",
937
- headers: {
938
- "Content-Type": "application/json",
939
- ...this.getAuthHeaders(),
940
- },
941
- body: JSON.stringify(body),
942
- ...(args.abortSignal ? { signal: args.abortSignal } : {}),
943
- });
944
- if (!res.ok) {
945
- const apiErr = await buildAPIError(args.url, body, res);
946
- // One-shot 400 retry — overflow corrector first (re-fits max_tokens
947
- // from the provider's own numbers + self-heals the window registry),
948
- // then the subclass hook (e.g. NIM strips chat_template /
949
- // reasoning_budget when a model rejects them).
950
- const retryBody = res.status === 400
937
+ // The initial fetch gets 429/5xx retry-with-backoff via the same
938
+ // primitive the non-streaming path already uses (withProviderRetry).
939
+ // `doFetch` throws the classified APIError (buildAPIError attaches
940
+ // .statusCode + .responseHeaders, which withProviderRetry's duck-typing
941
+ // reads directly) so a non-ok response is what drives the retry
942
+ // decision, not a return value.
943
+ const doFetch = async () => {
944
+ const attemptRes = await args.fetchImpl(args.url, {
945
+ method: "POST",
946
+ headers: {
947
+ "Content-Type": "application/json",
948
+ ...this.getAuthHeaders(),
949
+ },
950
+ body: JSON.stringify(body),
951
+ ...(args.abortSignal ? { signal: args.abortSignal } : {}),
952
+ });
953
+ if (!attemptRes.ok) {
954
+ throw await buildAPIError(args.url, body, attemptRes);
955
+ }
956
+ return attemptRes;
957
+ };
958
+ let res;
959
+ try {
960
+ res = await withProviderRetry(doFetch, trace.getActiveSpan() ?? undefined, `${this.providerName} stream`);
961
+ }
962
+ catch (err) {
963
+ // The one-shot 400 context-overflow fallback lives outside
964
+ // withProviderRetry (400 isn't retryable there anyway — see
965
+ // isRetryableProviderError), so it fires on the classified error's
966
+ // .statusCode. The raw Response is no longer in scope here: it was
967
+ // consumed inside doFetch's closure, either returned on success or
968
+ // discarded after buildAPIError read its body on failure.
969
+ const apiErr = err;
970
+ // Overflow corrector first (re-fits max_tokens from the provider's
971
+ // own numbers + self-heals the window registry), then the subclass
972
+ // hook (e.g. NIM strips chat_template / reasoning_budget when a model
973
+ // rejects them).
974
+ const retryBody = apiErr.statusCode === 400
951
975
  ? (this.correctBodyAfterContextOverflow(body, apiErr) ??
952
976
  this.adjustBodyAfter400(body, apiErr))
953
977
  : undefined;
@@ -1,7 +1,7 @@
1
- import { AuthenticationError, InvalidModelError, NetworkError, ProviderError, RateLimitError, } from "../../types/index.js";
1
+ import { AuthenticationError, InvalidModelError, NetworkError, } from "../../types/index.js";
2
2
  import { logger } from "../../utils/logger.js";
3
3
  import { redactUrlCredentials } from "../../utils/logSanitize.js";
4
- import { TimeoutError } from "../../utils/timeout.js";
4
+ import { classifyProviderError, DEFAULT_ERROR_RULES, } from "../../utils/errorClassifier.js";
5
5
  import { OpenAIChatCompletionsProvider } from "../openaiChatCompletionsBase.js";
6
6
  import { FALLBACK_OPENAI_COMPATIBLE_MODEL } from "./constants.js";
7
7
  import { getDefaultOpenAICompatibleModel, getOpenAICompatibleConfig, } from "./utils.js";
@@ -52,35 +52,39 @@ export class OpenAICompatibleProvider extends OpenAIChatCompletionsProvider {
52
52
  ];
53
53
  }
54
54
  formatProviderError(error) {
55
- if (error instanceof TimeoutError) {
56
- return new NetworkError(`Request timed out: ${error.message}`, "openai-compatible");
57
- }
58
- const errorRecord = error;
59
- if (errorRecord?.name === "TimeoutError" ||
60
- (typeof errorRecord?.message === "string" &&
61
- errorRecord.message.includes("Timeout"))) {
62
- return new NetworkError(`Request timed out: ${errorRecord?.message || "Unknown timeout"}`, "openai-compatible");
63
- }
64
- if (typeof errorRecord?.message === "string") {
65
- if (errorRecord.message.includes("ECONNREFUSED") ||
66
- errorRecord.message.includes("Failed to fetch")) {
67
- return new NetworkError(`OpenAI Compatible endpoint not available. Please check your OPENAI_COMPATIBLE_BASE_URL: ${redactUrlCredentials(this.config.baseURL)}`, "openai-compatible");
68
- }
69
- if (errorRecord.message.includes("API_KEY_INVALID") ||
70
- errorRecord.message.includes("Invalid API key") ||
71
- errorRecord.message.includes("Unauthorized")) {
72
- return new AuthenticationError("Invalid OpenAI Compatible API key. Please check your OPENAI_COMPATIBLE_API_KEY environment variable.", "openai-compatible");
73
- }
74
- if (errorRecord.message.includes("rate limit")) {
75
- return new RateLimitError("OpenAI Compatible rate limit exceeded. Please try again later.", "openai-compatible");
76
- }
77
- if (errorRecord.message.includes("model") &&
78
- (errorRecord.message.includes("not found") ||
79
- errorRecord.message.includes("does not exist"))) {
80
- return new InvalidModelError(`Model '${this.modelName}' not available on OpenAI Compatible endpoint. ` +
81
- "Please check available models or use getAvailableModels() to see supported models.", "openai-compatible");
82
- }
83
- }
84
- return new ProviderError(`OpenAI Compatible error: ${errorRecord?.message || "Unknown error"}`, "openai-compatible");
55
+ const rules = [
56
+ // Duck-typed timeout check (name === "TimeoutError" or a "Timeout"
57
+ // substring) — broader than classifyProviderError's built-in
58
+ // `instanceof TimeoutError` handling, so it's preserved as an
59
+ // explicit rule rather than relying on the default.
60
+ {
61
+ match: (ctx) => ctx.errorName === "TimeoutError" || /Timeout/.test(ctx.message),
62
+ errorClass: NetworkError,
63
+ message: (ctx) => `Request timed out: ${ctx.message}`,
64
+ },
65
+ {
66
+ match: (ctx) => /ECONNREFUSED|Failed to fetch/i.test(ctx.message),
67
+ errorClass: NetworkError,
68
+ message: () => `OpenAI Compatible endpoint not available. Please check your OPENAI_COMPATIBLE_BASE_URL: ${redactUrlCredentials(this.config.baseURL)}`,
69
+ },
70
+ {
71
+ match: (ctx) => ctx.statusCode === 401 ||
72
+ /API_KEY_INVALID|Invalid API key|Unauthorized/i.test(ctx.message),
73
+ errorClass: AuthenticationError,
74
+ message: "Invalid OpenAI Compatible API key. Please check your OPENAI_COMPATIBLE_API_KEY environment variable.",
75
+ },
76
+ // "does not exist" isn't covered by DEFAULT_ERROR_RULES's
77
+ // model_not_found/"model not found" pattern — preserved verbatim.
78
+ {
79
+ match: (ctx) => /model/i.test(ctx.message) &&
80
+ (/not found/i.test(ctx.message) ||
81
+ /does not exist/i.test(ctx.message)),
82
+ errorClass: InvalidModelError,
83
+ message: (ctx) => `Model '${ctx.modelName}' not available on OpenAI Compatible endpoint. ` +
84
+ "Please check available models or use getAvailableModels() to see supported models.",
85
+ },
86
+ ...DEFAULT_ERROR_RULES,
87
+ ];
88
+ return classifyProviderError(error, rules, "openai-compatible", this.modelName);
85
89
  }
86
90
  }
@@ -1,9 +1,9 @@
1
1
  import { PerplexityModels } from "../constants/enums.js";
2
- import { AuthenticationError, InvalidModelError, NetworkError, ProviderError, RateLimitError, } from "../types/index.js";
2
+ import { AuthenticationError } 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 { createPerplexityConfig, getProviderModel, validateApiKey, } from "../utils/providerConfig.js";
6
- import { TimeoutError } from "../utils/timeout.js";
7
7
  import { OpenAIChatCompletionsProvider } from "./openaiChatCompletionsBase.js";
8
8
  const PERPLEXITY_DEFAULT_BASE_URL = "https://api.perplexity.ai";
9
9
  const getPerplexityApiKey = () => validateApiKey(createPerplexityConfig());
@@ -54,24 +54,15 @@ export class PerplexityProvider extends OpenAIChatCompletionsProvider {
54
54
  ];
55
55
  }
56
56
  formatProviderError(error) {
57
- if (error instanceof TimeoutError) {
58
- return new NetworkError(`Request timed out: ${error.message}`, "perplexity");
59
- }
60
- const errorRecord = error;
61
- const message = typeof errorRecord?.message === "string"
62
- ? errorRecord.message
63
- : "Unknown error";
64
- if (message.includes("Invalid API key") ||
65
- message.includes("Authentication") ||
66
- message.includes("401")) {
67
- return new AuthenticationError("Invalid Perplexity API key. Get one at https://www.perplexity.ai/settings/api", "perplexity");
68
- }
69
- if (message.includes("rate limit") || message.includes("429")) {
70
- return new RateLimitError("Perplexity rate limit exceeded. Back off and retry.", "perplexity");
71
- }
72
- if (message.includes("model_not_found") || message.includes("404")) {
73
- return new InvalidModelError(`Perplexity model '${this.modelName}' not found. Use sonar, sonar-pro, sonar-reasoning, sonar-reasoning-pro, or sonar-deep-research.`, "perplexity");
74
- }
75
- return new ProviderError(`Perplexity error: ${message}`, "perplexity");
57
+ const rules = [
58
+ {
59
+ match: (ctx) => ctx.statusCode === 401 ||
60
+ /Invalid API key|Authentication/i.test(ctx.message),
61
+ errorClass: AuthenticationError,
62
+ message: "Invalid Perplexity API key. Get one at https://www.perplexity.ai/settings/api",
63
+ },
64
+ ...DEFAULT_ERROR_RULES,
65
+ ];
66
+ return classifyProviderError(error, rules, "perplexity", this.modelName);
76
67
  }
77
68
  }
@@ -153,7 +153,7 @@ export declare class SageMakerLanguageModel implements SageMakerAsLanguageModel
153
153
  provider: string;
154
154
  specificationVersion: "v2";
155
155
  endpointName: string;
156
- modelType: "huggingface" | "mistral" | "custom" | "llama" | "claude" | "jumpstart" | undefined;
156
+ modelType: "huggingface" | "mistral" | "custom" | "claude" | "llama" | "jumpstart" | undefined;
157
157
  region: string;
158
158
  };
159
159
  /**
@@ -200,7 +200,7 @@ export declare class SageMakerLanguageModel implements SageMakerAsLanguageModel
200
200
  provider: string;
201
201
  specificationVersion: "v2";
202
202
  endpointName: string;
203
- modelType: "huggingface" | "mistral" | "custom" | "llama" | "claude" | "jumpstart" | undefined;
203
+ modelType: "huggingface" | "mistral" | "custom" | "claude" | "llama" | "jumpstart" | undefined;
204
204
  region: string;
205
205
  };
206
206
  }
@@ -1,9 +1,9 @@
1
1
  import { TogetherAIModels } from "../constants/enums.js";
2
- import { AuthenticationError, InvalidModelError, NetworkError, ProviderError, RateLimitError, } from "../types/index.js";
2
+ import { AuthenticationError } 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 { createTogetherAIConfig, getProviderModel, validateApiKey, } from "../utils/providerConfig.js";
6
- import { TimeoutError } from "../utils/timeout.js";
7
7
  import { OpenAIChatCompletionsProvider } from "./openaiChatCompletionsBase.js";
8
8
  const TOGETHER_DEFAULT_BASE_URL = "https://api.together.xyz/v1";
9
9
  const getTogetherApiKey = () => validateApiKey(createTogetherAIConfig());
@@ -58,24 +58,15 @@ export class TogetherAIProvider extends OpenAIChatCompletionsProvider {
58
58
  ];
59
59
  }
60
60
  formatProviderError(error) {
61
- if (error instanceof TimeoutError) {
62
- return new NetworkError(`Request timed out: ${error.message}`, "together-ai");
63
- }
64
- const errorRecord = error;
65
- const message = typeof errorRecord?.message === "string"
66
- ? errorRecord.message
67
- : "Unknown error";
68
- if (message.includes("Invalid API key") ||
69
- message.includes("Authentication") ||
70
- message.includes("401")) {
71
- return new AuthenticationError("Invalid Together AI API key. Get one at https://api.together.xyz/settings/api-keys", "together-ai");
72
- }
73
- if (message.includes("rate limit") || message.includes("429")) {
74
- return new RateLimitError("Together AI rate limit exceeded. Back off and retry.", "together-ai");
75
- }
76
- if (message.includes("model_not_found") || message.includes("404")) {
77
- return new InvalidModelError(`Together AI model '${this.modelName}' not found. Browse the catalog at https://api.together.xyz/models`, "together-ai");
78
- }
79
- return new ProviderError(`Together AI error: ${message}`, "together-ai");
61
+ const rules = [
62
+ {
63
+ match: (ctx) => ctx.statusCode === 401 ||
64
+ /Invalid API key|Authentication/i.test(ctx.message),
65
+ errorClass: AuthenticationError,
66
+ message: "Invalid Together AI API key. Get one at https://api.together.xyz/settings/api-keys",
67
+ },
68
+ ...DEFAULT_ERROR_RULES,
69
+ ];
70
+ return classifyProviderError(error, rules, "together-ai", this.modelName);
80
71
  }
81
72
  }
@@ -1,9 +1,9 @@
1
1
  import { XaiModels } from "../constants/enums.js";
2
- import { AuthenticationError, InvalidModelError, NetworkError, ProviderError, RateLimitError, } from "../types/index.js";
2
+ import { AuthenticationError, 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 { createXaiConfig, getProviderModel, validateApiKey, } from "../utils/providerConfig.js";
6
- import { TimeoutError } from "../utils/timeout.js";
7
7
  import { OpenAIChatCompletionsProvider } from "./openaiChatCompletionsBase.js";
8
8
  const XAI_DEFAULT_BASE_URL = "https://api.x.ai/v1";
9
9
  const getXaiApiKey = () => validateApiKey(createXaiConfig());
@@ -57,29 +57,20 @@ export class XaiProvider extends OpenAIChatCompletionsProvider {
57
57
  ];
58
58
  }
59
59
  formatProviderError(error) {
60
- if (error instanceof TimeoutError) {
61
- return new NetworkError(`Request timed out: ${error.message}`, "xai");
62
- }
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 xAI API key. Please check your XAI_API_KEY environment variable. Get one at https://console.x.ai/", "xai");
72
- }
73
- if (message.includes("rate limit") || message.includes("429")) {
74
- return new RateLimitError("xAI rate limit exceeded. Back off and retry.", "xai");
75
- }
76
- if (message.includes("model_not_found") || message.includes("404")) {
77
- return new InvalidModelError(`xAI model '${this.modelName}' not found. Use grok-2-latest, grok-3, grok-3-mini, grok-2-vision-latest, or grok-beta.`, "xai");
78
- }
79
- if (message.includes("insufficient_quota") ||
80
- message.includes("quota exceeded")) {
81
- return new ProviderError("xAI account has insufficient quota. Top up at https://console.x.ai/", "xai");
82
- }
83
- return new ProviderError(`xAI error: ${message}`, "xai");
60
+ const rules = [
61
+ {
62
+ match: (ctx) => ctx.statusCode === 401 ||
63
+ /Invalid API key|Authentication|invalid_api_key/i.test(ctx.message),
64
+ errorClass: AuthenticationError,
65
+ message: "Invalid xAI API key. Please check your XAI_API_KEY environment variable. Get one at https://console.x.ai/",
66
+ },
67
+ {
68
+ match: (ctx) => /insufficient_quota|quota exceeded/i.test(ctx.message),
69
+ errorClass: ProviderError,
70
+ message: "xAI account has insufficient quota. Top up at https://console.x.ai/",
71
+ },
72
+ ...DEFAULT_ERROR_RULES,
73
+ ];
74
+ return classifyProviderError(error, rules, "xai", this.modelName);
84
75
  }
85
76
  }
@@ -88,7 +88,7 @@ export declare class RateLimitError extends ServerAdapterError {
88
88
  /**
89
89
  * Timeout error
90
90
  */
91
- export declare class TimeoutError extends ServerAdapterError {
91
+ export declare class ServerTimeoutError extends ServerAdapterError {
92
92
  constructor(timeoutMs: number, operation?: string, requestId?: string);
93
93
  }
94
94
  /**
@@ -206,7 +206,7 @@ export class RateLimitError extends ServerAdapterError {
206
206
  /**
207
207
  * Timeout error
208
208
  */
209
- export class TimeoutError extends ServerAdapterError {
209
+ export class ServerTimeoutError extends ServerAdapterError {
210
210
  constructor(timeoutMs, operation, requestId) {
211
211
  super(`Operation timed out after ${timeoutMs}ms${operation ? `: ${operation}` : ""}`, ServerAdapterErrorCode.TIMEOUT, {
212
212
  category: ErrorCategory.EXECUTION,
@@ -215,7 +215,7 @@ export class TimeoutError extends ServerAdapterError {
215
215
  requestId,
216
216
  details: { timeoutMs, operation },
217
217
  });
218
- this.name = "TimeoutError";
218
+ this.name = "ServerTimeoutError";
219
219
  }
220
220
  }
221
221
  /**
@@ -27,7 +27,7 @@ export { ExpressServerAdapter } from "./adapters/expressAdapter.js";
27
27
  export { FastifyServerAdapter } from "./adapters/fastifyAdapter.js";
28
28
  export { HonoServerAdapter } from "./adapters/honoAdapter.js";
29
29
  export { KoaServerAdapter } from "./adapters/koaAdapter.js";
30
- export { AlreadyRunningError, AuthenticationError, AuthorizationError, ConfigurationError, ErrorRecoveryStrategies, HandlerError, InvalidAuthenticationError, MissingDependencyError, NotRunningError, RateLimitError as ServerRateLimitError, RouteConflictError, RouteNotFoundError, ServerAdapterError, ServerStartError, ServerStopError, StreamAbortedError, StreamingError, TimeoutError, ValidationError as ServerValidationError, WebSocketConnectionError, WebSocketError, wrapError, } from "./errors.js";
30
+ export { AlreadyRunningError, AuthenticationError as ServerAuthenticationError, AuthorizationError as ServerAuthorizationError, ConfigurationError, ErrorRecoveryStrategies, HandlerError, InvalidAuthenticationError, MissingDependencyError, NotRunningError, RateLimitError as ServerRateLimitError, RouteConflictError, RouteNotFoundError, ServerAdapterError, ServerStartError, ServerStopError, StreamAbortedError, StreamingError, ServerTimeoutError, ValidationError as ServerValidationError, WebSocketConnectionError, WebSocketError, wrapError, } from "./errors.js";
31
31
  export { createServer, ServerAdapterFactory, } from "./factory/serverAdapterFactory.js";
32
32
  export { createAbortSignalMiddleware, createExpressAbortMiddleware, } from "./middleware/abortSignal.js";
33
33
  export { createAuthMiddleware, createRoleMiddleware, } from "./middleware/auth.js";
@@ -35,7 +35,7 @@ export { createCacheInvalidator, createCacheMiddleware, InMemoryCacheStore, } fr
35
35
  export { createCompressionMiddleware, createErrorHandlingMiddleware, createLoggingMiddleware, createRequestIdMiddleware, createSecurityHeadersMiddleware, createTimingMiddleware, } from "./middleware/common.js";
36
36
  export { createDeprecationMiddleware } from "./middleware/deprecation.js";
37
37
  export { createMCPBodyAttachmentMiddleware, fastifyMCPBodyHook, } from "./middleware/mcpBodyAttachment.js";
38
- export { createRateLimitMiddleware, createSlidingWindowRateLimitMiddleware, InMemoryRateLimitStore, RateLimitError, } from "./middleware/rateLimit.js";
38
+ export { createRateLimitMiddleware, createSlidingWindowRateLimitMiddleware, InMemoryRateLimitStore, } from "./middleware/rateLimit.js";
39
39
  export { CommonSchemas, createFieldValidator, createRequestValidationMiddleware, ValidationError, } from "./middleware/validation.js";
40
40
  export { AgentExecuteRequestSchema as OpenAPIAgentExecuteRequestSchema, AgentExecuteResponseSchema, AgentInputSchema, ApiKeySecurityScheme, BasicSecurityScheme, BearerSecurityScheme, CommonParameters, ConversationMessageSchema, createApiInfo, createDeleteOperation, createErrorResponse as createOpenAPIErrorResponse, createGetOperation, createHeaderParameter, createOpenAPIGenerator, createPathParameter, createPostOperation, createQueryParameter, createServer as createOpenAPIServer, createStreamingPostOperation, createStreamingResponse, createSuccessResponse, DefaultServers, ErrorResponseSchema, generateOpenAPIFromConfig, generateOpenAPISpec, HealthResponseSchema, MCPServerStatusSchema, MCPServersListResponseSchema, MCPServerToolSchema, MetricsResponseSchema, NeuroLinkApiInfo, OpenAPIGenerator, OpenAPISchemas, ProviderInfoSchema, ReadyResponseSchema, SessionSchema, SessionsListResponseSchema, StandardErrorResponses, StandardTags, TokenUsageSchema, ToolCallSchema, ToolDefinitionSchema, ToolExecuteRequestSchema as OpenAPIToolExecuteRequestSchema, ToolExecuteResponseSchema, ToolListResponseSchema, ToolParameterSchema, } from "./openapi/index.js";
41
41
  export { createAgentRoutes, createAllRoutes, createHealthRoutes, createMCPRoutes, createMemoryRoutes, createOpenApiRoutes, createToolRoutes, registerAllRoutes, } from "./routes/index.js";
@@ -38,7 +38,9 @@ export { KoaServerAdapter } from "./adapters/koaAdapter.js";
38
38
  // ============================================
39
39
  export { AlreadyRunningError,
40
40
  // Authentication/Authorization errors
41
- AuthenticationError, AuthorizationError,
41
+ // (aliased: the bare names resolve to the provider-error classes of the
42
+ // same name from types/ on the public @juspay/neurolink surface)
43
+ AuthenticationError as ServerAuthenticationError, AuthorizationError as ServerAuthorizationError,
42
44
  // Configuration errors
43
45
  ConfigurationError,
44
46
  // Error recovery
@@ -56,7 +58,7 @@ ServerStartError, ServerStopError, StreamAbortedError,
56
58
  // Streaming errors
57
59
  StreamingError,
58
60
  // Timeout errors
59
- TimeoutError,
61
+ ServerTimeoutError,
60
62
  // Validation errors
61
63
  ValidationError as ServerValidationError, WebSocketConnectionError,
62
64
  // WebSocket errors
@@ -76,7 +78,7 @@ export { createCacheInvalidator, createCacheMiddleware, InMemoryCacheStore, } fr
76
78
  export { createCompressionMiddleware, createErrorHandlingMiddleware, createLoggingMiddleware, createRequestIdMiddleware, createSecurityHeadersMiddleware, createTimingMiddleware, } from "./middleware/common.js";
77
79
  export { createDeprecationMiddleware } from "./middleware/deprecation.js";
78
80
  export { createMCPBodyAttachmentMiddleware, fastifyMCPBodyHook, } from "./middleware/mcpBodyAttachment.js";
79
- export { createRateLimitMiddleware, createSlidingWindowRateLimitMiddleware, InMemoryRateLimitStore, RateLimitError, } from "./middleware/rateLimit.js";
81
+ export { createRateLimitMiddleware, createSlidingWindowRateLimitMiddleware, InMemoryRateLimitStore, } from "./middleware/rateLimit.js";
80
82
  export { CommonSchemas, createFieldValidator, createRequestValidationMiddleware, ValidationError, } from "./middleware/validation.js";
81
83
  // ============================================
82
84
  // OpenAPI
@@ -40,10 +40,6 @@ export declare class InMemoryRateLimitStore implements RateLimitStore {
40
40
  * ```
41
41
  */
42
42
  export declare function createRateLimitMiddleware(config: RateLimitMiddlewareConfig): MiddlewareDefinition;
43
- /**
44
- * Re-export RateLimitError from errors for convenience
45
- */
46
- export { RateLimitError } from "../errors.js";
47
43
  /**
48
44
  * Create a sliding window rate limiter
49
45
  * More accurate than fixed window but slightly more complex
@@ -114,10 +114,6 @@ function defaultKeyGenerator(ctx) {
114
114
  ctx.headers["x-real-ip"] ||
115
115
  "unknown");
116
116
  }
117
- /**
118
- * Re-export RateLimitError from errors for convenience
119
- */
120
- export { RateLimitError } from "../errors.js";
121
117
  /**
122
118
  * Create a sliding window rate limiter
123
119
  * More accurate than fixed window but slightly more complex
@@ -47,6 +47,41 @@ export declare class RateLimitError extends ProviderError {
47
47
  export declare class InvalidModelError extends ProviderError {
48
48
  constructor(message: string, provider?: string);
49
49
  }
50
+ /**
51
+ * Everything a ProviderErrorRule's `match`/`message` functions can inspect
52
+ * about a raw thrown error, pre-extracted once so every rule doesn't
53
+ * re-derive the same duck-typed fields.
54
+ */
55
+ export type ProviderErrorContext = {
56
+ /** The raw thrown value, for rules that need custom inspection beyond the extracted fields. */
57
+ error: unknown;
58
+ /** `.message` off the raw error, or "Unknown error" if absent/non-string. */
59
+ message: string;
60
+ /** HTTP status code, duck-typed from `.statusCode` / `.status`. */
61
+ statusCode: number | undefined;
62
+ /** `.name` off the raw error (e.g. AWS SDK exception names like "ThrottlingException"). */
63
+ errorName: string | undefined;
64
+ /** `.code` off the raw error (e.g. AWS SDK / Node network error codes). */
65
+ errorCode: string | undefined;
66
+ /** Provider key passed to classifyProviderError (e.g. "mistral", "vertex"). */
67
+ provider: string;
68
+ /** Model name in effect for this call, when the caller has one available. */
69
+ modelName: string | undefined;
70
+ };
71
+ /**
72
+ * One row of a provider's error-classification table. Rules are tried in
73
+ * array order; the first `match` to return true wins. `errorClass` must be
74
+ * `ProviderError` or one of its subclasses (AuthenticationError,
75
+ * RateLimitError, InvalidModelError, NetworkError, ...) sharing its
76
+ * `(message, provider?)` constructor shape. `message` can be a static
77
+ * string or a function of the context, for providers that need to
78
+ * interpolate a model name, a scraped retry-delay, or an AWS error code.
79
+ */
80
+ export type ProviderErrorRule = {
81
+ match: (ctx: ProviderErrorContext) => boolean;
82
+ errorClass: new (message: string, provider?: string) => ProviderError;
83
+ message: string | ((ctx: ProviderErrorContext) => string);
84
+ };
50
85
  /**
51
86
  * Base class for OAuth-specific errors
52
87
  */