@juspay/neurolink 10.12.9 → 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 +32 -0
- package/dist/browser/neurolink.min.js +504 -506
- package/dist/cli/commands/setup.d.ts +20 -1
- package/dist/cli/commands/setup.js +160 -36
- package/dist/cli/factories/commandFactory.d.ts +1157 -1
- package/dist/cli/factories/commandFactory.js +20 -53
- package/dist/core/baseProvider.js +3 -3
- package/dist/core/dynamicModels.d.ts +1 -1
- package/dist/core/dynamicModels.js +1 -23
- package/dist/core/modelConfiguration.d.ts +1 -70
- package/dist/core/modelConfiguration.js +0 -303
- package/dist/core/modules/GenerationHandler.js +2 -2
- package/dist/core/modules/structuredOutputPolicy.d.ts +2 -2
- package/dist/core/modules/structuredOutputPolicy.js +2 -2
- package/dist/factories/providerDescriptors.d.ts +18 -0
- package/dist/factories/providerDescriptors.js +536 -0
- package/dist/factories/providerFactory.d.ts +28 -2
- package/dist/factories/providerFactory.js +53 -42
- package/dist/factories/providerRegistry.js +33 -32
- package/dist/index.d.ts +10 -1
- package/dist/index.js +13 -3
- package/dist/lib/core/baseProvider.js +3 -3
- package/dist/lib/core/dynamicModels.d.ts +1 -1
- package/dist/lib/core/dynamicModels.js +1 -23
- package/dist/lib/core/modelConfiguration.d.ts +1 -70
- package/dist/lib/core/modelConfiguration.js +0 -303
- package/dist/lib/core/modules/GenerationHandler.js +2 -2
- package/dist/lib/core/modules/structuredOutputPolicy.d.ts +2 -2
- package/dist/lib/core/modules/structuredOutputPolicy.js +2 -2
- package/dist/lib/factories/providerDescriptors.d.ts +18 -0
- package/dist/lib/factories/providerDescriptors.js +537 -0
- package/dist/lib/factories/providerFactory.d.ts +28 -2
- package/dist/lib/factories/providerFactory.js +53 -42
- package/dist/lib/factories/providerRegistry.js +33 -32
- package/dist/lib/index.d.ts +10 -1
- package/dist/lib/index.js +13 -3
- package/dist/lib/models/anthropicModels.d.ts +0 -7
- package/dist/lib/models/anthropicModels.js +0 -9
- 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/anthropic/constants.d.ts +0 -1
- package/dist/lib/providers/anthropic/constants.js +0 -2
- package/dist/lib/providers/anthropic/index.d.ts +0 -1
- package/dist/lib/providers/anthropic/index.js +0 -1
- 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/googleAiStudio/index.d.ts +0 -1
- package/dist/lib/providers/googleAiStudio/index.js +0 -1
- package/dist/lib/providers/googleNativeGemini3/index.d.ts +0 -1
- package/dist/lib/providers/googleNativeGemini3/index.js +0 -1
- package/dist/lib/providers/googleVertex/client.d.ts +0 -51
- package/dist/lib/providers/googleVertex/client.js +107 -777
- package/dist/lib/providers/groq.js +19 -22
- package/dist/lib/providers/huggingFace/client.d.ts +1 -1
- package/dist/lib/providers/huggingFace/client.js +27 -25
- package/dist/lib/providers/huggingFace/index.d.ts +0 -1
- package/dist/lib/providers/huggingFace/index.js +0 -1
- package/dist/lib/providers/jina.d.ts +0 -1
- package/dist/lib/providers/jina.js +0 -1
- package/dist/lib/providers/litellm/client.js +54 -39
- package/dist/lib/providers/litellm/index.d.ts +0 -2
- package/dist/lib/providers/litellm/index.js +0 -2
- package/dist/lib/providers/llamaCpp.d.ts +1 -0
- package/dist/lib/providers/llamaCpp.js +24 -18
- package/dist/lib/providers/lmStudio.js +25 -42
- package/dist/lib/providers/mistral.js +12 -24
- package/dist/lib/providers/nvidiaNim/client.js +37 -34
- package/dist/lib/providers/nvidiaNim/index.d.ts +0 -1
- package/dist/lib/providers/nvidiaNim/index.js +0 -1
- package/dist/lib/providers/ollama/client.js +51 -58
- package/dist/lib/providers/ollama/index.d.ts +0 -1
- package/dist/lib/providers/ollama/index.js +0 -1
- package/dist/lib/providers/openAI/client.js +37 -40
- package/dist/lib/providers/openAI/index.d.ts +0 -2
- package/dist/lib/providers/openAI/index.js +0 -2
- package/dist/lib/providers/openRouter/client.js +53 -47
- package/dist/lib/providers/openRouter/utils.d.ts +0 -2
- package/dist/lib/providers/openRouter/utils.js +0 -12
- package/dist/lib/providers/openaiChatCompletionsBase.d.ts +9 -0
- package/dist/lib/providers/openaiChatCompletionsBase.js +73 -16
- package/dist/lib/providers/openaiCompatible/client.js +36 -32
- package/dist/lib/providers/perplexity.d.ts +2 -1
- package/dist/lib/providers/perplexity.js +14 -22
- package/dist/lib/providers/replicate.d.ts +0 -1
- package/dist/lib/providers/replicate.js +22 -9
- package/dist/lib/providers/togetherAi.js +12 -21
- package/dist/lib/providers/voyage.d.ts +0 -1
- package/dist/lib/providers/voyage.js +0 -1
- 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/index.d.ts +0 -1
- package/dist/lib/types/index.js +0 -1
- package/dist/lib/types/providers.d.ts +66 -11
- 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/providerConfig.d.ts +0 -8
- package/dist/lib/utils/providerConfig.js +0 -37
- package/dist/lib/utils/providerHealth.d.ts +42 -7
- package/dist/lib/utils/providerHealth.js +115 -122
- package/dist/lib/utils/providerUtils.js +21 -87
- package/dist/models/anthropicModels.d.ts +0 -7
- package/dist/models/anthropicModels.js +0 -9
- 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/anthropic/constants.d.ts +0 -1
- package/dist/providers/anthropic/constants.js +0 -2
- package/dist/providers/anthropic/index.d.ts +0 -1
- package/dist/providers/anthropic/index.js +0 -1
- 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/googleAiStudio/index.d.ts +0 -1
- package/dist/providers/googleAiStudio/index.js +0 -1
- package/dist/providers/googleNativeGemini3/index.d.ts +0 -1
- package/dist/providers/googleNativeGemini3/index.js +0 -1
- package/dist/providers/googleVertex/client.d.ts +0 -51
- package/dist/providers/googleVertex/client.js +107 -777
- package/dist/providers/groq.js +19 -22
- package/dist/providers/huggingFace/client.d.ts +1 -1
- package/dist/providers/huggingFace/client.js +27 -25
- package/dist/providers/huggingFace/index.d.ts +0 -1
- package/dist/providers/huggingFace/index.js +0 -1
- package/dist/providers/jina.d.ts +0 -1
- package/dist/providers/jina.js +0 -1
- package/dist/providers/litellm/client.js +54 -39
- package/dist/providers/litellm/index.d.ts +0 -2
- package/dist/providers/litellm/index.js +0 -2
- package/dist/providers/llamaCpp.d.ts +1 -0
- package/dist/providers/llamaCpp.js +24 -18
- package/dist/providers/lmStudio.js +25 -42
- package/dist/providers/mistral.js +12 -24
- package/dist/providers/nvidiaNim/client.js +37 -34
- package/dist/providers/nvidiaNim/index.d.ts +0 -1
- package/dist/providers/nvidiaNim/index.js +0 -1
- package/dist/providers/ollama/client.js +51 -58
- package/dist/providers/ollama/index.d.ts +0 -1
- package/dist/providers/ollama/index.js +0 -1
- package/dist/providers/openAI/client.js +37 -40
- package/dist/providers/openAI/index.d.ts +0 -2
- package/dist/providers/openAI/index.js +0 -2
- package/dist/providers/openRouter/client.js +53 -47
- package/dist/providers/openRouter/utils.d.ts +0 -2
- package/dist/providers/openRouter/utils.js +0 -12
- package/dist/providers/openaiChatCompletionsBase.d.ts +9 -0
- package/dist/providers/openaiChatCompletionsBase.js +73 -16
- package/dist/providers/openaiCompatible/client.js +36 -32
- package/dist/providers/perplexity.d.ts +2 -1
- package/dist/providers/perplexity.js +14 -22
- package/dist/providers/replicate.d.ts +0 -1
- package/dist/providers/replicate.js +22 -9
- package/dist/providers/sagemaker/language-model.d.ts +2 -2
- package/dist/providers/togetherAi.js +12 -21
- package/dist/providers/voyage.d.ts +0 -1
- package/dist/providers/voyage.js +0 -1
- 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/index.d.ts +0 -1
- package/dist/types/index.js +0 -1
- package/dist/types/providers.d.ts +66 -11
- 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/providerConfig.d.ts +0 -8
- package/dist/utils/providerConfig.js +0 -37
- package/dist/utils/providerHealth.d.ts +42 -7
- package/dist/utils/providerHealth.js +115 -122
- package/dist/utils/providerUtils.js +21 -87
- package/package.json +21 -75
- package/dist/lib/providers/anthropic/utils.d.ts +0 -7
- package/dist/lib/providers/anthropic/utils.js +0 -150
- package/dist/lib/providers/googleAiStudio/utils.d.ts +0 -2
- package/dist/lib/providers/googleAiStudio/utils.js +0 -19
- package/dist/lib/providers/googleNativeGemini3/constants.d.ts +0 -1
- package/dist/lib/providers/googleNativeGemini3/constants.js +0 -2
- package/dist/lib/providers/huggingFace/utils.d.ts +0 -2
- package/dist/lib/providers/huggingFace/utils.js +0 -8
- package/dist/lib/providers/index.d.ts +0 -33
- package/dist/lib/providers/index.js +0 -34
- package/dist/lib/providers/litellm/constants.d.ts +0 -1
- package/dist/lib/providers/litellm/constants.js +0 -3
- package/dist/lib/providers/litellm/utils.d.ts +0 -5
- package/dist/lib/providers/litellm/utils.js +0 -11
- package/dist/lib/providers/nvidiaNim/utils.d.ts +0 -9
- package/dist/lib/providers/nvidiaNim/utils.js +0 -101
- package/dist/lib/providers/ollama/utils.d.ts +0 -8
- package/dist/lib/providers/ollama/utils.js +0 -43
- package/dist/lib/providers/openAI/constants.d.ts +0 -1
- package/dist/lib/providers/openAI/constants.js +0 -3
- package/dist/lib/providers/openAI/utils.d.ts +0 -2
- package/dist/lib/providers/openAI/utils.js +0 -8
- package/dist/lib/types/universalProviderOptions.d.ts +0 -89
- package/dist/lib/types/universalProviderOptions.js +0 -55
- package/dist/providers/anthropic/utils.d.ts +0 -7
- package/dist/providers/anthropic/utils.js +0 -149
- package/dist/providers/googleAiStudio/utils.d.ts +0 -2
- package/dist/providers/googleAiStudio/utils.js +0 -18
- package/dist/providers/googleNativeGemini3/constants.d.ts +0 -1
- package/dist/providers/googleNativeGemini3/constants.js +0 -1
- package/dist/providers/huggingFace/utils.d.ts +0 -2
- package/dist/providers/huggingFace/utils.js +0 -7
- package/dist/providers/index.d.ts +0 -33
- package/dist/providers/index.js +0 -33
- package/dist/providers/litellm/constants.d.ts +0 -1
- package/dist/providers/litellm/constants.js +0 -2
- package/dist/providers/litellm/utils.d.ts +0 -5
- package/dist/providers/litellm/utils.js +0 -10
- package/dist/providers/nvidiaNim/utils.d.ts +0 -9
- package/dist/providers/nvidiaNim/utils.js +0 -100
- package/dist/providers/ollama/utils.d.ts +0 -8
- package/dist/providers/ollama/utils.js +0 -42
- package/dist/providers/openAI/constants.d.ts +0 -1
- package/dist/providers/openAI/constants.js +0 -2
- package/dist/providers/openAI/utils.d.ts +0 -2
- package/dist/providers/openAI/utils.js +0 -7
- package/dist/types/universalProviderOptions.d.ts +0 -89
- package/dist/types/universalProviderOptions.js +0 -54
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { modelConfig } from "../../core/modelConfiguration.js";
|
|
2
2
|
import { createProxyFetch } from "../../proxy/proxyFetch.js";
|
|
3
3
|
import { InvalidModelError, NetworkError, ProviderError, } from "../../types/index.js";
|
|
4
|
+
import { classifyProviderError, DEFAULT_ERROR_RULES, } from "../../utils/errorClassifier.js";
|
|
4
5
|
import { logger } from "../../utils/logger.js";
|
|
5
6
|
import { redactUrlCredentials } from "../../utils/logSanitize.js";
|
|
6
7
|
import { createTimeoutController, parseTimeout, TimeoutError, } from "../../utils/timeout.js";
|
|
@@ -81,47 +82,61 @@ export class OllamaProvider extends OpenAIChatCompletionsProvider {
|
|
|
81
82
|
}
|
|
82
83
|
formatProviderError(error) {
|
|
83
84
|
if (error instanceof TimeoutError) {
|
|
85
|
+
// Custom message (not classifyProviderError's built-in "Request timed
|
|
86
|
+
// out: ..." default). TimeoutError is handled unconditionally inside
|
|
87
|
+
// classifyProviderError ahead of any rule table, so this quirk can only
|
|
88
|
+
// be preserved via a pre-delegate intercept (same pattern used for
|
|
89
|
+
// groq's TimeoutError override).
|
|
84
90
|
return new NetworkError(`Ollama request timed out. The model may be loading or the request is too large.`, "ollama");
|
|
85
91
|
}
|
|
92
|
+
// `responseBody` isn't part of ProviderErrorContext, so it's read off the
|
|
93
|
+
// raw error here (mirrors openAI's `errorType` extraction) for the
|
|
94
|
+
// missing-model / 404 rules below, which match against message+body
|
|
95
|
+
// combined exactly as the pre-migration code did.
|
|
86
96
|
const errorRecord = error;
|
|
87
|
-
const message = typeof errorRecord?.message === "string"
|
|
88
|
-
? errorRecord.message
|
|
89
|
-
: "Unknown error";
|
|
90
|
-
const cause = errorRecord?.cause ?? {};
|
|
91
|
-
const code = (errorRecord?.code ?? cause?.code);
|
|
92
|
-
if (code === "ECONNREFUSED" ||
|
|
93
|
-
message.includes("ECONNREFUSED") ||
|
|
94
|
-
message.includes("Failed to fetch") ||
|
|
95
|
-
message.includes("fetch failed")) {
|
|
96
|
-
return new NetworkError(`Cannot connect to Ollama at ${redactUrlCredentials(this.config.baseURL)}. ` +
|
|
97
|
-
`Install Ollama (https://ollama.com), start it with 'ollama serve', then try again.`, "ollama");
|
|
98
|
-
}
|
|
99
|
-
// The base client (buildAPIError) attaches statusCode + responseBody to
|
|
100
|
-
// HTTP failures. Distinguish a genuine missing-model error (give 'ollama
|
|
101
|
-
// pull' guidance) from a bare endpoint-mismatch 404 (wrong base URL / not
|
|
102
|
-
// the OpenAI-compatible /v1 surface) so the advice is actionable. Match on
|
|
103
|
-
// wording, not a bare "404" substring, to avoid misclassifying unrelated
|
|
104
|
-
// messages that merely contain those digits.
|
|
105
|
-
const statusCode = typeof errorRecord?.statusCode === "number"
|
|
106
|
-
? errorRecord.statusCode
|
|
107
|
-
: undefined;
|
|
108
97
|
const responseBody = typeof errorRecord?.responseBody === "string"
|
|
109
98
|
? errorRecord.responseBody
|
|
110
99
|
: "";
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
100
|
+
const cause = errorRecord?.cause ?? {};
|
|
101
|
+
const code = (errorRecord?.code ?? cause?.code);
|
|
102
|
+
const rules = [
|
|
103
|
+
{
|
|
104
|
+
match: (ctx) => code === "ECONNREFUSED" ||
|
|
105
|
+
/ECONNREFUSED|Failed to fetch|fetch failed/.test(ctx.message),
|
|
106
|
+
errorClass: NetworkError,
|
|
107
|
+
message: () => `Cannot connect to Ollama at ${redactUrlCredentials(this.config.baseURL)}. ` +
|
|
108
|
+
`Install Ollama (https://ollama.com), start it with 'ollama serve', then try again.`,
|
|
109
|
+
},
|
|
110
|
+
// The base client (buildAPIError) attaches statusCode + responseBody to
|
|
111
|
+
// HTTP failures. Distinguish a genuine missing-model error (give 'ollama
|
|
112
|
+
// pull' guidance) from a bare endpoint-mismatch 404 (wrong base URL / not
|
|
113
|
+
// the OpenAI-compatible /v1 surface) so the advice is actionable. Match
|
|
114
|
+
// on wording, not a bare "404" substring, to avoid misclassifying
|
|
115
|
+
// unrelated messages that merely contain those digits.
|
|
116
|
+
{
|
|
117
|
+
match: (ctx) => {
|
|
118
|
+
const haystack = `${ctx.message} ${responseBody}`.toLowerCase();
|
|
119
|
+
return (haystack.includes("model_not_found") ||
|
|
120
|
+
(haystack.includes("model") && haystack.includes("not found")));
|
|
121
|
+
},
|
|
122
|
+
errorClass: InvalidModelError,
|
|
123
|
+
message: () => `Ollama model '${this.modelName}' is not available locally. ` +
|
|
124
|
+
`Pull it first with 'ollama pull ${this.modelName}' (or try '${FALLBACK_OLLAMA_MODEL}'); ` +
|
|
125
|
+
`list installed models with 'ollama list'.`,
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
match: (ctx) => {
|
|
129
|
+
const haystack = `${ctx.message} ${responseBody}`.toLowerCase();
|
|
130
|
+
return ctx.statusCode === 404 || haystack.includes("status 404");
|
|
131
|
+
},
|
|
132
|
+
errorClass: ProviderError,
|
|
133
|
+
message: () => `Ollama returned HTTP 404 from ${redactUrlCredentials(this.config.baseURL)}. ` +
|
|
134
|
+
`Verify the base URL serves the OpenAI-compatible /v1 API and that the ` +
|
|
135
|
+
`model is installed ('ollama list').`,
|
|
136
|
+
},
|
|
137
|
+
...DEFAULT_ERROR_RULES,
|
|
138
|
+
];
|
|
139
|
+
return classifyProviderError(error, rules, "ollama", this.modelName);
|
|
125
140
|
}
|
|
126
141
|
// ===========================================================================
|
|
127
142
|
// Optional hooks — Ollama-specific behaviour
|
|
@@ -181,29 +196,7 @@ export class OllamaProvider extends OpenAIChatCompletionsProvider {
|
|
|
181
196
|
* back to a model that the first real request can't serve, so report unusable.
|
|
182
197
|
*/
|
|
183
198
|
async validateConfiguration() {
|
|
184
|
-
|
|
185
|
-
const url = `${stripTrailingSlash(this.config.baseURL)}/models`;
|
|
186
|
-
const proxyFetch = createProxyFetch();
|
|
187
|
-
const r = await proxyFetch(url, {
|
|
188
|
-
headers: {
|
|
189
|
-
...this.getAuthHeaders(),
|
|
190
|
-
"Content-Type": "application/json",
|
|
191
|
-
},
|
|
192
|
-
signal: AbortSignal.timeout(5000),
|
|
193
|
-
});
|
|
194
|
-
if (!r.ok) {
|
|
195
|
-
return false;
|
|
196
|
-
}
|
|
197
|
-
const data = (await r.json().catch(() => null));
|
|
198
|
-
return Boolean(data?.data?.some((m) => typeof m?.id === "string" && m.id.trim().length > 0));
|
|
199
|
-
}
|
|
200
|
-
catch (error) {
|
|
201
|
-
logger.debug("Ollama validateConfiguration probe failed", {
|
|
202
|
-
baseURL: redactUrlCredentials(this.config.baseURL),
|
|
203
|
-
error: error instanceof Error ? error.message : String(error),
|
|
204
|
-
});
|
|
205
|
-
return false;
|
|
206
|
-
}
|
|
199
|
+
return this.probeModelsEndpoint(this.getAuthHeaders());
|
|
207
200
|
}
|
|
208
201
|
getConfiguration() {
|
|
209
202
|
return {
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
2
2
|
import { AIProviderName as AIProviderNameEnum } from "../../constants/enums.js";
|
|
3
3
|
import { createProxyFetch } from "../../proxy/proxyFetch.js";
|
|
4
|
-
import { AuthenticationError, InvalidModelError,
|
|
4
|
+
import { AuthenticationError, InvalidModelError, ProviderError, RateLimitError, } from "../../types/index.js";
|
|
5
5
|
import { logger } from "../../utils/logger.js";
|
|
6
6
|
import { redactUrlCredentials } from "../../utils/logSanitize.js";
|
|
7
7
|
import { calculateCost } from "../../utils/pricing.js";
|
|
8
|
+
import { classifyProviderError, DEFAULT_ERROR_RULES, } from "../../utils/errorClassifier.js";
|
|
8
9
|
import { createOpenAIConfig, getProviderModel, validateApiKey, } from "../../utils/providerConfig.js";
|
|
9
10
|
import { MAX_IMAGE_BYTES, readBoundedBuffer } from "../../utils/sizeGuard.js";
|
|
10
11
|
import { assertSafeUrl } from "../../utils/ssrfGuard.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 OPENAI_DEFAULT_BASE_URL = "https://api.openai.com/v1";
|
|
@@ -89,48 +90,44 @@ export class OpenAIProvider extends OpenAIChatCompletionsProvider {
|
|
|
89
90
|
return getOpenAIModel();
|
|
90
91
|
}
|
|
91
92
|
formatProviderError(error) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
// `type` isn't part of `ProviderErrorContext` (it's an OpenAI-specific
|
|
94
|
+
// error-body field), so it's read directly off the raw error here and
|
|
95
|
+
// captured by the rule closures below.
|
|
95
96
|
const errorObj = error;
|
|
96
|
-
const message = errorObj?.message && typeof errorObj.message === "string"
|
|
97
|
-
? errorObj.message
|
|
98
|
-
: "Unknown error";
|
|
99
97
|
const errorType = errorObj?.type && typeof errorObj.type === "string"
|
|
100
98
|
? errorObj.type
|
|
101
99
|
: undefined;
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
:
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return new ProviderError(`OpenAI error: ${message}`, this.providerName);
|
|
100
|
+
const rules = [
|
|
101
|
+
// Curator P1-1 / Reviewer Finding #4: only the explicit auth markers
|
|
102
|
+
// map to AuthenticationError. Earlier we treated every
|
|
103
|
+
// `invalid_request_error` as an auth failure — that's OpenAI's
|
|
104
|
+
// catch-all for any bad request (unsupported parameter, malformed
|
|
105
|
+
// JSON, etc.) and mislabelled them as "invalid API key". Use
|
|
106
|
+
// credential-specific signals only.
|
|
107
|
+
{
|
|
108
|
+
match: (ctx) => ctx.statusCode === 401 ||
|
|
109
|
+
errorType === "invalid_api_key" ||
|
|
110
|
+
/API_KEY_INVALID|Invalid API key|Incorrect API key|invalid_api_key/i.test(ctx.message),
|
|
111
|
+
errorClass: AuthenticationError,
|
|
112
|
+
message: (ctx) => /Incorrect API key|Invalid API key/i.test(ctx.message)
|
|
113
|
+
? ctx.message
|
|
114
|
+
: "Invalid OpenAI API key. Please check your OPENAI_API_KEY environment variable.",
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
match: (ctx) => ctx.statusCode === 429 ||
|
|
118
|
+
errorType === "rate_limit_error" ||
|
|
119
|
+
/rate limit/i.test(ctx.message),
|
|
120
|
+
errorClass: RateLimitError,
|
|
121
|
+
message: "OpenAI rate limit exceeded. Please try again later.",
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
match: (ctx) => /model_not_found/i.test(ctx.message),
|
|
125
|
+
errorClass: InvalidModelError,
|
|
126
|
+
message: (ctx) => `Model not found: ${ctx.modelName}`,
|
|
127
|
+
},
|
|
128
|
+
...DEFAULT_ERROR_RULES,
|
|
129
|
+
];
|
|
130
|
+
return classifyProviderError(error, rules, this.providerName, this.modelName);
|
|
134
131
|
}
|
|
135
132
|
// ===========================================================================
|
|
136
133
|
// Optional hook overrides
|
|
@@ -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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
127
|
-
|
|
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",
|
|
130
|
-
}
|
|
135
|
+
"Try a different model or check availability at https://openrouter.ai/models",
|
|
136
|
+
},
|
|
131
137
|
// Tool/function calling errors
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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",
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
|
|
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
|
|
@@ -1,17 +1,5 @@
|
|
|
1
1
|
import { OpenRouterModels } from "../../constants/enums.js";
|
|
2
2
|
import { getProviderModel } from "../../utils/providerConfig.js";
|
|
3
|
-
export const getOpenRouterConfig = () => {
|
|
4
|
-
const apiKey = process.env.OPENROUTER_API_KEY;
|
|
5
|
-
if (!apiKey) {
|
|
6
|
-
throw new Error("OPENROUTER_API_KEY environment variable is required. " +
|
|
7
|
-
"Get your API key at https://openrouter.ai/keys");
|
|
8
|
-
}
|
|
9
|
-
return {
|
|
10
|
-
apiKey,
|
|
11
|
-
referer: process.env.OPENROUTER_REFERER,
|
|
12
|
-
appName: process.env.OPENROUTER_APP_NAME,
|
|
13
|
-
};
|
|
14
|
-
};
|
|
15
3
|
export const getDefaultOpenRouterModel = () => {
|
|
16
4
|
return getProviderModel("OPENROUTER_MODEL", OpenRouterModels.CLAUDE_SONNET_4_5);
|
|
17
5
|
};
|
|
@@ -166,6 +166,15 @@ export declare abstract class OpenAIChatCompletionsProvider extends BaseProvider
|
|
|
166
166
|
* override this to probe the server's `/models` endpoint.
|
|
167
167
|
*/
|
|
168
168
|
validateConfiguration(): Promise<boolean>;
|
|
169
|
+
/**
|
|
170
|
+
* Shared local-runtime reachability probe: GET `${baseURL}/models` with a
|
|
171
|
+
* short timeout, requiring at least one model entry with a non-empty id.
|
|
172
|
+
* Local providers (Ollama, LM Studio, llama.cpp) call this from their own
|
|
173
|
+
* validateConfiguration() override instead of relying on the base class's
|
|
174
|
+
* "apiKey is a non-empty string" default, which can't detect an
|
|
175
|
+
* unreachable local server.
|
|
176
|
+
*/
|
|
177
|
+
protected probeModelsEndpoint(headers?: Record<string, string>): Promise<boolean>;
|
|
169
178
|
/**
|
|
170
179
|
* Snapshot of the provider's resolved configuration — part of the documented
|
|
171
180
|
* public provider contract (`docs/provider-integration/00-architecture.md`).
|
|
@@ -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";
|
|
@@ -26,12 +27,14 @@ import { DEFAULT_MAX_STEPS } from "../core/constants.js";
|
|
|
26
27
|
import { streamAnalyticsCollector } from "../core/streamAnalytics.js";
|
|
27
28
|
import { createProxyFetch } from "../proxy/proxyFetch.js";
|
|
28
29
|
import { logger } from "../utils/logger.js";
|
|
30
|
+
import { redactUrlCredentials } from "../utils/logSanitize.js";
|
|
29
31
|
import { NoOutputGeneratedError } from "../utils/generationErrors.js";
|
|
30
32
|
import { buildNoOutputSentinel, stampNoOutputSpan, } from "../utils/noOutputSentinel.js";
|
|
31
33
|
import { composeAbortSignalsScoped, createTimeoutController, mergeAbortSignals, } from "../utils/timeout.js";
|
|
32
34
|
import { emitToolEndFromStepFinish } from "../utils/toolEndEmitter.js";
|
|
33
35
|
import { resolveToolChoice } from "../utils/toolChoice.js";
|
|
34
36
|
import { transformToolExecutions } from "../utils/transformationUtils.js";
|
|
37
|
+
import { withProviderRetry } from "../utils/providerRetry.js";
|
|
35
38
|
import { resolveDeferredTool } from "../tools/toolDiscovery.js";
|
|
36
39
|
import { buildAPIError, buildBody, buildToolsForOpenAI, buildWireToolNameMaps, createChunkQueue, createDeferredAnalytics, ensureJsonWordInBody, estimateWireTokens, mapNeuroLinkToolChoice, mergeUsage, messageBuilderToOpenAI, parseSSEStream, stringifyToolOutput, stripTrailingSlash, v3ResponseFormatToOpenAI, v3ToolChoiceToOpenAI, v3ToolsToOpenAI, } from "./openaiChatCompletionsClient.js";
|
|
37
40
|
/**
|
|
@@ -272,6 +275,38 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
272
275
|
return (typeof this.config.apiKey === "string" &&
|
|
273
276
|
this.config.apiKey.trim().length > 0);
|
|
274
277
|
}
|
|
278
|
+
/**
|
|
279
|
+
* Shared local-runtime reachability probe: GET `${baseURL}/models` with a
|
|
280
|
+
* short timeout, requiring at least one model entry with a non-empty id.
|
|
281
|
+
* Local providers (Ollama, LM Studio, llama.cpp) call this from their own
|
|
282
|
+
* validateConfiguration() override instead of relying on the base class's
|
|
283
|
+
* "apiKey is a non-empty string" default, which can't detect an
|
|
284
|
+
* unreachable local server.
|
|
285
|
+
*/
|
|
286
|
+
async probeModelsEndpoint(headers = {}) {
|
|
287
|
+
try {
|
|
288
|
+
const url = `${stripTrailingSlash(this.config.baseURL)}/models`;
|
|
289
|
+
const proxyFetch = createProxyFetch();
|
|
290
|
+
const response = await proxyFetch(url, {
|
|
291
|
+
headers: { ...headers, "Content-Type": "application/json" },
|
|
292
|
+
signal: AbortSignal.timeout(5000),
|
|
293
|
+
});
|
|
294
|
+
if (!response.ok) {
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
const data = (await response
|
|
298
|
+
.json()
|
|
299
|
+
.catch(() => null));
|
|
300
|
+
return Boolean(data?.data?.some((m) => typeof m?.id === "string" && m.id.trim().length > 0));
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
logger.debug(`[${this.constructor.name}] probeModelsEndpoint failed`, {
|
|
304
|
+
baseURL: redactUrlCredentials(this.config.baseURL),
|
|
305
|
+
error: error instanceof Error ? error.message : String(error),
|
|
306
|
+
});
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
275
310
|
/**
|
|
276
311
|
* Snapshot of the provider's resolved configuration — part of the documented
|
|
277
312
|
* public provider contract (`docs/provider-integration/00-architecture.md`).
|
|
@@ -899,22 +934,44 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
899
934
|
: {}),
|
|
900
935
|
streaming: true,
|
|
901
936
|
}), args.modelId));
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
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
|
|
918
975
|
? (this.correctBodyAfterContextOverflow(body, apiErr) ??
|
|
919
976
|
this.adjustBodyAfter400(body, apiErr))
|
|
920
977
|
: undefined;
|