@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.
- package/CHANGELOG.md +6 -0
- package/dist/browser/neurolink.min.js +524 -524
- package/dist/cli/commands/setup.d.ts +4 -0
- package/dist/cli/commands/setup.js +19 -33
- package/dist/cli/factories/commandFactory.d.ts +1157 -1
- package/dist/cli/factories/commandFactory.js +20 -53
- package/dist/factories/providerDescriptors.d.ts +18 -0
- package/dist/factories/providerDescriptors.js +536 -0
- package/dist/factories/providerFactory.d.ts +25 -14
- package/dist/factories/providerFactory.js +46 -23
- package/dist/factories/providerRegistry.js +31 -30
- package/dist/index.d.ts +10 -1
- package/dist/index.js +13 -3
- package/dist/lib/factories/providerDescriptors.d.ts +18 -0
- package/dist/lib/factories/providerDescriptors.js +537 -0
- package/dist/lib/factories/providerFactory.d.ts +25 -14
- package/dist/lib/factories/providerFactory.js +46 -23
- package/dist/lib/factories/providerRegistry.js +31 -30
- package/dist/lib/index.d.ts +10 -1
- package/dist/lib/index.js +13 -3
- package/dist/lib/neurolink.js +25 -37
- package/dist/lib/providers/amazonBedrock/client.js +34 -16
- package/dist/lib/providers/anthropic/client.js +39 -34
- package/dist/lib/providers/azureOpenai.js +17 -15
- package/dist/lib/providers/cloudflare.js +12 -21
- package/dist/lib/providers/cohere.js +31 -25
- package/dist/lib/providers/deepseek.js +23 -26
- package/dist/lib/providers/fireworks.js +12 -21
- package/dist/lib/providers/googleAiStudio/client.d.ts +2 -0
- package/dist/lib/providers/googleAiStudio/client.js +39 -17
- package/dist/lib/providers/googleVertex/client.js +107 -99
- package/dist/lib/providers/groq.js +19 -22
- package/dist/lib/providers/huggingFace/client.js +26 -24
- package/dist/lib/providers/litellm/client.js +54 -39
- package/dist/lib/providers/llamaCpp.js +21 -18
- package/dist/lib/providers/lmStudio.js +22 -19
- package/dist/lib/providers/mistral.js +12 -24
- package/dist/lib/providers/nvidiaNim/client.js +37 -34
- package/dist/lib/providers/ollama/client.js +50 -35
- package/dist/lib/providers/openAI/client.js +37 -40
- package/dist/lib/providers/openRouter/client.js +53 -47
- package/dist/lib/providers/openaiChatCompletionsBase.js +40 -16
- package/dist/lib/providers/openaiCompatible/client.js +36 -32
- package/dist/lib/providers/perplexity.js +12 -21
- package/dist/lib/providers/togetherAi.js +12 -21
- package/dist/lib/providers/xai.js +17 -26
- package/dist/lib/server/errors.d.ts +1 -1
- package/dist/lib/server/errors.js +2 -2
- package/dist/lib/server/index.d.ts +2 -2
- package/dist/lib/server/index.js +5 -3
- package/dist/lib/server/middleware/rateLimit.d.ts +0 -4
- package/dist/lib/server/middleware/rateLimit.js +0 -4
- package/dist/lib/types/errors.d.ts +35 -0
- package/dist/lib/types/providers.d.ts +64 -0
- package/dist/lib/utils/errorClassifier.d.ts +30 -0
- package/dist/lib/utils/errorClassifier.js +94 -0
- package/dist/lib/utils/fileDetector.js +6 -43
- package/dist/lib/utils/providerHealth.d.ts +42 -7
- package/dist/lib/utils/providerHealth.js +115 -122
- package/dist/lib/utils/providerUtils.js +21 -63
- package/dist/neurolink.js +25 -37
- package/dist/providers/amazonBedrock/client.js +34 -16
- package/dist/providers/amazonSagemaker.d.ts +1 -1
- package/dist/providers/anthropic/client.js +39 -34
- package/dist/providers/azureOpenai.js +17 -15
- package/dist/providers/cloudflare.js +12 -21
- package/dist/providers/cohere.js +31 -25
- package/dist/providers/deepseek.js +23 -26
- package/dist/providers/fireworks.js +12 -21
- package/dist/providers/googleAiStudio/client.d.ts +2 -0
- package/dist/providers/googleAiStudio/client.js +39 -17
- package/dist/providers/googleVertex/client.js +107 -99
- package/dist/providers/groq.js +19 -22
- package/dist/providers/huggingFace/client.js +26 -24
- package/dist/providers/litellm/client.js +54 -39
- package/dist/providers/llamaCpp.js +21 -18
- package/dist/providers/lmStudio.js +22 -19
- package/dist/providers/mistral.js +12 -24
- package/dist/providers/nvidiaNim/client.js +37 -34
- package/dist/providers/ollama/client.js +50 -35
- package/dist/providers/openAI/client.js +37 -40
- package/dist/providers/openRouter/client.js +53 -47
- package/dist/providers/openaiChatCompletionsBase.js +40 -16
- package/dist/providers/openaiCompatible/client.js +36 -32
- package/dist/providers/perplexity.js +12 -21
- package/dist/providers/sagemaker/language-model.d.ts +2 -2
- package/dist/providers/togetherAi.js +12 -21
- package/dist/providers/xai.js +17 -26
- package/dist/server/errors.d.ts +1 -1
- package/dist/server/errors.js +2 -2
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js +5 -3
- package/dist/server/middleware/rateLimit.d.ts +0 -4
- package/dist/server/middleware/rateLimit.js +0 -4
- package/dist/types/errors.d.ts +35 -0
- package/dist/types/providers.d.ts +64 -0
- package/dist/utils/errorClassifier.d.ts +30 -0
- package/dist/utils/errorClassifier.js +93 -0
- package/dist/utils/fileDetector.js +6 -43
- package/dist/utils/providerHealth.d.ts +42 -7
- package/dist/utils/providerHealth.js +115 -122
- package/dist/utils/providerUtils.js +21 -63
- 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
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
`
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
:
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
`
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
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,
|
|
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
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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,
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
|
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
|
-
|
|
324
|
-
|
|
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?.
|
|
328
|
-
(
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
-
|
|
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
|
-
|
|
56
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
|
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
|
-
|
|
51
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|