import {
removeProviderAuthProfilesWithLock,
buildApiKeyCredential,
ensureApiKeyFromEnvOrPrompt,
normalizeOptionalSecretInput,
type OpenClawConfig,
type SecretInput,
type SecretInputMode,
} from "openclaw/plugin-sdk/provider-auth"; import type {
ModelDefinitionConfig,
ModelProviderConfig,
} from "openclaw/plugin-sdk/provider-model-shared"; import { withAgentModelAliases } from "openclaw/plugin-sdk/provider-onboard"; import {
applyProviderDefaultModel,
configureOpenAICompatibleSelfHostedProviderNonInteractive,
type ProviderAuthMethodNonInteractiveContext,
type ProviderAuthResult,
type ProviderCatalogContext,
type ProviderPrepareDynamicModelContext,
type ProviderRuntimeModel,
} from "openclaw/plugin-sdk/provider-setup"; import { WizardCancelledError, type WizardPrompter } from "openclaw/plugin-sdk/setup"; import {
LMSTUDIO_DEFAULT_API_KEY_ENV_VAR,
LMSTUDIO_DEFAULT_INFERENCE_BASE_URL,
LMSTUDIO_LOCAL_API_KEY_PLACEHOLDER,
LMSTUDIO_MODEL_PLACEHOLDER,
LMSTUDIO_DEFAULT_BASE_URL,
LMSTUDIO_PROVIDER_LABEL,
LMSTUDIO_DEFAULT_MODEL_ID,
LMSTUDIO_PROVIDER_ID as PROVIDER_ID,
} from "./defaults.js"; import { discoverLmstudioModels, fetchLmstudioModels } from "./models.fetch.js"; import {
mapLmstudioWireModelsToConfig,
type LmstudioModelWire,
resolveLmstudioInferenceBase,
} from "./models.js"; import {
hasLmstudioAuthorizationHeader,
resolveLmstudioProviderAuthMode,
shouldUseLmstudioApiKeyPlaceholder,
} from "./provider-auth.js"; import {
resolveLmstudioConfiguredApiKey,
resolveLmstudioProviderHeaders,
resolveLmstudioRequestContext,
} from "./runtime.js";
function resolveLmstudioDiscoveryFailure(params: {
baseUrl: string;
discovery: LmstudioDiscoveryResult;
}): { noteLines: [string, string]; reason: string } | null { 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.",
],
reason: "LM Studio not reachable",
};
} if (discovery.status !== undefined && discovery.status >= 400) { return {
noteLines: [
`LM Studio returned HTTP ${discovery.status} while listing models at ${baseUrl}.`, "Check the base URL and API key, then re-run setup.",
],
reason: `LM Studio discovery failed (${discovery.status})`,
};
} const hasUsableModel = discovery.models.some(
(model) => model.type === "llm" && Boolean(model.key?.trim()),
); if (!hasUsableModel) { return {
noteLines: [
`No LM Studio LLM models were found at ${baseUrl}.`, "Load at least one model in LM Studio (or run lms load), then re-run setup.",
],
reason: "No LM Studio models found",
};
} returnnull;
}
/** Keeps explicit model entries first and appends unique discovered entries. */ function mergeDiscoveredModels(params: {
explicitModels?: ModelDefinitionConfig[];
discoveredModels?: ModelDefinitionConfig[];
}): ModelDefinitionConfig[] { const explicitModels = Array.isArray(params.explicitModels) ? params.explicitModels : []; const discoveredModels = Array.isArray(params.discoveredModels) ? params.discoveredModels : []; if (explicitModels.length === 0) { return discoveredModels;
} if (discoveredModels.length === 0) { return explicitModels;
}
const merged = [...explicitModels]; const seen = new Set(explicitModels.map((model) => model.id.trim()).filter(Boolean)); for (const model of discoveredModels) { const id = model.id.trim(); if (!id || seen.has(id)) { continue;
}
seen.add(id);
merged.push(model);
} return merged;
}
const existingProvider = normalizedCtx.config.models?.providers?.[PROVIDER_ID]; // Auth setup updates auth/profile/provider model fields but does not mutate // user-provided header overrides. Runtime request assembly is the source of truth for auth. const persistedHeaders = existingProvider?.headers; const resolvedHeaders = await resolveLmstudioProviderHeaders({
config: normalizedCtx.config,
env: process.env,
headers: persistedHeaders,
}); const hasAuthorizationHeader = hasLmstudioAuthorizationHeader(resolvedHeaders); const useHeaderOnlyAuth = hasAuthorizationHeader && (!resolved || resolved.source !== "flag"); const setupDiscoveryApiKey =
(useHeaderOnlyAuth ? undefined : resolved?.key) ??
(shouldUseLmstudioApiKeyPlaceholder({
hasModels: true,
resolvedApiKey: undefined,
hasAuthorizationHeader,
})
? LMSTUDIO_LOCAL_API_KEY_PLACEHOLDER
: undefined); if (!setupDiscoveryApiKey && !hasAuthorizationHeader) {
normalizedCtx.runtime.error(
`LM Studio API key is required. Set ${LMSTUDIO_DEFAULT_API_KEY_ENV_VAR} or pass --lmstudio-api-key.`,
);
normalizedCtx.runtime.exit(1); returnnull;
} const setupDiscovery = await discoverLmstudioSetupModels({
baseUrl,
apiKey: setupDiscoveryApiKey,
...(resolvedHeaders ? { headers: resolvedHeaders } : {}),
timeoutMs: 5000,
}); if ("failure" in setupDiscovery) {
normalizedCtx.runtime.error(setupDiscovery.failure.noteLines.join("\n"));
normalizedCtx.runtime.exit(1); returnnull;
} const discoveredModels = setupDiscovery.value.models; const selectedModelId = requestedModelId ?? setupDiscovery.value.defaultModelId; const selectedModel = selectedModelId
? discoveredModels.find((model) => model.id === selectedModelId)
: undefined; if (!selectedModelId || !selectedModel) { const availableModels = discoveredModels.map((model) => model.id).join(", ");
normalizedCtx.runtime.error(
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"),
);
normalizedCtx.runtime.exit(1); returnnull;
} if (useHeaderOnlyAuth) {
await removeProviderAuthProfilesWithLock({
provider: 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,
[PROVIDER_ID]: buildLmstudioSetupProviderConfig({
existingProvider,
baseUrl,
headers: persistedHeaders,
models: discoveredModels,
}),
},
},
},
`${PROVIDER_ID}/${selectedModelId}`,
);
} const resolvedOrSynthetic =
resolved ??
(setupDiscoveryApiKey
? {
key: setupDiscoveryApiKey,
source: "flag" as const,
}
: null); if (!resolvedOrSynthetic) { returnnull;
}
// Delegate to the shared helper even when modelId is set so that onboarding // state and credential storage are handled consistently. The pre-resolved key // is injected via resolveApiKey to skip a second prompt. The returned config // is then post-patched below to add the discovered model list and base URL. const configured = await configureShared({
...normalizedCtx,
opts: {
...normalizedCtx.opts,
customModelId: selectedModelId,
},
resolveApiKey: async () => resolvedOrSynthetic,
}); if (!configured) { returnnull;
} const sharedProvider = configured.models?.providers?.[PROVIDER_ID]; const resolvedSyntheticLocalKey = resolvedOrSynthetic.key === LMSTUDIO_LOCAL_API_KEY_PLACEHOLDER; const persistedApiKey = resolvePersistedLmstudioApiKey({ // If this run resolved to keyless local mode, avoid preserving stale env markers.
currentApiKey: resolvedSyntheticLocalKey ? undefined : existingProvider?.apiKey,
explicitAuth: resolveLmstudioProviderAuthMode(resolvedOrSynthetic.key),
fallbackApiKey: resolvedSyntheticLocalKey
? LMSTUDIO_LOCAL_API_KEY_PLACEHOLDER
: (configured.models?.providers?.[PROVIDER_ID]?.apiKey ?? LMSTUDIO_DEFAULT_API_KEY_ENV_VAR),
preferFallbackApiKey: true,
hasModels: discoveredModels.length > 0,
hasAuthorizationHeader: hasLmstudioAuthorizationHeader(resolvedHeaders),
});
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.