@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
77 lines (76 loc) • 3.4 kB
JavaScript
import { InvalidModelError } from "../types/index.js";
import { classifyProviderError, DEFAULT_ERROR_RULES, } from "../utils/errorClassifier.js";
import { logger } from "../utils/logger.js";
import { redactUrlCredentials } from "../utils/logSanitize.js";
import { OpenAIChatCompletionsProvider } from "./openaiChatCompletionsBase.js";
import { buildLocalUnreachableErrorRule } from "./localRuntimeOpenAICompat.js";
const LM_STUDIO_DEFAULT_BASE_URL = "http://localhost:1234/v1";
const LM_STUDIO_PLACEHOLDER_KEY = "lm-studio";
const FALLBACK_MODEL = "local-model";
const getLmStudioBaseURL = () => {
return process.env.LM_STUDIO_BASE_URL || LM_STUDIO_DEFAULT_BASE_URL;
};
/**
* LM Studio Provider — direct HTTP, no AI SDK.
*
* Wraps the LM Studio local server (https://lmstudio.ai/) which exposes an
* OpenAI-compatible API at http://localhost:1234/v1 by default.
* Auto-discovers the loaded model via /v1/models if no model is specified.
* All request/stream/tool-loop orchestration lives in
* `OpenAIChatCompletionsProvider`; this class only declares configuration
* and provider-specific error mapping.
*
* @see https://lmstudio.ai/
*/
export class LMStudioProvider extends OpenAIChatCompletionsProvider {
constructor(modelName, sdk, _region, credentials) {
// LM Studio's local server doesn't authenticate, but the base HTTP client
// requires an apiKey. Allow override via credentials/env for users who
// run LM Studio behind an auth-proxying reverse-proxy.
const apiKey = credentials?.apiKey ??
process.env.LM_STUDIO_API_KEY ??
LM_STUDIO_PLACEHOLDER_KEY;
const baseURL = credentials?.baseURL ?? getLmStudioBaseURL();
super("lm-studio", modelName, sdk, { baseURL, apiKey });
logger.debug("LM Studio Provider initialized", {
modelName: this.modelName,
providerName: this.providerName,
baseURL: redactUrlCredentials(this.config.baseURL),
});
}
getProviderName() {
return "lm-studio";
}
getDefaultModel() {
return process.env.LM_STUDIO_MODEL || "";
}
getFallbackModelName() {
return FALLBACK_MODEL;
}
formatProviderError(error) {
const rules = [
buildLocalUnreachableErrorRule(error, () => `LM Studio server not reachable at ${redactUrlCredentials(this.config.baseURL)}. ` +
`Open the LM Studio app, load a model, and click "Start Server".`),
{
match: (ctx) => /model_not_found|404/.test(ctx.message),
errorClass: InvalidModelError,
message: () => `LM Studio model '${this.modelName}' is not loaded. Load it in the LM Studio app first.`,
},
...DEFAULT_ERROR_RULES,
];
return classifyProviderError(error, rules, this.providerName, this.modelName);
}
async validateConfiguration() {
return this.probeModelsEndpoint(this.config.apiKey && this.config.apiKey !== LM_STUDIO_PLACEHOLDER_KEY
? { Authorization: `Bearer ${this.config.apiKey}` }
: {});
}
getConfiguration() {
return {
provider: this.providerName,
model: this.modelName || this.resolvedModel || FALLBACK_MODEL,
defaultModel: this.getDefaultModel(),
baseURL: this.config.baseURL,
};
}
}