openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
350 lines (349 loc) • 16.8 kB
JavaScript
import { c as isRecord } from "./record-coerce-DItp3I4t.js";
import { E as tryResolveDefaultAgentId, i as listAgentEntries, m as resolveAgentWorkspaceDir, u as resolveAgentDir } from "./agent-scope-config-DcbEhP0R.js";
import { c as resolvePluginMetadataSnapshot } from "./plugin-metadata-snapshot-w-4EjxI4.js";
import { c as normalizeProviderId } from "./model-ref-shared-Dz7QU0Lx.js";
import { n as DEFAULT_MODEL, r as DEFAULT_PROVIDER } from "./defaults-CdX9UGcX.js";
import "./agent-scope-DbtJyKUL.js";
import { t as listMutableCodexRouteAgentEntries } from "./codex-route-agent-entries-BI7NtrYf.js";
import "./model-selection-di2kjKCB.js";
import { n as resolveProviderInstallCatalogEntries } from "./provider-install-catalog-BzkhdO6-.js";
import { r as collectConfiguredProviderSelectionIds } from "./configured-provider-selection-ids-ClDBppbl.js";
import fs from "node:fs";
import path from "node:path";
//#region src/commands/doctor/shared/stale-agent-model-ref-repair.ts
const DEFAULT_MODEL_REF = `${DEFAULT_PROVIDER}/${DEFAULT_MODEL}`;
function providerFromModelRef(ref) {
const trimmed = ref.trim();
const slash = trimmed.indexOf("/");
if (slash <= 0 || slash === trimmed.length - 1) return;
return normalizeProviderId(trimmed.slice(0, slash)) || void 0;
}
function collectPluginProviderIds(cfg, options) {
let providerIds;
if (options.pluginProviderIds) providerIds = new Set([...options.pluginProviderIds].map(normalizeProviderId).filter(Boolean));
else {
const defaultAgentId = tryResolveDefaultAgentId(cfg);
const workspaceDir = defaultAgentId ? resolveAgentWorkspaceDir(cfg, defaultAgentId) : void 0;
const snapshot = options.pluginMetadataSnapshot ?? resolvePluginMetadataSnapshot({
config: cfg,
workspaceDir: workspaceDir ?? void 0,
env: options.env ?? process.env,
allowWorkspaceScopedCurrent: true
});
if (snapshot.diagnostics.some((diagnostic) => diagnostic.level === "error")) return { warnings: ["Skipped stale agent model reference repair because plugin discovery reported errors."] };
providerIds = /* @__PURE__ */ new Set();
for (const owners of [
snapshot.owners.providers,
snapshot.owners.modelCatalogProviders,
snapshot.owners.setupProviders,
snapshot.owners.cliBackends
]) for (const providerId of owners.keys()) {
const normalized = normalizeProviderId(providerId);
if (normalized) providerIds.add(normalized);
}
}
const selectedProviderIds = collectConfiguredProviderSelectionIds(cfg);
for (const entry of resolveProviderInstallCatalogEntries({
config: cfg,
env: options.env ?? process.env,
includeUntrustedWorkspacePlugins: false
})) {
const entryProviderIds = [entry.providerId, ...entry.providerAliases ?? []];
if (!entryProviderIds.some((providerId) => selectedProviderIds.has(providerId.toLowerCase()))) continue;
for (const providerId of entryProviderIds) {
const normalized = normalizeProviderId(providerId);
if (normalized) providerIds.add(normalized);
}
}
return {
providerIds,
warnings: []
};
}
function collectPersistedProviderIds(params) {
const injected = params.injected?.get(params.agentId);
if (injected) return { providerIds: new Set([...injected].map(normalizeProviderId).filter(Boolean)) };
if (params.injected) return { providerIds: /* @__PURE__ */ new Set() };
const modelsPath = path.join(resolveAgentDir(params.cfg, params.agentId, params.env), "models.json");
let raw;
try {
raw = fs.readFileSync(modelsPath, "utf8");
} catch (error) {
if (error.code === "ENOENT") return { providerIds: /* @__PURE__ */ new Set() };
return { warning: `Skipped stale model reference repair for agent "${params.agentId}" because ${modelsPath} could not be read.` };
}
try {
const parsed = JSON.parse(raw);
if (!parsed.providers || typeof parsed.providers !== "object" || Array.isArray(parsed.providers)) return { providerIds: /* @__PURE__ */ new Set() };
return { providerIds: new Set(Object.keys(parsed.providers).map(normalizeProviderId).filter(Boolean)) };
} catch {
return { warning: `Skipped stale model reference repair for agent "${params.agentId}" because ${modelsPath} is invalid JSON.` };
}
}
function repairModelMap(params) {
if (!isRecord(params.models)) return;
const refs = Object.keys(params.models);
const staleRefs = refs.filter((ref) => params.isStale(ref));
if (staleRefs.length === refs.length && staleRefs.length > 0 && !params.replacementRef) {
params.warnings.push(`Skipped clearing ${params.path} because no available replacement model could keep the allowlist restrictive.`);
return;
}
for (const ref of staleRefs) {
const provider = params.isStale(ref);
delete params.models[ref];
params.changes.push(`Removed stale ${params.path} entry "${ref}" (provider "${provider}" is unavailable).`);
}
if (refs.length > 0 && (staleRefs.length > 0 || params.ensureReplacement === true) && params.replacementRef && !Object.hasOwn(params.models, params.replacementRef)) {
params.models[params.replacementRef] = {};
params.changes.push(`Added ${params.path} entry "${params.replacementRef}" to keep the repaired allowlist restrictive.`);
}
}
function filterFallbacks(params) {
if (!Array.isArray(params.model.fallbacks)) return;
params.model.fallbacks = params.model.fallbacks.filter((ref) => {
if (typeof ref !== "string") return true;
const provider = params.isStale(ref);
if (!provider) return true;
params.changes.push(`Removed stale ${params.path} fallback "${ref}" (provider "${provider}" is unavailable).`);
return false;
});
if (params.model.fallbacks.length === 0) delete params.model.fallbacks;
}
function firstExplicitModelRef(cfg) {
if (!isRecord(cfg.models?.providers)) return;
for (const [providerId, provider] of Object.entries(cfg.models.providers)) {
if (!isRecord(provider) || !Array.isArray(provider.models)) continue;
const normalizedProvider = normalizeProviderId(providerId);
const modelId = provider.models.map((model) => isRecord(model) && typeof model.id === "string" ? model.id.trim() : "").find(Boolean);
if (normalizedProvider && modelId) return `${normalizedProvider}/${modelId}`;
}
}
function modelPrimaryRef(model) {
if (typeof model === "string") return model;
return isRecord(model) && typeof model.primary === "string" ? model.primary : void 0;
}
function repairStaleAgentModelRefs(cfg, options = {}) {
const replaceMode = cfg.models?.mode === "replace";
const pluginProviders = replaceMode ? {
providerIds: /* @__PURE__ */ new Set(),
warnings: []
} : collectPluginProviderIds(cfg, options);
if (!pluginProviders.providerIds) return {
config: cfg,
changes: [],
warnings: pluginProviders.warnings
};
const baseAvailableProviders = pluginProviders.providerIds;
if (!replaceMode) baseAvailableProviders.add(normalizeProviderId(DEFAULT_PROVIDER));
for (const providerId of Object.keys(cfg.models?.providers ?? {})) {
const normalized = normalizeProviderId(providerId);
if (normalized) baseAvailableProviders.add(normalized);
}
const config = structuredClone(cfg);
const changes = [];
const warnings = [...pluginProviders.warnings];
const env = options.env ?? process.env;
const persistedForAgent = (agentId) => {
const persisted = collectPersistedProviderIds({
cfg,
agentId,
env,
injected: options.persistedProviderIdsByAgentId
});
if (!persisted.providerIds) {
if (persisted.warning) warnings.push(persisted.warning);
return;
}
return persisted.providerIds;
};
const availabilityForAgent = (agentId) => {
const available = new Set(baseAvailableProviders);
if (replaceMode) return available;
const persisted = persistedForAgent(agentId);
if (!persisted) return;
for (const providerId of persisted) available.add(providerId);
return available;
};
const availabilityForDefaults = () => {
const available = new Set(baseAvailableProviders);
if (replaceMode) return available;
const inheritingAgentIds = [];
for (const agent of listAgentEntries(cfg)) {
if (typeof agent.id !== "string") continue;
const explicitPrimary = modelPrimaryRef(agent.model);
if (!explicitPrimary) {
inheritingAgentIds.push(agent.id);
continue;
}
const agentAvailability = availabilityForAgent(agent.id);
const provider = providerFromModelRef(explicitPrimary);
if (agentAvailability && provider && !agentAvailability.has(provider)) inheritingAgentIds.push(agent.id);
}
if (inheritingAgentIds.length === 0) {
const defaultAgentId = tryResolveDefaultAgentId(cfg);
if (defaultAgentId) inheritingAgentIds.push(defaultAgentId);
}
let commonPersisted;
for (const agentId of inheritingAgentIds) {
const persisted = persistedForAgent(agentId);
if (!persisted) return;
commonPersisted = commonPersisted ? new Set([...commonPersisted].filter((providerId) => persisted.has(providerId))) : new Set(persisted);
}
for (const providerId of commonPersisted ?? []) available.add(providerId);
return available;
};
const availabilityForDefaultModelMap = () => {
const available = new Set(baseAvailableProviders);
if (replaceMode) return available;
const inheritingAgentIds = listAgentEntries(cfg).filter((agent) => isRecord(agent) && typeof agent.id === "string" && !isRecord(agent.models)).map((agent) => agent.id);
if (inheritingAgentIds.length === 0) {
const defaultAgentId = tryResolveDefaultAgentId(cfg);
if (defaultAgentId) inheritingAgentIds.push(defaultAgentId);
}
for (const agentId of inheritingAgentIds) {
const persisted = persistedForAgent(agentId);
if (!persisted) return;
for (const providerId of persisted) available.add(providerId);
}
return available;
};
const makeStaleChecker = (available) => (ref) => {
const provider = providerFromModelRef(ref);
return provider && !available.has(provider) ? provider : void 0;
};
const defaults = config.agents?.defaults;
const defaultAvailability = availabilityForDefaults();
let repairedDefaultPrimary = modelPrimaryRef(defaults?.model) ?? (replaceMode ? firstExplicitModelRef(cfg) : DEFAULT_MODEL_REF);
let defaultPrimaryChanged = false;
if (defaults && defaultAvailability) {
const isStale = makeStaleChecker(defaultAvailability);
const configuredReplacement = replaceMode ? firstExplicitModelRef(cfg) : DEFAULT_MODEL_REF;
if (defaults.model) {
if (typeof defaults.model === "string") {
const provider = isStale(defaults.model);
if (provider) {
const staleRef = defaults.model;
if (configuredReplacement) {
defaults.model = configuredReplacement;
defaultPrimaryChanged = true;
changes.push(`Replaced stale agents.defaults.model "${staleRef}" with default "${configuredReplacement}" (provider "${provider}" is unavailable).`);
} else {
delete defaults.model;
defaultPrimaryChanged = true;
changes.push(`Removed stale agents.defaults.model "${staleRef}" because provider "${provider}" is unavailable and no replacement model is configured.`);
}
}
} else if (isRecord(defaults.model)) {
const provider = typeof defaults.model.primary === "string" ? isStale(defaults.model.primary) : void 0;
let replacement;
if (provider && typeof defaults.model.primary === "string") {
const staleRef = defaults.model.primary;
replacement = replaceMode ? (Array.isArray(defaults.model.fallbacks) ? defaults.model.fallbacks.find((fallback) => typeof fallback === "string" && !isStale(fallback)) : void 0) ?? configuredReplacement : configuredReplacement;
if (replacement) {
defaults.model.primary = replacement;
defaultPrimaryChanged = true;
changes.push(`Replaced stale agents.defaults.model primary "${staleRef}" with default "${replacement}" (provider "${provider}" is unavailable).`);
} else {
delete defaults.model.primary;
defaultPrimaryChanged = true;
changes.push(`Removed stale agents.defaults.model primary "${staleRef}" because provider "${provider}" is unavailable and no replacement model is configured.`);
}
}
filterFallbacks({
model: defaults.model,
path: "agents.defaults.model",
isStale,
changes
});
if (replacement && Array.isArray(defaults.model.fallbacks) && defaults.model.fallbacks.includes(replacement)) {
defaults.model.fallbacks = defaults.model.fallbacks.filter((fallback) => fallback !== replacement);
changes.push(`Removed duplicate agents.defaults.model fallback "${replacement}" after selecting it as the default primary.`);
if (defaults.model.fallbacks.length === 0) delete defaults.model.fallbacks;
}
if (!defaults.model.primary && !defaults.model.fallbacks) delete defaults.model;
}
}
repairedDefaultPrimary = modelPrimaryRef(defaults.model) ?? (replaceMode ? firstExplicitModelRef(cfg) : DEFAULT_MODEL_REF);
const modelMapAvailability = availabilityForDefaultModelMap();
if (modelMapAvailability) repairModelMap({
models: defaults.models,
path: "agents.defaults.models",
isStale: makeStaleChecker(modelMapAvailability),
replacementRef: repairedDefaultPrimary,
ensureReplacement: defaultPrimaryChanged,
changes,
warnings
});
}
for (const entry of listMutableCodexRouteAgentEntries(config)) {
const agent = entry.agent;
const available = availabilityForAgent(entry.agentId);
if (!available) continue;
const isStale = makeStaleChecker(available);
const modelPath = `${entry.path}.model`;
const canInheritDefault = Boolean(defaultAvailability && repairedDefaultPrimary && (!replaceMode || modelPrimaryRef(defaults?.model)) && !isStale(repairedDefaultPrimary));
let agentPrimaryChanged = false;
if (typeof agent.model === "string") {
const provider = isStale(agent.model);
if (provider) {
const staleRef = agent.model;
if (canInheritDefault) {
delete agent.model;
agentPrimaryChanged = true;
changes.push(`Removed stale ${modelPath} "${staleRef}" so agent "${entry.agentId}" inherits the default model (provider "${provider}" is unavailable).`);
} else if (repairedDefaultPrimary && !isStale(repairedDefaultPrimary)) {
agent.model = repairedDefaultPrimary;
agentPrimaryChanged = true;
changes.push(`Replaced stale ${modelPath} "${staleRef}" with "${repairedDefaultPrimary}" (provider "${provider}" is unavailable).`);
} else warnings.push(`Skipped stale ${modelPath} repair because no available inherited or replacement model is configured.`);
}
} else if (isRecord(agent.model)) {
const model = agent.model;
const provider = typeof model.primary === "string" ? isStale(model.primary) : void 0;
let agentReplacement;
if (provider && typeof model.primary === "string") {
const staleRef = model.primary;
if (canInheritDefault) {
delete model.primary;
agentPrimaryChanged = true;
agentReplacement = repairedDefaultPrimary;
changes.push(`Removed stale ${modelPath} primary "${staleRef}" so agent "${entry.agentId}" inherits the default model (provider "${provider}" is unavailable).`);
} else if (agentReplacement = (Array.isArray(model.fallbacks) ? model.fallbacks.find((fallback) => typeof fallback === "string" && !isStale(fallback)) : void 0) ?? (repairedDefaultPrimary && !isStale(repairedDefaultPrimary) ? repairedDefaultPrimary : void 0)) {
model.primary = agentReplacement;
agentPrimaryChanged = true;
changes.push(`Replaced stale ${modelPath} primary "${staleRef}" with "${agentReplacement}" (provider "${provider}" is unavailable).`);
} else warnings.push(`Skipped stale ${modelPath} primary repair because no available inherited or replacement model is configured.`);
}
filterFallbacks({
model,
path: modelPath,
isStale,
changes
});
if (agentReplacement && Array.isArray(model.fallbacks) && model.fallbacks.includes(agentReplacement)) {
const filteredFallbacks = model.fallbacks.filter((fallback) => fallback !== agentReplacement);
model.fallbacks = filteredFallbacks;
changes.push(`Removed duplicate ${modelPath} fallback "${agentReplacement}" after selecting it as the primary.`);
if (filteredFallbacks.length === 0) delete model.fallbacks;
}
if (!model.primary && !model.fallbacks) delete agent.model;
}
const effectiveAgentPrimary = modelPrimaryRef(agent.model) ?? repairedDefaultPrimary;
repairModelMap({
models: isRecord(agent.models) ? agent.models : void 0,
path: `${entry.path}.models`,
isStale,
replacementRef: effectiveAgentPrimary && !isStale(effectiveAgentPrimary) ? effectiveAgentPrimary : void 0,
ensureReplacement: agentPrimaryChanged || !modelPrimaryRef(agent.model) && defaultPrimaryChanged,
changes,
warnings
});
}
return {
config: changes.length > 0 ? config : cfg,
changes,
warnings
};
}
//#endregion
export { repairStaleAgentModelRefs as t };