UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

257 lines (256 loc) 10.6 kB
import { l as asPositiveSafeInteger } from "../../number-coercion-CJQ8TR--.js"; import { t as createSubsystemLogger } from "../../subsystem-BzXSmsuh.js"; import { y as ssrfPolicyFromHttpBaseUrlAllowedHostname } from "../../ssrf-CvPEXMGn.js"; import { n as CUSTOM_LOCAL_AUTH_MARKER } from "../../model-auth-markers-Sq8HdQFX.js"; import { i as streamSimple } from "../../stream-4H1GSjKe.js"; import "../../llm-B2byLsni.js"; import { s as createPlainTextToolCallCompatWrapper } from "../../provider-stream-shared-BIV_zuwB.js"; import "../../logging-core-BH_8JYZP.js"; import "../../string-coerce-runtime-CEGJWkQ_.js"; import { t as definePluginEntry } from "../../plugin-entry-C7DUzV0e.js"; import "../../provider-auth-DAOC_qI9.js"; import "../../ssrf-runtime-BOGN5pUi.js"; import { F as LMSTUDIO_PROVIDER_LABEL, P as LMSTUDIO_PROVIDER_ID, c as resolveLmstudioRuntimeApiKey, f as shouldUseLmstudioSyntheticAuth, g as normalizeLmstudioConfiguredCatalogEntries, n as ensureLmstudioModelLoaded, o as resolveLmstudioProviderHeaders, v as normalizeLmstudioProviderConfig, w as LMSTUDIO_DEFAULT_API_KEY_ENV_VAR, y as resolveLmstudioInferenceBase } from "../../models.fetch-CZerIN69.js"; import { t as lmstudioMemoryEmbeddingProviderAdapter } from "../../memory-embedding-adapter-C75kveGE.js"; //#region extensions/lmstudio/src/stream.ts const log = createSubsystemLogger("extensions/lmstudio/stream"); const preloadInFlight = /* @__PURE__ */ new Map(); const preloadCooldown = /* @__PURE__ */ new Map(); const PRELOAD_BACKOFF_BASE_MS = 5e3; const PRELOAD_BACKOFF_MAX_MS = 3e5; function computePreloadBackoffMs(consecutiveFailures) { const raw = PRELOAD_BACKOFF_BASE_MS * 2 ** Math.max(0, consecutiveFailures - 1); return Math.min(PRELOAD_BACKOFF_MAX_MS, raw); } function recordPreloadSuccess(preloadKey) { preloadCooldown.delete(preloadKey); } function recordPreloadFailure(preloadKey, now) { const consecutiveFailures = (preloadCooldown.get(preloadKey)?.consecutiveFailures ?? 0) + 1; const entry = { consecutiveFailures, untilMs: now + computePreloadBackoffMs(consecutiveFailures) }; preloadCooldown.set(preloadKey, entry); return entry; } function isPreloadCoolingDown(preloadKey, now) { const entry = preloadCooldown.get(preloadKey); if (!entry) return; if (entry.untilMs <= now) { preloadCooldown.delete(preloadKey); return; } return entry; } function normalizeLmstudioModelKey(modelId) { const trimmed = modelId.trim(); if (trimmed.toLowerCase().startsWith("lmstudio/")) return trimmed.slice(9).trim(); return trimmed; } function resolveRequestedContextLength(model) { const contextTokens = asPositiveSafeInteger(model.contextTokens); if (contextTokens !== void 0) return contextTokens; const contextWindow = asPositiveSafeInteger(model.contextWindow); if (contextWindow !== void 0) return contextWindow; } function resolveModelHeaders(model) { if (!model.headers || typeof model.headers !== "object" || Array.isArray(model.headers)) return; return model.headers; } function toRecord(value) { return value && typeof value === "object" ? value : void 0; } function shouldPreloadLmstudioModels(value) { return toRecord(toRecord(value)?.params)?.preload !== false; } function withLmstudioUsageCompat(model) { return { ...model, compat: { ...model.compat && typeof model.compat === "object" ? model.compat : {}, supportsUsageInStreaming: true } }; } function createPreloadKey(params) { return `${params.baseUrl}::${params.modelKey}::${params.requestedContextLength ?? "default"}`; } async function ensureLmstudioModelLoadedBestEffort(params) { const providerHeaders = { ...(params.ctx.config?.models?.providers?.[LMSTUDIO_PROVIDER_ID])?.headers, ...params.modelHeaders }; const runtimeApiKey = typeof params.options?.apiKey === "string" && params.options.apiKey.trim().length > 0 ? params.options.apiKey.trim() : void 0; const headers = await resolveLmstudioProviderHeaders({ config: params.ctx.config, headers: providerHeaders }); const configuredApiKey = runtimeApiKey !== void 0 ? void 0 : await resolveLmstudioRuntimeApiKey({ config: params.ctx.config, agentDir: params.ctx.agentDir, headers: providerHeaders }); await ensureLmstudioModelLoaded({ baseUrl: params.baseUrl, apiKey: runtimeApiKey ?? configuredApiKey, headers, ssrfPolicy: ssrfPolicyFromHttpBaseUrlAllowedHostname(params.baseUrl), modelKey: params.modelKey, requestedContextLength: params.requestedContextLength }); } function wrapLmstudioInferencePreload(ctx) { const underlying = ctx.streamFn ?? streamSimple; const streamWithPlainTextToolCalls = createPlainTextToolCallCompatWrapper(underlying); return (model, context, options) => { if (model.provider !== "lmstudio") return underlying(model, context, options); const modelKey = normalizeLmstudioModelKey(model.id); if (!modelKey) return underlying(model, context, options); const providerConfig = ctx.config?.models?.providers?.[LMSTUDIO_PROVIDER_ID]; if (!shouldPreloadLmstudioModels(providerConfig)) return streamWithPlainTextToolCalls(withLmstudioUsageCompat(model), context, options); const providerBaseUrl = providerConfig?.baseUrl; const resolvedBaseUrl = resolveLmstudioInferenceBase(typeof model.baseUrl === "string" ? model.baseUrl : providerBaseUrl); const requestedContextLength = resolveRequestedContextLength(model); const preloadKey = createPreloadKey({ baseUrl: resolvedBaseUrl, modelKey, requestedContextLength }); const cooldownEntry = isPreloadCoolingDown(preloadKey, Date.now()); const preloadPromise = preloadInFlight.get(preloadKey) ?? (cooldownEntry ? void 0 : (() => { const created = ensureLmstudioModelLoadedBestEffort({ baseUrl: resolvedBaseUrl, modelKey, requestedContextLength, options, ctx, modelHeaders: resolveModelHeaders(model) }).then(() => { recordPreloadSuccess(preloadKey); }, (error) => { const entry = recordPreloadFailure(preloadKey, Date.now()); throw Object.assign(/* @__PURE__ */ new Error("preload-failed"), { cause: error, consecutiveFailures: entry.consecutiveFailures, cooldownMs: entry.untilMs - Date.now() }); }).finally(() => { preloadInFlight.delete(preloadKey); }); preloadInFlight.set(preloadKey, created); return created; })()); return (async () => { if (preloadPromise) try { await preloadPromise; } catch (error) { const annotated = error; const cause = annotated.cause ?? error; const failures = annotated.consecutiveFailures ?? 1; const cooldownSec = Math.max(0, Math.round((annotated.cooldownMs ?? 0) / 1e3)); log.warn(`LM Studio inference preload failed for "${modelKey}" (${failures} consecutive failure${failures === 1 ? "" : "s"}, next preload attempt skipped for ~${cooldownSec}s); continuing without preload: ${String(cause)}`); } else if (cooldownEntry) log.debug(`LM Studio inference preload for "${modelKey}" skipped while backoff active (${cooldownEntry.consecutiveFailures} prior failures)`); const stream = streamWithPlainTextToolCalls(withLmstudioUsageCompat(model), context, options); return stream instanceof Promise ? await stream : stream; })(); }; } //#endregion //#region extensions/lmstudio/index.ts const PROVIDER_ID = "lmstudio"; const cachedDynamicModels = /* @__PURE__ */ new Map(); function resolveLmstudioAugmentedCatalogEntries(config) { if (!config) return []; return normalizeLmstudioConfiguredCatalogEntries(config.models?.providers?.lmstudio?.models).map((entry) => ({ provider: PROVIDER_ID, id: entry.id, name: entry.name ?? entry.id, compat: { ...entry.compat, supportsUsageInStreaming: true }, contextWindow: entry.contextWindow, contextTokens: entry.contextTokens, reasoning: entry.reasoning, input: entry.input })); } /** Lazily loads setup helpers so provider wiring stays lightweight at startup. */ async function loadProviderSetup() { return await import("./api.js"); } var lmstudio_default = definePluginEntry({ id: PROVIDER_ID, name: "LM Studio Provider", description: "Bundled LM Studio provider plugin", register(api) { api.registerMemoryEmbeddingProvider(lmstudioMemoryEmbeddingProviderAdapter); api.registerProvider({ id: PROVIDER_ID, label: "LM Studio", docsPath: "/providers/lmstudio", envVars: [LMSTUDIO_DEFAULT_API_KEY_ENV_VAR], auth: [{ id: "custom", label: LMSTUDIO_PROVIDER_LABEL, hint: "Local/self-hosted LM Studio server", kind: "custom", run: async (ctx) => { return await (await loadProviderSetup()).promptAndConfigureLmstudioInteractive({ config: ctx.config, agentDir: ctx.agentDir, prompter: ctx.prompter, secretInputMode: ctx.secretInputMode, allowSecretRefPrompt: ctx.allowSecretRefPrompt }); }, runNonInteractive: async (ctx) => { return await (await loadProviderSetup()).configureLmstudioNonInteractive(ctx); } }], catalog: { order: "late", run: async (ctx) => { return await (await loadProviderSetup()).discoverLmstudioProvider(ctx); } }, resolveSyntheticAuth: ({ providerConfig }) => { if (!shouldUseLmstudioSyntheticAuth(providerConfig)) return; return { apiKey: CUSTOM_LOCAL_AUTH_MARKER, source: "models.providers.lmstudio (synthetic local key)", mode: "api-key" }; }, shouldDeferSyntheticProfileAuth: ({ resolvedApiKey }) => resolvedApiKey?.trim() === "lmstudio-local" || resolvedApiKey?.trim() === "custom-local", normalizeConfig: ({ providerConfig }) => normalizeLmstudioProviderConfig(providerConfig), prepareDynamicModel: async (ctx) => { const providerSetup = await loadProviderSetup(); cachedDynamicModels.set(ctx.providerConfig?.baseUrl ?? "", await providerSetup.prepareLmstudioDynamicModels(ctx)); }, resolveDynamicModel: (ctx) => cachedDynamicModels.get(ctx.providerConfig?.baseUrl ?? "")?.find((model) => model.id === ctx.modelId), augmentModelCatalog: (ctx) => resolveLmstudioAugmentedCatalogEntries(ctx.config), wrapStreamFn: wrapLmstudioInferencePreload, wizard: { setup: { choiceId: PROVIDER_ID, choiceLabel: "LM Studio", choiceHint: "Local/self-hosted LM Studio server", groupId: PROVIDER_ID, groupLabel: "LM Studio", groupHint: "Self-hosted open-weight models", methodId: "custom" }, modelPicker: { label: "LM Studio (custom)", hint: "Detect models from LM Studio /api/v1/models", methodId: "custom" } } }); } }); //#endregion export { lmstudio_default as default };