UNPKG

@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
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, }; } }