@juspay/neurolink
Version:
The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002
91 lines (90 loc) • 4.24 kB
JavaScript
import { AuthenticationError, InvalidModelError, NetworkError, } from "../../types/index.js";
import { logger } from "../../utils/logger.js";
import { redactUrlCredentials } from "../../utils/logSanitize.js";
import { classifyProviderError, DEFAULT_ERROR_RULES, } from "../../utils/errorClassifier.js";
import { OpenAIChatCompletionsProvider } from "../openaiChatCompletionsBase.js";
import { FALLBACK_OPENAI_COMPATIBLE_MODEL } from "./constants.js";
import { getDefaultOpenAICompatibleModel, getOpenAICompatibleConfig, } from "./utils.js";
/**
* OpenAI Compatible Provider — direct HTTP, no AI SDK.
*
* Talks to any OpenAI chat-completions-shaped endpoint (LiteLLM, vLLM,
* OpenRouter, etc.). All request/stream/tool-loop orchestration lives in
* `OpenAIChatCompletionsProvider`. This class just declares config and
* provider-specific error mapping.
*/
export class OpenAICompatibleProvider extends OpenAIChatCompletionsProvider {
constructor(modelName, sdk, _region, credentials) {
const resolved = credentials?.apiKey && credentials?.baseURL
? { apiKey: credentials.apiKey, baseURL: credentials.baseURL }
: (() => {
const env = getOpenAICompatibleConfig();
return {
apiKey: credentials?.apiKey ?? env.apiKey,
baseURL: credentials?.baseURL ?? env.baseURL,
};
})();
super("openai-compatible", modelName, sdk, resolved);
logger.debug("OpenAI Compatible Provider initialized", {
modelName: this.modelName,
provider: this.providerName,
baseURL: redactUrlCredentials(this.config.baseURL),
});
}
getProviderName() {
return "openai-compatible";
}
getDefaultModel() {
return getDefaultOpenAICompatibleModel() || "";
}
getFallbackModelName() {
return FALLBACK_OPENAI_COMPATIBLE_MODEL;
}
getFallbackModels() {
return [
"gpt-4o",
"gpt-4o-mini",
"gpt-4-turbo",
FALLBACK_OPENAI_COMPATIBLE_MODEL,
"claude-3-5-sonnet",
"claude-3-haiku",
"gemini-pro",
];
}
formatProviderError(error) {
const rules = [
// Duck-typed timeout check (name === "TimeoutError" or a "Timeout"
// substring) — broader than classifyProviderError's built-in
// `instanceof TimeoutError` handling, so it's preserved as an
// explicit rule rather than relying on the default.
{
match: (ctx) => ctx.errorName === "TimeoutError" || /Timeout/.test(ctx.message),
errorClass: NetworkError,
message: (ctx) => `Request timed out: ${ctx.message}`,
},
{
match: (ctx) => /ECONNREFUSED|Failed to fetch/i.test(ctx.message),
errorClass: NetworkError,
message: () => `OpenAI Compatible endpoint not available. Please check your OPENAI_COMPATIBLE_BASE_URL: ${redactUrlCredentials(this.config.baseURL)}`,
},
{
match: (ctx) => ctx.statusCode === 401 ||
/API_KEY_INVALID|Invalid API key|Unauthorized/i.test(ctx.message),
errorClass: AuthenticationError,
message: "Invalid OpenAI Compatible API key. Please check your OPENAI_COMPATIBLE_API_KEY environment variable.",
},
// "does not exist" isn't covered by DEFAULT_ERROR_RULES's
// model_not_found/"model not found" pattern — preserved verbatim.
{
match: (ctx) => /model/i.test(ctx.message) &&
(/not found/i.test(ctx.message) ||
/does not exist/i.test(ctx.message)),
errorClass: InvalidModelError,
message: (ctx) => `Model '${ctx.modelName}' not available on OpenAI Compatible endpoint. ` +
"Please check available models or use getAvailableModels() to see supported models.",
},
...DEFAULT_ERROR_RULES,
];
return classifyProviderError(error, rules, this.providerName, this.modelName);
}
}