openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
592 lines (591 loc) • 27.2 kB
JavaScript
import { l as normalizeOptionalString, o as normalizeLowercaseStringOrEmpty } from "./string-coerce-1VuiLaaR.mjs";
import { w as parseStrictPositiveInteger } from "./number-coercion-CuVhdCBs.mjs";
import { r as normalizeProviderId } from "./provider-id-D1VImdKk.mjs";
import { a as resolveAgentDir, d as resolveAgentWorkspaceDir } from "./agent-scope-config-CDYpzA0r.mjs";
import { t as resolveDefaultAgentWorkspaceDir } from "./workspace-default-path-Cwkw6Ed6.mjs";
import { g as resolveSessionAgentId } from "./agent-scope-CG17uXFn.mjs";
import { i as buildModelAliasIndex, o as dedupeModelCatalogEntries, p as resolveBareModelDefaultProvider, v as resolveModelRefFromString } from "./model-selection-shared-DcM6hdAC.mjs";
import { a as resolveModelCatalogIdentityKey, r as openAIModelCatalogRoutePolicy } from "./openai-model-routes-C1mdGNT1.mjs";
import { t as resolveDefaultModelForAgent } from "./model-selection-config-BdeHEviO.mjs";
import { a as listOpenAIAuthProfileProvidersForAgentRuntime } from "./openai-routing-CI5rYN6I.mjs";
import { f as formatProviderLoginCommand } from "./oauth-refresh-failure-B2gKOXl5.mjs";
import { t as resolveAgentHarnessPolicy } from "./policy-C25OZkRl.mjs";
import { s as getPreparedModelRuntimeAuthStore } from "./prepared-model-runtime-auth-Dn7HF9Vu.mjs";
import "./model-selection-CC48fyGE.mjs";
import { r as listCliRuntimeModelBackendBindings } from "./cli-backends-t2xvJDIp.mjs";
import { o as isRetiredModelPickerProvider } from "./model-runtime-aliases-t0kOML62.mjs";
import "./workspace-BHjQqjjT.mjs";
import { r as PreparedModelRuntimePublicationSupersededError, t as PreparedModelRuntimeOwnerNotPublishedError } from "./prepared-model-runtime.errors-5KDV2tDJ.mjs";
import { c as loadPublishedPreparedModelCatalogOwnerSnapshot } from "./prepared-model-catalog-CntkE0sm.mjs";
import { t as getChannelPlugin } from "./registry-bbtfCBEf.mjs";
import "./plugins-9R-VsYQh.mjs";
import { r as defineAuthorizedTextCommand } from "./command-gates-B9VOdSH7.mjs";
import { n as resolveAgentRuntimeLabel } from "./agent-runtime-label--_OLo_UB.mjs";
import { n as createModelVisibilityPolicy } from "./model-visibility-policy-CJ73IRCN.mjs";
import { i as normalizeRuntimeChoiceId, o as resolveRuntimeNormalization } from "./model-runtime-normalization-CqEACQNb.mjs";
import { n as resolveCatalogDecisionRuntime, t as createModelCatalogDecisions } from "./model-catalog-decisions-BnfdvuhD.mjs";
import { n as resolveLogicalModelCatalogEntryState, r as resolveLogicalVisibleModelCatalog } from "./model-catalog-visibility-vJ2_y7h9.mjs";
import { a as resolveProviderChannelLoginChoice } from "./provider-login-options-BFCrqQAc.mjs";
import { t as resolveModelAuthLabel } from "./model-auth-label-qTNFhvdn.mjs";
//#region src/shared/model-runtime-route.ts
/** Presentation only: a provider name alone does not identify Anthropic's execution route. */
function resolveModelRuntimeRoute(provider, runtimeId) {
if (runtimeId === "claude-cli" || provider === "claude-cli" && !runtimeId) return "claudeCli";
if (provider === "anthropic") {
if (runtimeId === "openclaw") return "anthropicApi";
if (!runtimeId) return "anthropicConfigured";
}
}
//#endregion
//#region src/auto-reply/reply/commands-models-menu.ts
const CUSTOM_MODEL_SETUP_GUIDANCE = "Set up this connection with the custom-provider guide: https://docs.openclaw.ai/concepts/model-providers/custom-providers";
const MODEL_PROVIDER_ROUTE_DETAILS = {
claudeCli: "Claude CLI runs through Claude Code using its native login or a selected saved account. An explicitly selected API-key account has separate API billing; CLI does not mean free or subscription-only.",
anthropicConfigured: "Anthropic models can use the API or Claude CLI. Check each model's route label and selected account: API-key usage is billed separately from a Claude subscription."
};
function buildModelsMenu(data) {
const modelNames = new Map(data.modelNames);
const byProvider = /* @__PURE__ */ new Map();
for (const [id, models] of data.byProvider) {
const notices = /* @__PURE__ */ new Set();
const providerRoute = resolveModelRuntimeRoute(id);
if (providerRoute === "claudeCli" || providerRoute === "anthropicConfigured") notices.add(MODEL_PROVIDER_ROUTE_DETAILS[providerRoute]);
let available = 0;
const loginSupported = data.loginProviders.has(id);
const loginCommand = formatProviderLoginCommand(id);
const pending = data.pendingProviders?.includes(id) === true;
for (const model of models) {
const key = `${id}/${model}`;
const state = data.modelAvailability.get(key);
const route = resolveModelRuntimeRoute(id, state.runtimeId);
const routeLabel = route === "claudeCli" ? "Claude CLI" : route === "anthropicApi" ? "API" : "";
if (routeLabel) modelNames.set(key, `${routeLabel} · ${data.modelNames.get(key) ?? model}`);
if (state.availability === true) {
available += 1;
continue;
}
let label;
let recovery;
if (state.runtimeAuth?.source === "native" && state.availability === void 0) {
label = pending ? "Checking native agent" : "Connection not confirmed";
recovery = pending ? "Run /models again when discovery finishes." : "Check the native app on the Gateway host, then run /models again.";
} else switch (state.unavailableReason) {
case "missing-auth":
label = "Sign-in needed";
recovery = loginSupported ? `Connect with ${loginCommand}.` : CUSTOM_MODEL_SETUP_GUIDANCE;
break;
case "auth-failed":
label = "Sign-in failed";
recovery = loginSupported ? `Sign in again with ${loginCommand}.` : CUSTOM_MODEL_SETUP_GUIDANCE;
break;
case "cooldown":
label = "Temporarily unavailable";
recovery = "Try again later or choose another model.";
break;
default:
label = state.availability === false ? "Unavailable" : "Connection not confirmed";
recovery = state.availability === false ? "Run /models again or choose another model." : loginSupported ? `Connect with ${loginCommand}, or choose another model.` : CUSTOM_MODEL_SETUP_GUIDANCE;
}
modelNames.set(key, `${label} — ${modelNames.get(key) ?? model}`);
notices.add(`${id}: ${label}. ${recovery}`);
}
byProvider.set(id, {
available,
notice: [...notices].join("\n")
});
}
return {
modelNames,
byProvider
};
}
//#endregion
//#region src/auto-reply/reply/commands-models-catalog.ts
function buildRuntimeChoice(params) {
const id = normalizeRuntimeChoiceId(params.runtime);
const label = resolveAgentRuntimeLabel({
config: params.cfg,
resolvedHarness: id
});
return {
id,
label,
description: id === "openclaw" ? "Use OpenClaw's built-in agent and tools." : `Use ${label} to run this model.`
};
}
/** Undefined is unknown; an empty list is an authoritative refusal. */
function getModelsRuntimeChoices(data, provider, model) {
if (data.isCurrent?.() === false) return;
return model ? data.runtimeChoicesByModel?.get(`${normalizeProviderId(provider)}/${model}`) : data.runtimeChoicesByProvider?.get(normalizeProviderId(provider));
}
function buildPreparedModelsProviderData(cfg, agentId, options = {}) {
return loadModelsProviderData(cfg, agentId, options);
}
async function loadModelsProviderData(config, agentId, options, agentDir) {
const owner = await loadPublishedPreparedModelCatalogOwnerSnapshot({
config,
...agentId ? { agentId } : {},
...agentDir ? { agentDir } : {},
...options.workspaceDir ? { workspaceDir: options.workspaceDir } : {},
readOnly: true
});
owner.loadFullModelCatalog?.().catch(() => void 0);
const cfg = owner.config;
const runtimeNormalization = resolveRuntimeNormalization(cfg);
const resolvedDefault = resolveDefaultModelForAgent({
cfg,
agentId,
...runtimeNormalization
});
const workspaceDir = options.workspaceDir ?? (agentId ? resolveAgentWorkspaceDir(cfg, agentId) : void 0) ?? resolveDefaultAgentWorkspaceDir();
const cliRuntimeProviders = new Set(listCliRuntimeModelBackendBindings().map((binding) => normalizeProviderId(binding.runtime)));
const snapshot = owner.modelCatalog;
const authStore = getPreparedModelRuntimeAuthStore(owner);
const catalog = snapshot.entries;
const visibilityPolicy = createModelVisibilityPolicy({
cfg,
catalog,
defaultProvider: resolvedDefault.provider,
defaultModel: resolvedDefault,
agentId,
...runtimeNormalization
});
if (!authStore) throw new Error("Model catalog owner omitted its auth store");
const decisionParams = {
cfg,
agentId: owner.agentId ?? agentId ?? "main",
agentDir: owner.agentDir,
workspaceDir,
snapshot,
metadataSnapshot: owner.metadataSnapshot,
preparedAuthStore: authStore,
preparedRuntimeAuthModes: owner.authModes,
pluginRegistry: owner.pluginRegistry,
observationConfig: owner.observationConfig,
isCurrent: owner.isCurrent,
preferredProfileId: options.sessionEntry?.authProfileOverride,
pinnedProfileId: options.sessionEntry?.authProfileOverrideSource === "user" ? options.sessionEntry.authProfileOverride : void 0,
profileProvider: options.sessionEntry?.providerOverride ?? options.sessionEntry?.modelProvider,
runtimeOverride: options.sessionEntry?.agentRuntimeOverride
};
const decisions = createModelCatalogDecisions(decisionParams);
const defaultDecisions = decisionParams.runtimeOverride && resolveModelRuntimeRoute(resolvedDefault.provider) ? createModelCatalogDecisions({
...decisionParams,
runtimeOverride: void 0
}) : decisions;
const decisionsForEntry = (entry) => normalizeProviderId(entry.provider) === resolvedDefault.provider && entry.id === resolvedDefault.model ? defaultDecisions : decisions;
const incompatibleModelKeys = /* @__PURE__ */ new Set();
const modelAvailability = /* @__PURE__ */ new Map();
const recordModelAvailability = (entry, evaluation, provider = entry.provider) => {
modelAvailability.set(`${normalizeProviderId(provider)}/${entry.id}`, {
availability: evaluation.availability,
unavailableReason: evaluation.unavailableReason,
runtimeAuth: evaluation.runtimeAuth,
runtimeId: resolveModelRuntimeRoute(provider) ? resolveCatalogDecisionRuntime({
cfg,
agentId: owner.agentId ?? agentId ?? "main",
entry,
evaluation,
pluginRegistry: owner.pluginRegistry
})?.id : void 0
});
};
const hasAuth = options.view === "all" ? async () => true : async (provider, ref) => {
const entry = catalog.find((row) => row.provider === provider && row.id === ref?.modelId);
if (!entry) return false;
const selectionDecisions = decisionsForEntry(entry);
return selectionDecisions.evaluateNative(entry, await selectionDecisions.evaluateEntry(entry)).availability === true;
};
const visibleCatalog = await resolveLogicalVisibleModelCatalog({
cfg,
metadataSnapshot: owner.metadataSnapshot,
catalog,
defaultProvider: resolvedDefault.provider,
defaultModel: resolvedDefault,
selectedModel: resolvedDefault,
agentId,
workspaceDir,
view: options.view,
policy: visibilityPolicy,
routePolicy: openAIModelCatalogRoutePolicy,
routeVariants: snapshot.routeVariants,
evaluateEntry: async (entry, routeVariants) => {
const selectionDecisions = decisionsForEntry(entry);
const evaluation = selectionDecisions.evaluateNative(entry, await selectionDecisions.evaluateEntry(entry, routeVariants));
recordModelAvailability(entry, evaluation);
if (evaluation.routeResolution?.kind === "incompatible") incompatibleModelKeys.add(resolveModelCatalogIdentityKey(entry));
return resolveLogicalModelCatalogEntryState({
evaluation,
authBacked: options.view === "all" || evaluation.availability === true,
routePolicy: openAIModelCatalogRoutePolicy
});
}
});
const aliasIndex = buildModelAliasIndex({
cfg,
defaultProvider: resolvedDefault.provider,
agentId,
...runtimeNormalization
});
const restrictToProviderWildcards = options.view !== "all" && visibilityPolicy.hasProviderWildcards;
const useUnfilteredCliCatalog = options.view === "all" || visibilityPolicy.allowAny || visibilityPolicy.allowConfigPath === "agents.defaults.models";
const byProvider = /* @__PURE__ */ new Map();
const add = (p, m) => {
const key = normalizeProviderId(p);
if (isRetiredModelPickerProvider(key)) return;
if (restrictToProviderWildcards && !(useUnfilteredCliCatalog && cliRuntimeProviders.has(key)) && !visibilityPolicy.allows({
provider: key,
model: m
})) return;
const set = byProvider.get(key) ?? /* @__PURE__ */ new Set();
set.add(m);
byProvider.set(key, set);
};
const addRawModelRef = (raw) => {
const trimmed = normalizeOptionalString(raw);
if (!trimmed) return;
const defaultProvider = !trimmed.includes("/") ? resolveBareModelDefaultProvider({
cfg,
catalog,
model: trimmed,
defaultProvider: resolvedDefault.provider,
agentId,
manifestPlugins: runtimeNormalization.manifestPlugins
}) : resolvedDefault.provider;
const resolved = resolveModelRefFromString({
cfg,
agentId,
raw: trimmed,
defaultProvider,
aliasIndex,
...runtimeNormalization
});
if (!resolved) return;
if (incompatibleModelKeys.has(resolveModelCatalogIdentityKey({
provider: resolved.ref.provider,
id: resolved.ref.model
}))) return;
add(resolved.ref.provider, resolved.ref.model);
};
const addModelConfigEntries = () => {
for (const modelConfig of [cfg.agents?.defaults?.model, cfg.agents?.defaults?.imageModel]) if (typeof modelConfig === "string") addRawModelRef(modelConfig);
else if (modelConfig && typeof modelConfig === "object") {
addRawModelRef(modelConfig.primary);
for (const fallback of modelConfig.fallbacks ?? []) addRawModelRef(fallback);
}
};
for (const entry of visibleCatalog) {
if (incompatibleModelKeys.has(resolveModelCatalogIdentityKey(entry))) continue;
add(entry.provider, entry.id);
}
for (const entry of catalog) if (useUnfilteredCliCatalog && cliRuntimeProviders.has(normalizeProviderId(entry.provider)) && await hasAuth(entry.provider, {
modelId: entry.id,
api: entry.api,
baseUrl: entry.baseUrl
})) add(entry.provider, entry.id);
for (const raw of visibilityPolicy.exactModelRefs) addRawModelRef(raw);
if (!incompatibleModelKeys.has(resolveModelCatalogIdentityKey({
provider: resolvedDefault.provider,
id: resolvedDefault.model
}))) add(resolvedDefault.provider, resolvedDefault.model);
addModelConfigEntries();
const pendingProviders = decisions.snapshot.pendingProviders?.filter((provider) => !isRetiredModelPickerProvider(provider) && (options.view === "all" || visibilityPolicy.allowAny || [...visibilityPolicy.allowedKeys].some((key) => key.startsWith(`${provider}/`))));
for (const provider of pendingProviders ?? []) if (!byProvider.has(provider)) byProvider.set(provider, /* @__PURE__ */ new Set());
const providers = [...byProvider.keys()].toSorted();
const loginProviders = new Set(providers.filter((provider) => resolveProviderChannelLoginChoice(provider, {
config: cfg,
workspaceDir,
metadataSnapshot: owner.metadataSnapshot
}).status !== "unsupported"));
const modelNames = /* @__PURE__ */ new Map();
for (const entry of [...catalog, ...visibleCatalog]) if (entry.name && entry.name !== entry.id) modelNames.set(`${normalizeProviderId(entry.provider)}/${entry.id}`, entry.name);
const runtimeChoicesByProvider = /* @__PURE__ */ new Map();
const runtimeChoicesByModel = /* @__PURE__ */ new Map();
for (const [provider, models] of byProvider) {
const providerChoices = /* @__PURE__ */ new Map();
for (const model of models) {
const entry = [...visibleCatalog, ...catalog].find((row) => normalizeProviderId(row.provider) === provider && row.id === model);
const authEntry = entry ?? {
provider,
id: model,
name: model
};
const selectionDecisions = decisionsForEntry(authEntry);
const variants = snapshot.routeVariants.filter((row) => resolveModelCatalogIdentityKey(row) === resolveModelCatalogIdentityKey(authEntry));
if (!modelAvailability.has(`${provider}/${model}`)) recordModelAvailability(authEntry, selectionDecisions.evaluateNative(authEntry, await selectionDecisions.evaluateEntry(authEntry, variants.length ? variants : [authEntry])), provider);
if (!entry) continue;
const runtimes = await selectionDecisions.runtimeChoices(entry, variants.length ? variants : [entry]);
if (!runtimes) continue;
const choices = runtimes.map((runtime) => buildRuntimeChoice({
cfg,
runtime
}));
runtimeChoicesByModel.set(`${provider}/${model}`, choices);
for (const choice of choices) providerChoices.set(choice.id, choice);
}
runtimeChoicesByProvider.set(provider, [...providerChoices.values()]);
}
if (!owner.isCurrent()) throw new PreparedModelRuntimePublicationSupersededError("model browse owner was superseded");
return {
byProvider,
pendingProviders,
providers,
resolvedDefault,
modelNames,
modelMenu: buildModelsMenu({
byProvider,
modelNames,
modelAvailability,
loginProviders,
pendingProviders
}),
refreshWarning: snapshot.refreshFailed ? "Some models could not be refreshed. You can still choose from the available models." : void 0,
modelCatalog: dedupeModelCatalogEntries([...visibleCatalog, ...catalog]),
runtimeChoicesByProvider,
runtimeChoicesByModel,
isCurrent: decisions.isCurrent
};
}
//#endregion
//#region src/auto-reply/reply/commands-models.ts
const PAGE_SIZE_DEFAULT = 20;
const PAGE_SIZE_MAX = 100;
const MODELS_ADD_DEPRECATED_TEXT = "⚠️ /models add is deprecated. Use /models to browse providers and /model to switch models.";
const MODEL_PICKER_CHANGED_MESSAGE = "Available models changed. Open /models and choose again.";
function parseListArgs(tokens) {
const provider = normalizeOptionalString(tokens[0]);
let page = 1;
let all = false;
for (const token of tokens.slice(1)) {
const lower = normalizeLowercaseStringOrEmpty(token);
if (lower === "all" || lower === "--all") {
all = true;
continue;
}
if (lower.startsWith("page=")) {
const value = parseStrictPositiveInteger(lower.slice(5));
if (value !== void 0) page = value;
continue;
}
const pageToken = parseStrictPositiveInteger(lower);
if (pageToken !== void 0) page = pageToken;
}
let pageSize = PAGE_SIZE_DEFAULT;
for (const token of tokens) {
const lower = normalizeLowercaseStringOrEmpty(token);
if (lower.startsWith("limit=") || lower.startsWith("size=")) {
const rawValue = lower.slice(lower.indexOf("=") + 1);
const value = parseStrictPositiveInteger(rawValue);
if (value !== void 0) pageSize = Math.min(PAGE_SIZE_MAX, value);
}
}
return {
action: "list",
provider: provider ? normalizeProviderId(provider) : void 0,
page,
pageSize,
all
};
}
function parseModelsArgs(raw) {
const trimmed = raw.trim();
if (!trimmed) return { action: "providers" };
const tokens = trimmed.split(/\s+/g).filter(Boolean);
switch (normalizeLowercaseStringOrEmpty(tokens[0])) {
case "providers": return { action: "providers" };
case "list": return parseListArgs(tokens.slice(1));
case "add": return { action: "add" };
default: return parseListArgs(tokens);
}
}
function resolveProviderLabel(params) {
const harnessPolicy = resolveAgentHarnessPolicy({
config: params.cfg,
provider: params.provider,
agentId: params.agentId
});
const acceptedProviderIds = listOpenAIAuthProfileProvidersForAgentRuntime({
provider: params.provider,
harnessRuntime: harnessPolicy.runtime,
config: params.cfg
});
const authLabel = resolveModelAuthLabel({
provider: params.provider,
acceptedProviderIds,
cfg: params.cfg,
sessionEntry: params.sessionEntry,
agentDir: params.agentDir,
workspaceDir: params.workspaceDir
});
if (!authLabel || authLabel === "unknown") return params.provider;
return `${params.provider} · 🔑 ${authLabel}`;
}
function formatModelsAvailableHeader(params) {
return [`Models (${resolveProviderLabel(params)}) — ${params.availability && params.availability.available !== params.total ? `${params.availability.available} of ${params.total}` : String(params.total)} available`, params.availability?.notice].filter(Boolean).join("\n\n");
}
function buildModelsMenuText(params) {
return [
"Providers:",
...params.providers.map((provider) => `- ${provider} (${params.byProvider.get(provider)?.size ?? 0})`),
"",
"Use: /models <provider>",
"Switch: /model <provider/model>"
].join("\n");
}
async function resolveModelsCommandReply(params) {
const body = params.commandBodyNormalized.trim();
if (!body.startsWith("/models")) return null;
const parsed = parseModelsArgs(body.replace(/^\/models\b/i, "").trim());
let data;
try {
data = await loadModelsProviderData(params.cfg, params.agentId, {
...parsed.action === "list" && parsed.all ? { view: "all" } : {},
workspaceDir: params.workspaceDir,
sessionEntry: params.sessionEntry
}, params.agentDir);
} catch (error) {
if (error instanceof PreparedModelRuntimeOwnerNotPublishedError) return { text: "Model catalog is not ready. Retry after Gateway startup or refresh finishes." };
if (error instanceof PreparedModelRuntimePublicationSupersededError) return { text: MODEL_PICKER_CHANGED_MESSAGE };
throw error;
}
const reply = buildModelsCommandReply(params, parsed, data);
return {
...reply,
text: [data.refreshWarning, reply.text].filter(Boolean).join("\n\n")
};
}
function buildModelsCommandReply(params, parsed, data) {
const { byProvider, providers } = data;
const availability = parsed.action === "list" && parsed.provider ? data.modelMenu?.byProvider.get(parsed.provider) : void 0;
const modelNames = data.modelMenu?.modelNames ?? data.modelNames;
const notice = parsed.action === "list" && parsed.provider ? availability?.notice : [...data.modelMenu?.byProvider.values() ?? []].map((provider) => provider.notice).filter(Boolean).join("\n");
const checking = data.pendingProviders?.filter((provider) => parsed.action !== "list" || !parsed.provider || parsed.provider === provider).map((provider) => `${provider}: checking models…`).join("\n");
const withAvailability = (text) => [
text,
notice,
checking
].filter(Boolean).join("\n\n");
const commandPlugin = params.surface ? getChannelPlugin(params.surface) : null;
const providerInfos = providers.map((provider) => ({
id: provider,
count: byProvider.get(provider)?.size ?? 0
}));
const providerMenuReply = (preferMenu) => {
const channelData = (preferMenu ? commandPlugin?.commands?.buildModelsMenuChannelData?.({ providers: providerInfos }) : void 0) ?? commandPlugin?.commands?.buildModelsProviderChannelData?.({ providers: providerInfos });
if (channelData) return {
text: withAvailability("Select a provider:"),
channelData
};
return { text: withAvailability(buildModelsMenuText({
providers,
byProvider
})) };
};
if (parsed.action === "providers") return providerMenuReply(true);
if (parsed.action === "add") return { text: MODELS_ADD_DEPRECATED_TEXT };
const { provider, page, pageSize, all } = parsed;
if (!provider) return providerMenuReply(false);
if (!byProvider.has(provider)) return { text: [
`Unknown provider: ${provider}`,
"",
"Available providers:",
...providers.map((entry) => `- ${entry}`),
"",
"Use: /models <provider>"
].join("\n") };
const models = [...byProvider.get(provider) ?? /* @__PURE__ */ new Set()];
const total = models.length;
if (total === 0) {
if (checking) return { text: checking };
return { text: [
`Models (${resolveProviderLabel({
...params,
provider
})}) — none`,
"",
"Browse: /models",
"Switch: /model <provider/model>"
].join("\n") };
}
const interactivePageSize = 8;
const interactiveTotalPages = Math.max(1, Math.ceil(total / interactivePageSize));
const interactivePage = Math.max(1, Math.min(page, interactiveTotalPages));
const interactiveChannelData = commandPlugin?.commands?.buildModelsListChannelData?.({
provider,
models: models.toSorted(),
currentModel: params.currentModel,
currentPage: interactivePage,
totalPages: interactiveTotalPages,
pageSize: interactivePageSize,
modelNames
});
if (interactiveChannelData) return {
text: formatModelsAvailableHeader({
...params,
provider,
total,
availability
}),
channelData: interactiveChannelData
};
const currentIndex = models.findIndex((model) => params.currentModel === `${provider}/${model}`);
if (currentIndex > 0) models.unshift(...models.splice(currentIndex, 1));
const effectivePageSize = all ? total : pageSize;
const pageCount = effectivePageSize > 0 ? Math.ceil(total / effectivePageSize) : 1;
const safePage = all ? 1 : Math.max(1, Math.min(page, pageCount));
if (!all && page !== safePage) return { text: [
`Page out of range: ${page} (valid: 1-${pageCount})`,
"",
`Try: /models list ${provider} ${safePage}`,
`All: /models list ${provider} all`
].join("\n") };
const startIndex = (safePage - 1) * effectivePageSize;
const endIndexExclusive = Math.min(total, startIndex + effectivePageSize);
const pageModels = models.slice(startIndex, endIndexExclusive);
const lines = [`Models (${resolveProviderLabel({
...params,
provider
})}) — showing ${startIndex + 1}-${endIndexExclusive} of ${total} (page ${safePage}/${pageCount})`];
for (const id of pageModels) {
const key = `${provider}/${id}`;
const label = modelNames.get(key);
lines.push(`- ${key}${label && label !== data.modelNames.get(key) ? ` (${label})` : ""}`);
}
lines.push("", "Switch: /model <provider/model>");
if (!all && safePage < pageCount) lines.push(`More: /models list ${provider} ${safePage + 1}`);
if (!all) lines.push(`All: /models list ${provider} all`);
return { text: withAvailability(lines.join("\n")) };
}
const handleModelsCommand = defineAuthorizedTextCommand({
label: "/models",
match: (body) => body.trim().startsWith("/models") ? body.trim() : null
}, async (params, commandBodyNormalized) => {
if (parseModelsArgs(commandBodyNormalized.replace(/^\/models\b/i, "").trim()).action === "add") return {
shouldContinue: false,
reply: { text: MODELS_ADD_DEPRECATED_TEXT }
};
const modelsAgentId = params.sessionKey ? resolveSessionAgentId({
sessionKey: params.sessionKey,
config: params.cfg
}) : params.agentId ?? "main";
const currentAgentId = params.agentId ?? "main";
const modelsAgentDir = modelsAgentId === currentAgentId && params.agentDir ? params.agentDir : resolveAgentDir(params.cfg, modelsAgentId);
const targetSessionEntry = params.sessionStore?.[params.sessionKey] ?? params.sessionEntry;
const reply = await resolveModelsCommandReply({
cfg: params.cfg,
commandBodyNormalized,
surface: params.ctx.Surface,
currentModel: params.model ? `${params.provider}/${params.model}` : void 0,
agentId: modelsAgentId,
agentDir: modelsAgentDir,
workspaceDir: targetSessionEntry?.spawnedWorkspaceDir ?? (modelsAgentId === currentAgentId ? params.workspaceDir : void 0),
sessionEntry: targetSessionEntry
});
if (!reply) return null;
return {
reply,
shouldContinue: false
};
});
//#endregion
export { buildPreparedModelsProviderData as a, resolveModelsCommandReply as i, formatModelsAvailableHeader as n, getModelsRuntimeChoices as o, handleModelsCommand as r, MODEL_PICKER_CHANGED_MESSAGE as t };