feat!: redesign model config + auth profiles

This commit is contained in:
Peter Steinberger
2026-01-06 00:56:29 +00:00
parent bd2e003171
commit b04c838c15
60 changed files with 2037 additions and 790 deletions

View File

@@ -13,7 +13,15 @@ export async function modelsAliasesListCommand(
) {
ensureFlagCompatibility(opts);
const cfg = loadConfig();
const aliases = cfg.agent?.modelAliases ?? {};
const models = cfg.agent?.models ?? {};
const aliases = Object.entries(models).reduce<Record<string, string>>(
(acc, [modelKey, entry]) => {
const alias = entry?.alias?.trim();
if (alias) acc[alias] = modelKey;
return acc;
},
{},
);
if (opts.json) {
runtime.log(JSON.stringify({ aliases }, null, 2));
@@ -42,21 +50,29 @@ export async function modelsAliasesAddCommand(
runtime: RuntimeEnv,
) {
const alias = normalizeAlias(aliasRaw);
const updated = await updateConfig((cfg) => {
const resolved = resolveModelTarget({ raw: modelRaw, cfg });
const nextAliases = { ...cfg.agent?.modelAliases };
nextAliases[alias] = `${resolved.provider}/${resolved.model}`;
const resolved = resolveModelTarget({ raw: modelRaw, cfg: loadConfig() });
const _updated = await updateConfig((cfg) => {
const modelKey = `${resolved.provider}/${resolved.model}`;
const nextModels = { ...cfg.agent?.models };
for (const [key, entry] of Object.entries(nextModels)) {
const existing = entry?.alias?.trim();
if (existing && existing === alias && key !== modelKey) {
throw new Error(`Alias ${alias} already points to ${key}.`);
}
}
const existing = nextModels[modelKey] ?? {};
nextModels[modelKey] = { ...existing, alias };
return {
...cfg,
agent: {
...cfg.agent,
modelAliases: nextAliases,
models: nextModels,
},
};
});
runtime.log(`Updated ${CONFIG_PATH_CLAWDBOT}`);
runtime.log(`Alias ${alias} -> ${updated.agent?.modelAliases?.[alias]}`);
runtime.log(`Alias ${alias} -> ${resolved.provider}/${resolved.model}`);
}
export async function modelsAliasesRemoveCommand(
@@ -65,24 +81,31 @@ export async function modelsAliasesRemoveCommand(
) {
const alias = normalizeAlias(aliasRaw);
const updated = await updateConfig((cfg) => {
const nextAliases = { ...cfg.agent?.modelAliases };
if (!nextAliases[alias]) {
const nextModels = { ...cfg.agent?.models };
let found = false;
for (const [key, entry] of Object.entries(nextModels)) {
if (entry?.alias?.trim() === alias) {
nextModels[key] = { ...entry, alias: undefined };
found = true;
break;
}
}
if (!found) {
throw new Error(`Alias not found: ${alias}`);
}
delete nextAliases[alias];
return {
...cfg,
agent: {
...cfg.agent,
modelAliases: nextAliases,
models: nextModels,
},
};
});
runtime.log(`Updated ${CONFIG_PATH_CLAWDBOT}`);
if (
!updated.agent?.modelAliases ||
Object.keys(updated.agent.modelAliases).length === 0
!updated.agent?.models ||
Object.values(updated.agent.models).every((entry) => !entry?.alias?.trim())
) {
runtime.log("No aliases configured.");
}

View File

@@ -18,7 +18,7 @@ export async function modelsFallbacksListCommand(
) {
ensureFlagCompatibility(opts);
const cfg = loadConfig();
const fallbacks = cfg.agent?.modelFallbacks ?? [];
const fallbacks = cfg.agent?.model?.fallbacks ?? [];
if (opts.json) {
runtime.log(JSON.stringify({ fallbacks }, null, 2));
@@ -44,11 +44,13 @@ export async function modelsFallbacksAddCommand(
const updated = await updateConfig((cfg) => {
const resolved = resolveModelTarget({ raw: modelRaw, cfg });
const targetKey = modelKey(resolved.provider, resolved.model);
const nextModels = { ...cfg.agent?.models };
if (!nextModels[targetKey]) nextModels[targetKey] = {};
const aliasIndex = buildModelAliasIndex({
cfg,
defaultProvider: DEFAULT_PROVIDER,
});
const existing = cfg.agent?.modelFallbacks ?? [];
const existing = cfg.agent?.model?.fallbacks ?? [];
const existingKeys = existing
.map((entry) =>
resolveModelRefFromString({
@@ -66,13 +68,22 @@ export async function modelsFallbacksAddCommand(
...cfg,
agent: {
...cfg.agent,
modelFallbacks: [...existing, targetKey],
model: {
...((cfg.agent?.model as {
primary?: string;
fallbacks?: string[];
}) ?? {}),
fallbacks: [...existing, targetKey],
},
models: nextModels,
},
};
});
runtime.log(`Updated ${CONFIG_PATH_CLAWDBOT}`);
runtime.log(`Fallbacks: ${(updated.agent?.modelFallbacks ?? []).join(", ")}`);
runtime.log(
`Fallbacks: ${(updated.agent?.model?.fallbacks ?? []).join(", ")}`,
);
}
export async function modelsFallbacksRemoveCommand(
@@ -86,7 +97,7 @@ export async function modelsFallbacksRemoveCommand(
cfg,
defaultProvider: DEFAULT_PROVIDER,
});
const existing = cfg.agent?.modelFallbacks ?? [];
const existing = cfg.agent?.model?.fallbacks ?? [];
const filtered = existing.filter((entry) => {
const resolvedEntry = resolveModelRefFromString({
raw: String(entry ?? ""),
@@ -108,13 +119,21 @@ export async function modelsFallbacksRemoveCommand(
...cfg,
agent: {
...cfg.agent,
modelFallbacks: filtered,
model: {
...((cfg.agent?.model as {
primary?: string;
fallbacks?: string[];
}) ?? {}),
fallbacks: filtered,
},
},
};
});
runtime.log(`Updated ${CONFIG_PATH_CLAWDBOT}`);
runtime.log(`Fallbacks: ${(updated.agent?.modelFallbacks ?? []).join(", ")}`);
runtime.log(
`Fallbacks: ${(updated.agent?.model?.fallbacks ?? []).join(", ")}`,
);
}
export async function modelsFallbacksClearCommand(runtime: RuntimeEnv) {
@@ -122,7 +141,11 @@ export async function modelsFallbacksClearCommand(runtime: RuntimeEnv) {
...cfg,
agent: {
...cfg.agent,
modelFallbacks: [],
model: {
...((cfg.agent?.model as { primary?: string; fallbacks?: string[] }) ??
{}),
fallbacks: [],
},
},
}));

View File

@@ -18,7 +18,7 @@ export async function modelsImageFallbacksListCommand(
) {
ensureFlagCompatibility(opts);
const cfg = loadConfig();
const fallbacks = cfg.agent?.imageModelFallbacks ?? [];
const fallbacks = cfg.agent?.imageModel?.fallbacks ?? [];
if (opts.json) {
runtime.log(JSON.stringify({ fallbacks }, null, 2));
@@ -44,11 +44,13 @@ export async function modelsImageFallbacksAddCommand(
const updated = await updateConfig((cfg) => {
const resolved = resolveModelTarget({ raw: modelRaw, cfg });
const targetKey = modelKey(resolved.provider, resolved.model);
const nextModels = { ...cfg.agent?.models };
if (!nextModels[targetKey]) nextModels[targetKey] = {};
const aliasIndex = buildModelAliasIndex({
cfg,
defaultProvider: DEFAULT_PROVIDER,
});
const existing = cfg.agent?.imageModelFallbacks ?? [];
const existing = cfg.agent?.imageModel?.fallbacks ?? [];
const existingKeys = existing
.map((entry) =>
resolveModelRefFromString({
@@ -66,14 +68,21 @@ export async function modelsImageFallbacksAddCommand(
...cfg,
agent: {
...cfg.agent,
imageModelFallbacks: [...existing, targetKey],
imageModel: {
...((cfg.agent?.imageModel as {
primary?: string;
fallbacks?: string[];
}) ?? {}),
fallbacks: [...existing, targetKey],
},
models: nextModels,
},
};
});
runtime.log(`Updated ${CONFIG_PATH_CLAWDBOT}`);
runtime.log(
`Image fallbacks: ${(updated.agent?.imageModelFallbacks ?? []).join(", ")}`,
`Image fallbacks: ${(updated.agent?.imageModel?.fallbacks ?? []).join(", ")}`,
);
}
@@ -88,7 +97,7 @@ export async function modelsImageFallbacksRemoveCommand(
cfg,
defaultProvider: DEFAULT_PROVIDER,
});
const existing = cfg.agent?.imageModelFallbacks ?? [];
const existing = cfg.agent?.imageModel?.fallbacks ?? [];
const filtered = existing.filter((entry) => {
const resolvedEntry = resolveModelRefFromString({
raw: String(entry ?? ""),
@@ -110,14 +119,20 @@ export async function modelsImageFallbacksRemoveCommand(
...cfg,
agent: {
...cfg.agent,
imageModelFallbacks: filtered,
imageModel: {
...((cfg.agent?.imageModel as {
primary?: string;
fallbacks?: string[];
}) ?? {}),
fallbacks: filtered,
},
},
};
});
runtime.log(`Updated ${CONFIG_PATH_CLAWDBOT}`);
runtime.log(
`Image fallbacks: ${(updated.agent?.imageModelFallbacks ?? []).join(", ")}`,
`Image fallbacks: ${(updated.agent?.imageModel?.fallbacks ?? []).join(", ")}`,
);
}
@@ -126,7 +141,13 @@ export async function modelsImageFallbacksClearCommand(runtime: RuntimeEnv) {
...cfg,
agent: {
...cfg.agent,
imageModelFallbacks: [],
imageModel: {
...((cfg.agent?.imageModel as {
primary?: string;
fallbacks?: string[];
}) ?? {}),
fallbacks: [],
},
},
}));

View File

@@ -1,4 +1,4 @@
import { type Api, getEnvApiKey, type Model } from "@mariozechner/pi-ai";
import type { Api, Model } from "@mariozechner/pi-ai";
import {
discoverAuthStorage,
discoverModels,
@@ -6,6 +6,15 @@ import {
import chalk from "chalk";
import { resolveClawdbotAgentDir } from "../../agents/agent-paths.js";
import {
type AuthProfileStore,
ensureAuthProfileStore,
listProfilesForProvider,
} from "../../agents/auth-profiles.js";
import {
getCustomProviderApiKey,
resolveEnvApiKey,
} from "../../agents/model-auth.js";
import {
buildModelAliasIndex,
parseModelRef,
@@ -81,6 +90,17 @@ const isLocalBaseUrl = (baseUrl: string) => {
}
};
const hasAuthForProvider = (
provider: string,
cfg: ClawdbotConfig,
authStore: AuthProfileStore,
): boolean => {
if (listProfilesForProvider(authStore, provider).length > 0) return true;
if (resolveEnvApiKey(provider)) return true;
if (getCustomProviderApiKey(cfg, provider)) return true;
return false;
};
const resolveConfiguredEntries = (cfg: ClawdbotConfig) => {
const resolvedDefault = resolveConfiguredModelRef({
cfg,
@@ -110,7 +130,21 @@ const resolveConfiguredEntries = (cfg: ClawdbotConfig) => {
addEntry(resolvedDefault, "default");
(cfg.agent?.modelFallbacks ?? []).forEach((raw, idx) => {
const modelConfig = cfg.agent?.model as
| { primary?: string; fallbacks?: string[] }
| undefined;
const imageModelConfig = cfg.agent?.imageModel as
| { primary?: string; fallbacks?: string[] }
| undefined;
const modelFallbacks =
typeof modelConfig === "object" ? (modelConfig?.fallbacks ?? []) : [];
const imageFallbacks =
typeof imageModelConfig === "object"
? (imageModelConfig?.fallbacks ?? [])
: [];
const imagePrimary = imageModelConfig?.primary?.trim() ?? "";
modelFallbacks.forEach((raw, idx) => {
const resolved = resolveModelRefFromString({
raw: String(raw ?? ""),
defaultProvider: DEFAULT_PROVIDER,
@@ -120,17 +154,16 @@ const resolveConfiguredEntries = (cfg: ClawdbotConfig) => {
addEntry(resolved.ref, `fallback#${idx + 1}`);
});
const imageModelRaw = cfg.agent?.imageModel?.trim();
if (imageModelRaw) {
if (imagePrimary) {
const resolved = resolveModelRefFromString({
raw: imageModelRaw,
raw: imagePrimary,
defaultProvider: DEFAULT_PROVIDER,
aliasIndex,
});
if (resolved) addEntry(resolved.ref, "image");
}
(cfg.agent?.imageModelFallbacks ?? []).forEach((raw, idx) => {
imageFallbacks.forEach((raw, idx) => {
const resolved = resolveModelRefFromString({
raw: String(raw ?? ""),
defaultProvider: DEFAULT_PROVIDER,
@@ -140,20 +173,10 @@ const resolveConfiguredEntries = (cfg: ClawdbotConfig) => {
addEntry(resolved.ref, `img-fallback#${idx + 1}`);
});
(cfg.agent?.allowedModels ?? []).forEach((raw) => {
const parsed = parseModelRef(String(raw ?? ""), DEFAULT_PROVIDER);
if (!parsed) return;
addEntry(parsed, "allowed");
});
for (const targetRaw of Object.values(cfg.agent?.modelAliases ?? {})) {
const resolved = resolveModelRefFromString({
raw: String(targetRaw ?? ""),
defaultProvider: DEFAULT_PROVIDER,
aliasIndex,
});
if (!resolved) continue;
addEntry(resolved.ref, "alias");
for (const key of Object.keys(cfg.agent?.models ?? {})) {
const parsed = parseModelRef(String(key ?? ""), DEFAULT_PROVIDER);
if (!parsed) continue;
addEntry(parsed, "configured");
}
const entries: ConfiguredEntry[] = order.map((key) => {
@@ -190,8 +213,18 @@ function toModelRow(params: {
tags: string[];
aliases?: string[];
availableKeys?: Set<string>;
cfg?: ClawdbotConfig;
authStore?: AuthProfileStore;
}): ModelRow {
const { model, key, tags, aliases = [], availableKeys } = params;
const {
model,
key,
tags,
aliases = [],
availableKeys,
cfg,
authStore,
} = params;
if (!model) {
return {
key,
@@ -207,9 +240,11 @@ function toModelRow(params: {
const input = model.input.join("+") || "text";
const local = isLocalBaseUrl(model.baseUrl);
const envKey = getEnvApiKey(model.provider);
const available =
availableKeys?.has(modelKey(model.provider, model.id)) || Boolean(envKey);
availableKeys?.has(modelKey(model.provider, model.id)) ||
(cfg && authStore
? hasAuthForProvider(model.provider, cfg, authStore)
: false);
const aliasTags = aliases.length > 0 ? [`alias:${aliases.join(",")}`] : [];
const mergedTags = new Set(tags);
if (aliasTags.length > 0) {
@@ -304,6 +339,7 @@ export async function modelsListCommand(
) {
ensureFlagCompatibility(opts);
const cfg = loadConfig();
const authStore = ensureAuthProfileStore();
const providerFilter = opts.provider?.trim().toLowerCase();
let models: Model<Api>[] = [];
@@ -346,6 +382,8 @@ export async function modelsListCommand(
tags: configured ? Array.from(configured.tags) : [],
aliases: configured?.aliases ?? [],
availableKeys,
cfg,
authStore,
}),
);
}
@@ -367,6 +405,8 @@ export async function modelsListCommand(
tags: Array.from(entry.tags),
aliases: entry.aliases,
availableKeys,
cfg,
authStore,
}),
);
}
@@ -392,13 +432,35 @@ export async function modelsStatusCommand(
defaultModel: DEFAULT_MODEL,
});
const rawModel = cfg.agent?.model?.trim() ?? "";
const modelConfig = cfg.agent?.model as
| { primary?: string; fallbacks?: string[] }
| string
| undefined;
const imageConfig = cfg.agent?.imageModel as
| { primary?: string; fallbacks?: string[] }
| string
| undefined;
const rawModel =
typeof modelConfig === "string"
? modelConfig.trim()
: (modelConfig?.primary?.trim() ?? "");
const defaultLabel = rawModel || `${resolved.provider}/${resolved.model}`;
const fallbacks = cfg.agent?.modelFallbacks ?? [];
const imageModel = cfg.agent?.imageModel?.trim() ?? "";
const imageFallbacks = cfg.agent?.imageModelFallbacks ?? [];
const aliases = cfg.agent?.modelAliases ?? {};
const allowed = cfg.agent?.allowedModels ?? [];
const fallbacks =
typeof modelConfig === "object" ? (modelConfig?.fallbacks ?? []) : [];
const imageModel =
typeof imageConfig === "string"
? imageConfig.trim()
: (imageConfig?.primary?.trim() ?? "");
const imageFallbacks =
typeof imageConfig === "object" ? (imageConfig?.fallbacks ?? []) : [];
const aliases = Object.entries(cfg.agent?.models ?? {}).reduce<
Record<string, string>
>((acc, [key, entry]) => {
const alias = entry?.alias?.trim();
if (alias) acc[alias] = key;
return acc;
}, {});
const allowed = Object.keys(cfg.agent?.models ?? {});
if (opts.json) {
runtime.log(
@@ -446,6 +508,8 @@ export async function modelsStatusCommand(
}`,
);
runtime.log(
`Allowed (${allowed.length || 0}): ${allowed.length ? allowed.join(", ") : "all"}`,
`Configured models (${allowed.length || 0}): ${
allowed.length ? allowed.join(", ") : "all"
}`,
);
}

View File

@@ -1,20 +1,12 @@
import { cancel, isCancel, multiselect } from "@clack/prompts";
import { discoverAuthStorage } from "@mariozechner/pi-coding-agent";
import { resolveClawdbotAgentDir } from "../../agents/agent-paths.js";
import { resolveApiKeyForProvider } from "../../agents/model-auth.js";
import {
type ModelScanResult,
scanOpenRouterModels,
} from "../../agents/model-scan.js";
import { CONFIG_PATH_CLAWDBOT } from "../../config/config.js";
import { warn } from "../../globals.js";
import { CONFIG_PATH_CLAWDBOT, loadConfig } from "../../config/config.js";
import type { RuntimeEnv } from "../../runtime.js";
import {
buildAllowlistSet,
formatMs,
formatTokenK,
updateConfig,
} from "./shared.js";
import { formatMs, formatTokenK, updateConfig } from "./shared.js";
const MODEL_PAD = 42;
const CTX_PAD = 8;
@@ -181,8 +173,17 @@ export async function modelsScanCommand(
throw new Error("--concurrency must be > 0");
}
const authStorage = discoverAuthStorage(resolveClawdbotAgentDir());
const storedKey = await authStorage.getApiKey("openrouter");
const cfg = loadConfig();
let storedKey: string | undefined;
try {
const resolved = await resolveApiKeyForProvider({
provider: "openrouter",
cfg,
});
storedKey = resolved.apiKey;
} catch {
storedKey = undefined;
}
const results = await scanOpenRouterModels({
apiKey: storedKey ?? undefined,
minParamB: minParams,
@@ -266,32 +267,42 @@ export async function modelsScanCommand(
throw new Error("No image-capable models selected for image model.");
}
const updated = await updateConfig((cfg) => {
const _updated = await updateConfig((cfg) => {
const nextModels = { ...cfg.agent?.models };
for (const entry of selected) {
if (!nextModels[entry]) nextModels[entry] = {};
}
for (const entry of selectedImages) {
if (!nextModels[entry]) nextModels[entry] = {};
}
const nextImageModel =
selectedImages.length > 0
? {
...((cfg.agent?.imageModel as {
primary?: string;
fallbacks?: string[];
}) ?? {}),
fallbacks: selectedImages,
...(opts.setImage ? { primary: selectedImages[0] } : {}),
}
: cfg.agent?.imageModel;
const agent = {
...cfg.agent,
modelFallbacks: selected,
...(opts.setDefault ? { model: selected[0] } : {}),
...(opts.setImage && selectedImages.length > 0
? { imageModel: selectedImages[0] }
: {}),
model: {
...((cfg.agent?.model as { primary?: string; fallbacks?: string[] }) ??
{}),
fallbacks: selected,
...(opts.setDefault ? { primary: selected[0] } : {}),
},
...(nextImageModel ? { imageModel: nextImageModel } : {}),
models: nextModels,
} satisfies NonNullable<typeof cfg.agent>;
if (imageSorted.length > 0) {
agent.imageModelFallbacks = selectedImages;
}
return {
...cfg,
agent,
};
});
const allowlist = buildAllowlistSet(updated);
const allowlistMissing =
allowlist.size > 0 ? selected.filter((entry) => !allowlist.has(entry)) : [];
const allowlistMissingImages =
allowlist.size > 0
? selectedImages.filter((entry) => !allowlist.has(entry))
: [];
if (opts.json) {
runtime.log(
JSON.stringify(
@@ -301,21 +312,7 @@ export async function modelsScanCommand(
setDefault: Boolean(opts.setDefault),
setImage: Boolean(opts.setImage),
results,
warnings:
allowlistMissing.length > 0 || allowlistMissingImages.length > 0
? [
...(allowlistMissing.length > 0
? [
`Selected models not in agent.allowedModels: ${allowlistMissing.join(", ")}`,
]
: []),
...(allowlistMissingImages.length > 0
? [
`Selected image models not in agent.allowedModels: ${allowlistMissingImages.join(", ")}`,
]
: []),
]
: [],
warnings: [],
},
null,
2,
@@ -324,21 +321,6 @@ export async function modelsScanCommand(
return;
}
if (allowlistMissing.length > 0) {
runtime.log(
warn(
`Warning: ${allowlistMissing.length} selected models are not in agent.allowedModels and will be ignored by fallback: ${allowlistMissing.join(", ")}`,
),
);
}
if (allowlistMissingImages.length > 0) {
runtime.log(
warn(
`Warning: ${allowlistMissingImages.length} selected image models are not in agent.allowedModels and will be ignored by fallback: ${allowlistMissingImages.join(", ")}`,
),
);
}
runtime.log(`Updated ${CONFIG_PATH_CLAWDBOT}`);
runtime.log(`Fallbacks: ${selected.join(", ")}`);
if (selectedImages.length > 0) {

View File

@@ -1,11 +1,6 @@
import { CONFIG_PATH_CLAWDBOT } from "../../config/config.js";
import type { RuntimeEnv } from "../../runtime.js";
import {
buildAllowlistSet,
modelKey,
resolveModelTarget,
updateConfig,
} from "./shared.js";
import { resolveModelTarget, updateConfig } from "./shared.js";
export async function modelsSetImageCommand(
modelRaw: string,
@@ -13,22 +8,25 @@ export async function modelsSetImageCommand(
) {
const updated = await updateConfig((cfg) => {
const resolved = resolveModelTarget({ raw: modelRaw, cfg });
const allowlist = buildAllowlistSet(cfg);
if (allowlist.size > 0) {
const key = modelKey(resolved.provider, resolved.model);
if (!allowlist.has(key)) {
throw new Error(`Model ${key} is not in agent.allowedModels.`);
}
}
const key = `${resolved.provider}/${resolved.model}`;
const nextModels = { ...cfg.agent?.models };
if (!nextModels[key]) nextModels[key] = {};
return {
...cfg,
agent: {
...cfg.agent,
imageModel: `${resolved.provider}/${resolved.model}`,
imageModel: {
...((cfg.agent?.imageModel as {
primary?: string;
fallbacks?: string[];
}) ?? {}),
primary: key,
},
models: nextModels,
},
};
});
runtime.log(`Updated ${CONFIG_PATH_CLAWDBOT}`);
runtime.log(`Image model: ${updated.agent?.imageModel ?? modelRaw}`);
runtime.log(`Image model: ${updated.agent?.imageModel?.primary ?? modelRaw}`);
}

View File

@@ -1,31 +1,29 @@
import { CONFIG_PATH_CLAWDBOT } from "../../config/config.js";
import type { RuntimeEnv } from "../../runtime.js";
import {
buildAllowlistSet,
modelKey,
resolveModelTarget,
updateConfig,
} from "./shared.js";
import { resolveModelTarget, updateConfig } from "./shared.js";
export async function modelsSetCommand(modelRaw: string, runtime: RuntimeEnv) {
const updated = await updateConfig((cfg) => {
const resolved = resolveModelTarget({ raw: modelRaw, cfg });
const allowlist = buildAllowlistSet(cfg);
if (allowlist.size > 0) {
const key = modelKey(resolved.provider, resolved.model);
if (!allowlist.has(key)) {
throw new Error(`Model ${key} is not in agent.allowedModels.`);
}
}
const key = `${resolved.provider}/${resolved.model}`;
const nextModels = { ...cfg.agent?.models };
if (!nextModels[key]) nextModels[key] = {};
return {
...cfg,
agent: {
...cfg.agent,
model: `${resolved.provider}/${resolved.model}`,
model: {
...((cfg.agent?.model as {
primary?: string;
fallbacks?: string[];
}) ?? {}),
primary: key,
},
models: nextModels,
},
};
});
runtime.log(`Updated ${CONFIG_PATH_CLAWDBOT}`);
runtime.log(`Default model: ${updated.agent?.model ?? modelRaw}`);
runtime.log(`Default model: ${updated.agent?.model?.primary ?? modelRaw}`);
}

View File

@@ -69,7 +69,8 @@ export function resolveModelTarget(params: {
export function buildAllowlistSet(cfg: ClawdbotConfig): Set<string> {
const allowed = new Set<string>();
for (const raw of cfg.agent?.allowedModels ?? []) {
const models = cfg.agent?.models ?? {};
for (const raw of Object.keys(models)) {
const parsed = parseModelRef(String(raw ?? ""), DEFAULT_PROVIDER);
if (!parsed) continue;
allowed.add(modelKey(parsed.provider, parsed.model));