UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

630 lines (629 loc) 28.9 kB
import { w as parseStrictPositiveInteger } from "./number-coercion-CLj0HTDM.js"; import { d as normalizeStringEntries } from "./string-normalization-DsCfAx8q.js"; import { n as isTruthyEnvValue } from "./env-M3R40TOb.js"; import { l as hasConfiguredSecretInput } from "./types.secrets-kC0nOetj.js"; import { t as normalizeOptionalSecretInput } from "./normalize-secret-input-Df_qhWv_.js"; import { o as removeProviderAuthProfilesWithLock } from "./profiles-DOTqXcYA.js"; import "./provider-auth-BeZ7NZUU.js"; import { t as ensureApiKeyFromEnvOrPrompt } from "./provider-auth-input-B4MRIGbK.js"; import { n as buildApiKeyCredential } from "./provider-auth-helpers-CaYTVMoC.js"; import "./runtime-env-0Q-gCyUb.js"; import "./number-runtime-Cy4drVnh.js"; import "./string-coerce-runtime-GQa0ehRA.js"; import { n as WizardCancelledError } from "./prompts-DLsO8MlU.js"; import { g as withAgentModelAliases } from "./provider-onboard-D4DWEIGi.js"; import { n as applyProviderDefaultModel, r as configureOpenAICompatibleSelfHostedProviderNonInteractive } from "./provider-setup-CQYNqo2d.js"; import { f as selectPreferredLocalModelId } from "./provider-model-shared-fX-Ywqb3.js"; import "./setup-BosJMgvw.js"; import "./provider-auth-runtime-BqyR7mkf.js"; import { A as LMSTUDIO_DEFAULT_INFERENCE_BASE_URL, D as LMSTUDIO_DEFAULT_API_KEY_ENV_VAR, E as resolveLoadedContextWindow, F as LMSTUDIO_LOCAL_API_KEY_PLACEHOLDER, I as LMSTUDIO_MODEL_PLACEHOLDER, L as LMSTUDIO_PROVIDER_ID, M as LMSTUDIO_DEFAULT_MODEL_ID, N as LMSTUDIO_DOCKER_HOST_BASE_URL, O as LMSTUDIO_DEFAULT_BASE_URL, P as LMSTUDIO_DOCKER_HOST_INFERENCE_BASE_URL, R as LMSTUDIO_PROVIDER_LABEL, S as resolveLmstudioInferenceBase, _ as mapLmstudioWireModelsToConfig, a as resolveLmstudioConfiguredApiKey, c as resolveLmstudioRequestContext, d as hasLmstudioAuthorizationHeader, f as resolveLmstudioProviderAuthMode, p as shouldUseLmstudioApiKeyPlaceholder, r as fetchLmstudioModels, s as resolveLmstudioProviderHeaders, t as discoverLmstudioModels } from "./models.fetch-DL1dYWdZ.js"; //#region extensions/lmstudio/src/setup.ts const LMSTUDIO_APP_GUIDED_MIN_CONTEXT_TOKENS = 16384; function resolveLmstudioSetupDefaultBaseUrl(env = process.env) { return isTruthyEnvValue(env.OPENCLAW_DOCKER_SETUP) ? LMSTUDIO_DOCKER_HOST_BASE_URL : LMSTUDIO_DEFAULT_BASE_URL; } function resolveLmstudioSetupDefaultInferenceBaseUrl(env = process.env) { return isTruthyEnvValue(env.OPENCLAW_DOCKER_SETUP) ? LMSTUDIO_DOCKER_HOST_INFERENCE_BASE_URL : LMSTUDIO_DEFAULT_INFERENCE_BASE_URL; } function stripLmstudioStoredAuthConfig(cfg) { const { profiles: _profiles, order: _order, ...restAuth } = cfg.auth ?? {}; const nextProfiles = Object.fromEntries(Object.entries(cfg.auth?.profiles ?? {}).filter(([, profile]) => profile.provider !== LMSTUDIO_PROVIDER_ID)); const nextOrder = Object.fromEntries(Object.entries(cfg.auth?.order ?? {}).filter(([providerId]) => providerId !== LMSTUDIO_PROVIDER_ID)); return { ...cfg, auth: Object.keys(restAuth).length > 0 || Object.keys(nextProfiles).length > 0 || Object.keys(nextOrder).length > 0 ? { ...restAuth, ...Object.keys(nextProfiles).length > 0 ? { profiles: nextProfiles } : {}, ...Object.keys(nextOrder).length > 0 ? { order: nextOrder } : {} } : void 0 }; } function resolvePositiveInteger(value) { if (typeof value === "number" && Number.isFinite(value)) { const normalized = Math.floor(value); return normalized > 0 ? normalized : void 0; } if (typeof value !== "string") return; const trimmed = value.trim(); if (!trimmed || !/^\d+$/.test(trimmed)) return; return parseStrictPositiveInteger(trimmed); } function buildLmstudioSetupProviderConfig(params) { const existingWithoutAuth = params.existingProvider ? (({ auth: _auth, apiKey: _apiKey, ...rest }) => rest)(params.existingProvider) : void 0; const sharedWithoutAuth = params.sharedProvider ? (({ auth: _auth, apiKey: _apiKey, ...rest }) => rest)(params.sharedProvider) : void 0; const resolvedAuth = resolveLmstudioProviderAuthMode(params.apiKey); return { ...existingWithoutAuth, ...sharedWithoutAuth, baseUrl: params.baseUrl, api: params.sharedProvider?.api ?? params.existingProvider?.api ?? "openai-completions", ...resolvedAuth ? { auth: resolvedAuth } : {}, ...params.apiKey !== void 0 ? { apiKey: params.apiKey } : {}, headers: params.headers, models: params.models }; } function resolveLmstudioModelAdvertisedContextLimit(entry) { const raw = entry.max_context_length; if (raw === void 0 || !Number.isFinite(raw) || raw <= 0) return; return Math.floor(raw); } function applyModelContextTokensOverride(model, contextTokens) { return { ...model, contextTokens, maxTokens: Math.min(model.maxTokens, contextTokens) }; } function applyRequestedContextWindowToAllModels(params) { const requestedContextWindow = params.requestedContextWindow; if (!requestedContextWindow) return params.models; const contextLimitByModelId = new Map(params.discoveryModels.map((entry) => { const modelId = entry.key?.trim(); if (!modelId) return null; return [modelId, resolveLmstudioModelAdvertisedContextLimit(entry)]; }).filter((entry) => Boolean(entry))); return params.models.map((model) => applyModelContextTokensOverride(model, Math.min(requestedContextWindow, contextLimitByModelId.get(model.id) ?? requestedContextWindow))); } function collectLoadedLmstudioModelIds(discovery) { return new Set(discovery.models.flatMap((entry) => { const id = entry.key?.trim(); return entry.type === "llm" && id && resolveLoadedContextWindow(entry) !== null ? [id] : []; })); } function resolveLmstudioDiscoveryFailure(params) { const { baseUrl, discovery } = params; if (!discovery.reachable) return { noteLines: [`LM Studio could not be reached at ${baseUrl}.`, "Start LM Studio (or run lms server start) and re-run setup."], retryLine: "Start LM Studio (or run lms server start), then continue to retry.", reason: "LM Studio not reachable" }; if (discovery.status !== void 0 && discovery.status >= 400) { const retryable = discovery.status === 408 || discovery.status === 425 || discovery.status === 429 || discovery.status >= 500; return { noteLines: [`LM Studio returned HTTP ${discovery.status} while listing models at ${baseUrl}.`, retryable && !params.resetPreflight ? "Wait for LM Studio to recover, then re-run setup." : "Check the base URL and API key, then re-run setup."], ...retryable ? { retryLine: "Wait for LM Studio to recover, then continue to retry." } : {}, reason: `LM Studio discovery failed (${discovery.status})` }; } if (!(params.resetPreflight && params.requestedModelId) && collectLoadedLmstudioModelIds(discovery).size === 0) return { noteLines: [`No loaded LM Studio LLM models were found at ${baseUrl}.`, "Load a model in LM Studio (or run lms load <model>), then re-run setup."], retryLine: "Load a model in LM Studio (or run lms load <model>), then continue to retry.", reason: "No loaded LM Studio models found" }; return null; } function resolvePersistedLmstudioApiKey(params) { if (params.explicitAuth === "api-key") { if (params.preferFallbackApiKey && params.fallbackApiKey !== void 0) return params.fallbackApiKey; if (resolveLmstudioProviderAuthMode(params.currentApiKey)) return params.currentApiKey; return params.fallbackApiKey; } return shouldUseLmstudioApiKeyPlaceholder({ hasModels: params.hasModels, resolvedApiKey: params.currentApiKey, hasAuthorizationHeader: params.hasAuthorizationHeader }) ? LMSTUDIO_LOCAL_API_KEY_PLACEHOLDER : void 0; } async function discoverLmstudioProviderCatalog(params) { const baseUrl = resolveLmstudioInferenceBase(params.baseUrl); return { baseUrl, api: "openai-completions", models: await discoverLmstudioModels({ baseUrl, apiKey: params.apiKey ?? "", headers: params.headers, quiet: params.quiet }) }; } function isLmstudioDiscoveryConfigResolutionError(error) { const message = error instanceof Error ? error.message : String(error); return message.includes("models.providers.lmstudio.apiKey") || message.includes("models.providers.lmstudio.headers."); } /** Preserves existing allowlist metadata and appends discovered LM Studio model refs. */ function mergeDiscoveredLmstudioAllowlistEntries(params) { return withAgentModelAliases(params.existing, normalizeStringEntries(params.discoveredModels.map((model) => model.id)).map((id) => `${LMSTUDIO_PROVIDER_ID}/${id}`)); } function selectDefaultLmstudioModelId(discoveredModels) { const ids = normalizeStringEntries(discoveredModels.map((model) => model.id)); if (ids.length === 0) return; return ids.includes("qwen/qwen3.5-9b") ? LMSTUDIO_DEFAULT_MODEL_ID : selectPreferredLocalModelId(ids) ?? ids[0]; } function collectAppGuidedLmstudioModelIds(discovery) { return new Set(discovery.models.flatMap((entry) => { const id = entry.key?.trim(); if (entry.type !== "llm" || entry.capabilities?.trained_for_tool_use !== true || !id) return []; const loadedContextWindow = resolveLoadedContextWindow(entry); return loadedContextWindow !== null && loadedContextWindow >= LMSTUDIO_APP_GUIDED_MIN_CONTEXT_TOKENS ? [id] : []; })); } async function discoverLmstudioSetupModels(params) { const discovery = await fetchLmstudioModels({ baseUrl: params.baseUrl, apiKey: params.apiKey, ...params.headers ? { headers: params.headers } : {}, timeoutMs: params.timeoutMs ?? 5e3 }); const failure = resolveLmstudioDiscoveryFailure({ baseUrl: params.baseUrl, discovery, requestedModelId: params.requestedModelId, resetPreflight: params.resetPreflight }); if (failure) return { failure }; const models = mapLmstudioWireModelsToConfig(discovery.models); const loadedModelIds = collectLoadedLmstudioModelIds(discovery); const defaultModelId = selectDefaultLmstudioModelId(models.filter((model) => loadedModelIds.has(model.id))); return { value: { discovery, models, loadedModelIds, defaultModel: defaultModelId ? `${LMSTUDIO_PROVIDER_ID}/${defaultModelId}` : void 0, defaultModelId } }; } async function resolveAppGuidedLmstudioConnection(ctx) { const existingProvider = ctx.config.models?.providers?.[LMSTUDIO_PROVIDER_ID]; const baseUrl = resolveLmstudioInferenceBase(existingProvider?.baseUrl ?? resolveLmstudioSetupDefaultInferenceBaseUrl(ctx.env)); try { const headers = await resolveLmstudioProviderHeaders({ config: ctx.config, env: ctx.env, headers: existingProvider?.headers }); const accessValue = await resolveLmstudioConfiguredApiKey({ config: ctx.config, env: ctx.env, allowUnresolved: true }) ?? ctx.env["LM_API_TOKEN"]?.trim(); return { existingProvider, accessValue, discoveryRequest: { baseUrl, apiKey: accessValue ?? "lmstudio-local", ...headers ? { headers } : {}, timeoutMs: 5e3 } }; } catch { return null; } } /** Read-only local discovery plus a success-gated config proposal for guided setup. */ async function prepareAppGuidedLmstudioSetup(ctx) { const connection = await resolveAppGuidedLmstudioConnection(ctx); if (!connection) return null; const { existingProvider, accessValue, discoveryRequest } = connection; const { baseUrl, headers } = discoveryRequest; const setupDiscovery = await discoverLmstudioSetupModels(discoveryRequest); if ("failure" in setupDiscovery) return null; const requestedPrefix = `${LMSTUDIO_PROVIDER_ID}/`; const requestedModelId = ctx.modelRef?.startsWith(requestedPrefix) ? ctx.modelRef.slice(requestedPrefix.length) : void 0; const appGuidedModelIds = collectAppGuidedLmstudioModelIds(setupDiscovery.value.discovery); const selectedModelId = requestedModelId ?? selectDefaultLmstudioModelId(setupDiscovery.value.models.filter((model) => appGuidedModelIds.has(model.id))); if (!selectedModelId || !appGuidedModelIds.has(selectedModelId) || !setupDiscovery.value.models.some((model) => model.id === selectedModelId)) return null; const persistedAccess = accessValue ? existingProvider?.apiKey ?? "LM_API_TOKEN" : shouldUseLmstudioApiKeyPlaceholder({ hasModels: true, resolvedApiKey: void 0, hasAuthorizationHeader: hasLmstudioAuthorizationHeader(headers) }) ? LMSTUDIO_LOCAL_API_KEY_PLACEHOLDER : void 0; return { profiles: [], defaultModel: `${LMSTUDIO_PROVIDER_ID}/${selectedModelId}`, configPatch: { models: { mode: ctx.config.models?.mode ?? "merge", providers: { [LMSTUDIO_PROVIDER_ID]: buildLmstudioSetupProviderConfig({ existingProvider, baseUrl, apiKey: persistedAccess, headers: existingProvider?.headers, models: setupDiscovery.value.models }) } } } }; } /** Read-only reachability probe for app-guided setup when no loaded model qualifies. */ async function detectAppGuidedLmstudioAvailability(ctx) { const connection = await resolveAppGuidedLmstudioConnection(ctx); if (!connection) return false; return (await fetchLmstudioModels(connection.discoveryRequest)).reachable; } /** Interactive LM Studio setup with connectivity and model-availability checks. */ async function promptAndConfigureLmstudioInteractive(params) { const promptText = params.prompter ? params.prompter.text.bind(params.prompter) : params.promptText; if (!promptText) throw new Error("LM Studio interactive setup requires a text prompter."); const note = params.prompter ? params.prompter.note.bind(params.prompter) : params.note; const defaultBaseUrl = resolveLmstudioSetupDefaultBaseUrl(); const baseUrlRaw = await promptText({ message: `${LMSTUDIO_PROVIDER_LABEL} base URL`, initialValue: defaultBaseUrl, placeholder: defaultBaseUrl, validate: (value) => value?.trim() ? void 0 : "Required" }); const baseUrl = resolveLmstudioInferenceBase(baseUrlRaw ?? defaultBaseUrl); let credentialInput; let credentialMode; const implicitRefMode = params.allowSecretRefPrompt === false && !params.secretInputMode; const autoRefEnvKey = process.env[LMSTUDIO_DEFAULT_API_KEY_ENV_VAR]?.trim(); const apiKey = params.prompter && implicitRefMode && autoRefEnvKey ? autoRefEnvKey : params.prompter ? await ensureApiKeyFromEnvOrPrompt({ config: params.config, workspaceDir: params.workspaceDir, provider: LMSTUDIO_PROVIDER_ID, envLabel: LMSTUDIO_DEFAULT_API_KEY_ENV_VAR, promptMessage: `${LMSTUDIO_PROVIDER_LABEL} API key`, normalize: (value) => value.trim(), validate: () => void 0, prompter: params.prompter, secretInputMode: params.allowSecretRefPrompt === false ? params.secretInputMode ?? "plaintext" : params.secretInputMode, setCredential: async (apiKeyValue, mode) => { credentialInput = apiKeyValue; credentialMode = mode; } }) : (await promptText({ message: `LM Studio API key`, placeholder: "sk-... (leave blank if auth is disabled)", validate: () => void 0 }) ?? "").trim(); const normalizedApiKey = normalizeOptionalSecretInput(apiKey); const credentialSource = credentialInput ?? (implicitRefMode && autoRefEnvKey ? `\${LM_API_TOKEN}` : apiKey); const credential = (params.prompter ? credentialMode === "ref" || hasConfiguredSecretInput(credentialSource) : normalizedApiKey !== void 0) ? params.prompter ? buildApiKeyCredential(LMSTUDIO_PROVIDER_ID, credentialSource, void 0, credentialMode ? { secretInputMode: credentialMode } : implicitRefMode && autoRefEnvKey ? { secretInputMode: "ref" } : void 0) : { type: "api_key", provider: LMSTUDIO_PROVIDER_ID, key: normalizedApiKey ?? apiKey } : void 0; const existingProvider = params.config.models?.providers?.[LMSTUDIO_PROVIDER_ID]; const persistedHeaders = existingProvider?.headers; const resolvedHeaders = await resolveLmstudioProviderHeaders({ config: params.config, env: process.env, headers: persistedHeaders }); const hasAuthorizationHeader = hasLmstudioAuthorizationHeader(resolvedHeaders); const setupDiscoveryApiKey = normalizedApiKey ?? (shouldUseLmstudioApiKeyPlaceholder({ hasModels: true, resolvedApiKey: void 0, hasAuthorizationHeader }) ? "lmstudio-local" : void 0); const discoverSetupModels = async () => { const result = await discoverLmstudioSetupModels({ baseUrl, apiKey: setupDiscoveryApiKey, ...resolvedHeaders ? { headers: resolvedHeaders } : {}, timeoutMs: 5e3 }); params.signal?.throwIfAborted(); return result; }; let setupDiscovery = await discoverSetupModels(); while ("failure" in setupDiscovery) { if (!params.isRemote || !params.prompter) { await note?.(setupDiscovery.failure.noteLines.join("\n"), "LM Studio"); throw new WizardCancelledError(setupDiscovery.failure.reason); } if (!setupDiscovery.failure.retryLine) { await note?.(setupDiscovery.failure.noteLines.join("\n"), "LM Studio"); throw new WizardCancelledError(setupDiscovery.failure.reason); } await note?.([setupDiscovery.failure.noteLines[0], setupDiscovery.failure.retryLine].join("\n"), "LM Studio"); if (!await params.prompter.confirm({ message: "Retry this LM Studio connection now?", initialValue: true })) throw new WizardCancelledError("LM Studio setup cancelled"); params.signal?.throwIfAborted(); setupDiscovery = await discoverSetupModels(); } let discoveredModels = setupDiscovery.value.models; if (params.prompter && !params.isRemote) { const requestedContextWindow = resolvePositiveInteger(await params.prompter.text({ message: "Preferred context length to load LM Studio models with (optional)", placeholder: "e.g. 32768 (leave blank to skip)", validate: (value) => value?.trim() ? resolvePositiveInteger(value) ? void 0 : "Enter a positive integer token count" : void 0 })); discoveredModels = applyRequestedContextWindowToAllModels({ models: discoveredModels, discoveryModels: setupDiscovery.value.discovery.models, requestedContextWindow }); } const allowlistEntries = mergeDiscoveredLmstudioAllowlistEntries({ existing: params.config.agents?.defaults?.models, discoveredModels }); const defaultModel = setupDiscovery.value.defaultModel; const persistedApiKey = resolvePersistedLmstudioApiKey({ currentApiKey: normalizedApiKey ? existingProvider?.apiKey : void 0, explicitAuth: resolveLmstudioProviderAuthMode(normalizedApiKey), fallbackApiKey: normalizedApiKey ? "LM_API_TOKEN" : void 0, preferFallbackApiKey: true, hasModels: discoveredModels.length > 0, hasAuthorizationHeader }) ?? (normalizedApiKey ? "LM_API_TOKEN" : void 0); if (!credential) await removeProviderAuthProfilesWithLock({ provider: LMSTUDIO_PROVIDER_ID, agentDir: params.agentDir }); return { profiles: credential ? [{ profileId: `${LMSTUDIO_PROVIDER_ID}:default`, credential }] : [], configPatch: { agents: { defaults: { models: allowlistEntries } }, models: { mode: params.config.models?.mode ?? "merge", providers: { [LMSTUDIO_PROVIDER_ID]: buildLmstudioSetupProviderConfig({ existingProvider, baseUrl, apiKey: persistedApiKey, headers: persistedHeaders, models: discoveredModels }) } } }, defaultModel }; } async function validateNonInteractiveLmstudioDiscovery(ctx, resetPreflight = false) { const customBaseUrl = normalizeOptionalSecretInput(ctx.opts.customBaseUrl); const baseUrl = resolveLmstudioInferenceBase(customBaseUrl || resolveLmstudioSetupDefaultInferenceBaseUrl()); const requestedModelId = normalizeOptionalSecretInput(ctx.opts.customModelId); const providerApiKey = normalizeOptionalSecretInput(ctx.opts.lmstudioApiKey); const resolved = await ctx.resolveApiKey({ provider: LMSTUDIO_PROVIDER_ID, flagValue: providerApiKey ?? normalizeOptionalSecretInput(ctx.opts.customApiKey), flagName: providerApiKey === void 0 ? "--custom-api-key" : "--lmstudio-api-key", envVar: LMSTUDIO_DEFAULT_API_KEY_ENV_VAR, envVarName: LMSTUDIO_DEFAULT_API_KEY_ENV_VAR, required: false }); const existingProvider = ctx.config.models?.providers?.[LMSTUDIO_PROVIDER_ID]; const persistedHeaders = existingProvider?.headers; const resolvedHeaders = await resolveLmstudioProviderHeaders({ config: ctx.config, env: process.env, headers: persistedHeaders }); const hasAuthorizationHeader = hasLmstudioAuthorizationHeader(resolvedHeaders); const useHeaderOnlyAuth = hasAuthorizationHeader && (!resolved || resolved.source !== "flag"); const setupDiscoveryApiKey = (useHeaderOnlyAuth ? void 0 : resolved?.key) ?? (shouldUseLmstudioApiKeyPlaceholder({ hasModels: true, resolvedApiKey: void 0, hasAuthorizationHeader }) ? "lmstudio-local" : void 0); if (!setupDiscoveryApiKey && !hasAuthorizationHeader) { ctx.runtime.error(`LM Studio API key is required. Set ${LMSTUDIO_DEFAULT_API_KEY_ENV_VAR} or pass --lmstudio-api-key.`); ctx.runtime.exit(1); return null; } const setupDiscovery = await discoverLmstudioSetupModels({ baseUrl, apiKey: setupDiscoveryApiKey, ...resolvedHeaders ? { headers: resolvedHeaders } : {}, requestedModelId, resetPreflight, timeoutMs: 5e3 }); if ("failure" in setupDiscovery) { ctx.runtime.error(setupDiscovery.failure.noteLines.join("\n")); ctx.runtime.exit(1); return null; } const discoveredModels = setupDiscovery.value.models; const selectedModelId = requestedModelId ?? setupDiscovery.value.defaultModelId; const selectedModel = selectedModelId ? discoveredModels.find((model) => model.id === selectedModelId) : void 0; const selectedModelLoaded = selectedModelId !== void 0 && setupDiscovery.value.loadedModelIds.has(selectedModelId); if (!selectedModelId || !selectedModel || !selectedModelLoaded) { const availableModels = discoveredModels.map((model) => model.id).join(", "); ctx.runtime.error(requestedModelId && selectedModel && !selectedModelLoaded ? [`LM Studio model ${requestedModelId} is installed but not loaded at ${baseUrl}.`, "Load that model in LM Studio, then re-run setup."].join("\n") : requestedModelId ? [`LM Studio model ${requestedModelId} was not found at ${baseUrl}.`, `Available models: ${availableModels}`].join("\n") : [`LM Studio did not expose a usable default model at ${baseUrl}.`, `Available models: ${availableModels || "(none)"}`].join("\n")); ctx.runtime.exit(1); return null; } return { baseUrl, customBaseUrl, discoveredModels, existingProvider, persistedHeaders, resolved, resolvedHeaders, selectedModelId, setupDiscoveryApiKey, useHeaderOnlyAuth }; } /** Checks endpoint auth and loaded models without mutating config, profiles, or the server. */ async function validateLmstudioNonInteractive(ctx) { return Boolean(await validateNonInteractiveLmstudioDiscovery(ctx, true)); } /** Non-interactive setup path backed by the shared self-hosted helper. */ async function configureLmstudioNonInteractive(ctx) { const validated = await validateNonInteractiveLmstudioDiscovery(ctx); if (!validated) return null; const { baseUrl, customBaseUrl, discoveredModels, existingProvider, persistedHeaders, resolved, resolvedHeaders, selectedModelId, setupDiscoveryApiKey, useHeaderOnlyAuth } = validated; const normalizedCtx = customBaseUrl ? { ...ctx, opts: { ...ctx.opts, customBaseUrl: baseUrl } } : ctx; if (useHeaderOnlyAuth) { await removeProviderAuthProfilesWithLock({ provider: LMSTUDIO_PROVIDER_ID, agentDir: normalizedCtx.agentDir }); const configWithoutStoredLmstudioAuth = stripLmstudioStoredAuthConfig(normalizedCtx.config); return applyProviderDefaultModel({ ...configWithoutStoredLmstudioAuth, models: { ...configWithoutStoredLmstudioAuth.models, mode: configWithoutStoredLmstudioAuth.models?.mode ?? "merge", providers: { ...configWithoutStoredLmstudioAuth.models?.providers, [LMSTUDIO_PROVIDER_ID]: buildLmstudioSetupProviderConfig({ existingProvider, baseUrl, headers: persistedHeaders, models: discoveredModels }) } } }, `${LMSTUDIO_PROVIDER_ID}/${selectedModelId}`); } const resolvedOrSynthetic = resolved ?? (setupDiscoveryApiKey ? { key: setupDiscoveryApiKey, source: "flag" } : null); if (!resolvedOrSynthetic) return null; const configured = await configureOpenAICompatibleSelfHostedProviderNonInteractive({ ctx: { ...normalizedCtx, opts: { ...normalizedCtx.opts, customModelId: selectedModelId }, resolveApiKey: async () => resolvedOrSynthetic }, providerId: LMSTUDIO_PROVIDER_ID, providerLabel: LMSTUDIO_PROVIDER_LABEL, defaultBaseUrl: resolveLmstudioSetupDefaultInferenceBaseUrl(), defaultApiKeyEnvVar: LMSTUDIO_DEFAULT_API_KEY_ENV_VAR, modelPlaceholder: LMSTUDIO_MODEL_PLACEHOLDER }); if (!configured) return null; const sharedProvider = configured.models?.providers?.[LMSTUDIO_PROVIDER_ID]; const resolvedSyntheticLocalKey = resolvedOrSynthetic.key === LMSTUDIO_LOCAL_API_KEY_PLACEHOLDER; const persistedApiKey = resolvePersistedLmstudioApiKey({ currentApiKey: resolvedSyntheticLocalKey ? void 0 : existingProvider?.apiKey, explicitAuth: resolveLmstudioProviderAuthMode(resolvedOrSynthetic.key), fallbackApiKey: resolvedSyntheticLocalKey ? LMSTUDIO_LOCAL_API_KEY_PLACEHOLDER : configured.models?.providers?.["lmstudio"]?.apiKey ?? "LM_API_TOKEN", preferFallbackApiKey: true, hasModels: discoveredModels.length > 0, hasAuthorizationHeader: hasLmstudioAuthorizationHeader(resolvedHeaders) }); return { ...configured, models: { ...configured.models, providers: { ...configured.models?.providers, [LMSTUDIO_PROVIDER_ID]: buildLmstudioSetupProviderConfig({ existingProvider, sharedProvider, baseUrl, apiKey: persistedApiKey, headers: persistedHeaders, models: discoveredModels }) } } }; } /** Discovers provider settings, merging explicit config with live model discovery. */ async function discoverLmstudioProvider(ctx) { const explicit = ctx.config.models?.providers?.[LMSTUDIO_PROVIDER_ID]; const explicitAuth = explicit?.auth; let explicitWithoutHeaders; if (explicit) { const { headers: _headers, auth: _auth, apiKey: _apiKey, ...rest } = explicit; explicitWithoutHeaders = rest; } const hasExplicitModels = Array.isArray(explicit?.models) && explicit.models.length > 0; const { apiKey, discoveryApiKey } = ctx.resolveProviderApiKey(LMSTUDIO_PROVIDER_ID); let resolvedHeaders; let hasAuthorizationHeader; let configuredDiscoveryApiKey; try { resolvedHeaders = await resolveLmstudioProviderHeaders({ config: ctx.config, env: ctx.env, headers: explicit?.headers }); hasAuthorizationHeader = hasLmstudioAuthorizationHeader(resolvedHeaders); configuredDiscoveryApiKey = await resolveLmstudioConfiguredApiKey({ config: ctx.config, env: ctx.env, allowUnresolved: hasAuthorizationHeader || Boolean(discoveryApiKey) }); } catch (error) { if (isLmstudioDiscoveryConfigResolutionError(error)) return null; throw error; } const resolvedDiscoveryApiKey = hasAuthorizationHeader ? void 0 : discoveryApiKey ?? configuredDiscoveryApiKey; const resolvedApiKey = apiKey ?? explicit?.apiKey; const explicitProvider = hasExplicitModels ? explicitWithoutHeaders : void 0; const provider = explicitProvider ?? await discoverLmstudioProviderCatalog({ baseUrl: explicit?.baseUrl, apiKey: resolvedDiscoveryApiKey, headers: resolvedHeaders, quiet: !apiKey && !explicit && !resolvedDiscoveryApiKey }); const models = provider.models; if (models.length === 0 && !apiKey && !explicit?.apiKey) return null; const persistedApiKey = resolvePersistedLmstudioApiKey({ currentApiKey: resolvedApiKey, explicitAuth, fallbackApiKey: LMSTUDIO_DEFAULT_API_KEY_ENV_VAR, hasModels: models.length > 0, hasAuthorizationHeader }); const persistedAuth = resolveLmstudioProviderAuthMode(persistedApiKey); return { provider: { ...provider, ...explicitWithoutHeaders, ...resolvedHeaders ? { headers: resolvedHeaders } : {}, baseUrl: resolveLmstudioInferenceBase(explicit?.baseUrl ?? provider.baseUrl), ...explicitProvider ? { api: explicitProvider.api ?? "openai-completions" } : {}, ...persistedApiKey ? { apiKey: persistedApiKey } : {}, ...persistedAuth ? { auth: persistedAuth } : {}, models } }; } async function prepareLmstudioDynamicModel(ctx) { const baseUrl = resolveLmstudioInferenceBase(ctx.providerConfig?.baseUrl); const { apiKey, headers } = await resolveLmstudioRequestContext({ config: ctx.config, agentDir: ctx.agentDir, env: process.env, providerHeaders: ctx.providerConfig?.headers }); const model = (await discoverLmstudioModels({ baseUrl, apiKey: apiKey ?? "", headers, quiet: true })).find((candidate) => candidate.id === ctx.modelId); if (!model) return; return { ...model, provider: LMSTUDIO_PROVIDER_ID, api: ctx.providerConfig?.api ?? "openai-completions", baseUrl, input: model.input.filter((entry) => entry === "text" || entry === "image") }; } //#endregion export { prepareLmstudioDynamicModel as a, prepareAppGuidedLmstudioSetup as i, detectAppGuidedLmstudioAvailability as n, promptAndConfigureLmstudioInteractive as o, discoverLmstudioProvider as r, validateLmstudioNonInteractive as s, configureLmstudioNonInteractive as t };