feat!: redesign model config + auth profiles
This commit is contained in:
@@ -59,7 +59,8 @@ function mockConfig(
|
||||
) {
|
||||
configSpy.mockReturnValue({
|
||||
agent: {
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
model: { primary: "anthropic/claude-opus-4-5" },
|
||||
models: { "anthropic/claude-opus-4-5": {} },
|
||||
workspace: path.join(home, "clawd"),
|
||||
...agentOverrides,
|
||||
},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import crypto from "node:crypto";
|
||||
import { ensureAuthProfileStore } from "../agents/auth-profiles.js";
|
||||
import { lookupContextTokens } from "../agents/context.js";
|
||||
import {
|
||||
DEFAULT_CONTEXT_TOKENS,
|
||||
@@ -289,7 +290,8 @@ export async function agentCommand(
|
||||
});
|
||||
let provider = defaultProvider;
|
||||
let model = defaultModel;
|
||||
const hasAllowlist = (agentCfg?.allowedModels?.length ?? 0) > 0;
|
||||
const hasAllowlist =
|
||||
agentCfg?.models && Object.keys(agentCfg.models).length > 0;
|
||||
const hasStoredOverride = Boolean(
|
||||
sessionEntry?.modelOverride || sessionEntry?.providerOverride,
|
||||
);
|
||||
@@ -335,6 +337,18 @@ export async function agentCommand(
|
||||
model = storedModelOverride;
|
||||
}
|
||||
}
|
||||
if (sessionEntry?.authProfileOverride) {
|
||||
const store = ensureAuthProfileStore();
|
||||
const profile = store.profiles[sessionEntry.authProfileOverride];
|
||||
if (!profile || profile.provider !== provider) {
|
||||
delete sessionEntry.authProfileOverride;
|
||||
sessionEntry.updatedAt = Date.now();
|
||||
if (sessionStore && sessionKey) {
|
||||
sessionStore[sessionKey] = sessionEntry;
|
||||
await saveSessionStore(storePath, sessionStore);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!resolvedThinkLevel) {
|
||||
let catalogForThinking = modelCatalog ?? allowedModelCatalog;
|
||||
@@ -381,6 +395,7 @@ export async function agentCommand(
|
||||
prompt: body,
|
||||
provider: providerOverride,
|
||||
model: modelOverride,
|
||||
authProfileId: sessionEntry?.authProfileOverride,
|
||||
thinkLevel: resolvedThinkLevel,
|
||||
verboseLevel: resolvedVerboseLevel,
|
||||
timeoutMs,
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
} from "./antigravity-oauth.js";
|
||||
import { healthCommand } from "./health.js";
|
||||
import {
|
||||
applyAuthProfileConfig,
|
||||
applyMinimaxConfig,
|
||||
setAnthropicApiKey,
|
||||
writeOAuthCredentials,
|
||||
@@ -275,6 +276,11 @@ async function promptAuthConfig(
|
||||
spin.stop("OAuth complete");
|
||||
if (oauthCreds) {
|
||||
await writeOAuthCredentials("anthropic", oauthCreds);
|
||||
next = applyAuthProfileConfig(next, {
|
||||
profileId: "anthropic:default",
|
||||
provider: "anthropic",
|
||||
mode: "oauth",
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
spin.stop("OAuth failed");
|
||||
@@ -316,12 +322,30 @@ async function promptAuthConfig(
|
||||
spin.stop("Antigravity OAuth complete");
|
||||
if (oauthCreds) {
|
||||
await writeOAuthCredentials("google-antigravity", oauthCreds);
|
||||
next = applyAuthProfileConfig(next, {
|
||||
profileId: "google-antigravity:default",
|
||||
provider: "google-antigravity",
|
||||
mode: "oauth",
|
||||
});
|
||||
// Set default model to Claude Opus 4.5 via Antigravity
|
||||
next = {
|
||||
...next,
|
||||
agent: {
|
||||
...next.agent,
|
||||
model: "google-antigravity/claude-opus-4-5-thinking",
|
||||
model: {
|
||||
...((next.agent?.model as {
|
||||
primary?: string;
|
||||
fallbacks?: string[];
|
||||
}) ?? {}),
|
||||
primary: "google-antigravity/claude-opus-4-5-thinking",
|
||||
},
|
||||
models: {
|
||||
...next.agent?.models,
|
||||
"google-antigravity/claude-opus-4-5-thinking":
|
||||
next.agent?.models?.[
|
||||
"google-antigravity/claude-opus-4-5-thinking"
|
||||
] ?? {},
|
||||
},
|
||||
},
|
||||
};
|
||||
note(
|
||||
@@ -342,6 +366,11 @@ async function promptAuthConfig(
|
||||
runtime,
|
||||
);
|
||||
await setAnthropicApiKey(String(key).trim());
|
||||
next = applyAuthProfileConfig(next, {
|
||||
profileId: "anthropic:default",
|
||||
provider: "anthropic",
|
||||
mode: "api_key",
|
||||
});
|
||||
} else if (authChoice === "minimax") {
|
||||
next = applyMinimaxConfig(next);
|
||||
}
|
||||
@@ -349,7 +378,10 @@ async function promptAuthConfig(
|
||||
const modelInput = guardCancel(
|
||||
await text({
|
||||
message: "Default model (blank to keep)",
|
||||
initialValue: next.agent?.model ?? "",
|
||||
initialValue:
|
||||
typeof next.agent?.model === "string"
|
||||
? next.agent?.model
|
||||
: (next.agent?.model?.primary ?? ""),
|
||||
}),
|
||||
runtime,
|
||||
);
|
||||
@@ -359,7 +391,17 @@ async function promptAuthConfig(
|
||||
...next,
|
||||
agent: {
|
||||
...next.agent,
|
||||
model,
|
||||
model: {
|
||||
...((next.agent?.model as {
|
||||
primary?: string;
|
||||
fallbacks?: string[];
|
||||
}) ?? {}),
|
||||
primary: model,
|
||||
},
|
||||
models: {
|
||||
...next.agent?.models,
|
||||
[model]: next.agent?.models?.[model] ?? {},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
@@ -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: [],
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
@@ -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: [],
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
@@ -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"
|
||||
}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -5,11 +5,12 @@ import path from "node:path";
|
||||
import type { OAuthCredentials } from "@mariozechner/pi-ai";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
|
||||
import { resolveOAuthPath } from "../config/paths.js";
|
||||
import { writeOAuthCredentials } from "./onboard-auth.js";
|
||||
|
||||
describe("writeOAuthCredentials", () => {
|
||||
const previousStateDir = process.env.CLAWDBOT_STATE_DIR;
|
||||
const previousAgentDir = process.env.CLAWDBOT_AGENT_DIR;
|
||||
const previousPiAgentDir = process.env.PI_CODING_AGENT_DIR;
|
||||
let tempStateDir: string | null = null;
|
||||
|
||||
afterEach(async () => {
|
||||
@@ -22,12 +23,24 @@ describe("writeOAuthCredentials", () => {
|
||||
} else {
|
||||
process.env.CLAWDBOT_STATE_DIR = previousStateDir;
|
||||
}
|
||||
if (previousAgentDir === undefined) {
|
||||
delete process.env.CLAWDBOT_AGENT_DIR;
|
||||
} else {
|
||||
process.env.CLAWDBOT_AGENT_DIR = previousAgentDir;
|
||||
}
|
||||
if (previousPiAgentDir === undefined) {
|
||||
delete process.env.PI_CODING_AGENT_DIR;
|
||||
} else {
|
||||
process.env.PI_CODING_AGENT_DIR = previousPiAgentDir;
|
||||
}
|
||||
delete process.env.CLAWDBOT_OAUTH_DIR;
|
||||
});
|
||||
|
||||
it("writes oauth.json under CLAWDBOT_STATE_DIR/credentials", async () => {
|
||||
it("writes auth-profiles.json under CLAWDBOT_STATE_DIR/agent", async () => {
|
||||
tempStateDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-oauth-"));
|
||||
process.env.CLAWDBOT_STATE_DIR = tempStateDir;
|
||||
process.env.CLAWDBOT_AGENT_DIR = path.join(tempStateDir, "agent");
|
||||
process.env.PI_CODING_AGENT_DIR = process.env.CLAWDBOT_AGENT_DIR;
|
||||
|
||||
const creds = {
|
||||
refresh: "refresh-token",
|
||||
@@ -37,16 +50,19 @@ describe("writeOAuthCredentials", () => {
|
||||
|
||||
await writeOAuthCredentials("anthropic", creds);
|
||||
|
||||
const oauthPath = resolveOAuthPath();
|
||||
expect(oauthPath).toBe(
|
||||
path.join(tempStateDir, "credentials", "oauth.json"),
|
||||
const authProfilePath = path.join(
|
||||
tempStateDir,
|
||||
"agent",
|
||||
"auth-profiles.json",
|
||||
);
|
||||
|
||||
const raw = await fs.readFile(oauthPath, "utf8");
|
||||
const parsed = JSON.parse(raw) as Record<string, OAuthCredentials>;
|
||||
expect(parsed.anthropic).toMatchObject({
|
||||
const raw = await fs.readFile(authProfilePath, "utf8");
|
||||
const parsed = JSON.parse(raw) as {
|
||||
profiles?: Record<string, OAuthCredentials & { type?: string }>;
|
||||
};
|
||||
expect(parsed.profiles?.["anthropic:default"]).toMatchObject({
|
||||
refresh: "refresh-token",
|
||||
access: "access-token",
|
||||
type: "oauth",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,47 +1,73 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
import type { OAuthCredentials, OAuthProvider } from "@mariozechner/pi-ai";
|
||||
import { discoverAuthStorage } from "@mariozechner/pi-coding-agent";
|
||||
|
||||
import { resolveClawdbotAgentDir } from "../agents/agent-paths.js";
|
||||
import { upsertAuthProfile } from "../agents/auth-profiles.js";
|
||||
import type { ClawdbotConfig } from "../config/config.js";
|
||||
import { resolveOAuthPath } from "../config/paths.js";
|
||||
|
||||
export async function writeOAuthCredentials(
|
||||
provider: OAuthProvider,
|
||||
creds: OAuthCredentials,
|
||||
): Promise<void> {
|
||||
const filePath = resolveOAuthPath();
|
||||
const dir = path.dirname(filePath);
|
||||
await fs.mkdir(dir, { recursive: true, mode: 0o700 });
|
||||
let storage: Record<string, OAuthCredentials> = {};
|
||||
try {
|
||||
const raw = await fs.readFile(filePath, "utf8");
|
||||
const parsed = JSON.parse(raw) as Record<string, OAuthCredentials>;
|
||||
if (parsed && typeof parsed === "object") storage = parsed;
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
storage[provider] = creds;
|
||||
await fs.writeFile(filePath, `${JSON.stringify(storage, null, 2)}\n`, "utf8");
|
||||
await fs.chmod(filePath, 0o600);
|
||||
upsertAuthProfile({
|
||||
profileId: `${provider}:default`,
|
||||
credential: {
|
||||
type: "oauth",
|
||||
provider,
|
||||
...creds,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function setAnthropicApiKey(key: string) {
|
||||
const agentDir = resolveClawdbotAgentDir();
|
||||
const authStorage = discoverAuthStorage(agentDir);
|
||||
authStorage.set("anthropic", { type: "api_key", key });
|
||||
upsertAuthProfile({
|
||||
profileId: "anthropic:default",
|
||||
credential: {
|
||||
type: "api_key",
|
||||
provider: "anthropic",
|
||||
key,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function applyAuthProfileConfig(
|
||||
cfg: ClawdbotConfig,
|
||||
params: {
|
||||
profileId: string;
|
||||
provider: string;
|
||||
mode: "api_key" | "oauth";
|
||||
email?: string;
|
||||
},
|
||||
): ClawdbotConfig {
|
||||
const profiles = {
|
||||
...cfg.auth?.profiles,
|
||||
[params.profileId]: {
|
||||
provider: params.provider,
|
||||
mode: params.mode,
|
||||
...(params.email ? { email: params.email } : {}),
|
||||
},
|
||||
};
|
||||
const order = { ...cfg.auth?.order };
|
||||
const list = order[params.provider] ? [...order[params.provider]] : [];
|
||||
if (!list.includes(params.profileId)) list.push(params.profileId);
|
||||
order[params.provider] = list;
|
||||
return {
|
||||
...cfg,
|
||||
auth: {
|
||||
...cfg.auth,
|
||||
profiles,
|
||||
order,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function applyMinimaxConfig(cfg: ClawdbotConfig): ClawdbotConfig {
|
||||
const allowed = new Set(cfg.agent?.allowedModels ?? []);
|
||||
allowed.add("anthropic/claude-opus-4-5");
|
||||
allowed.add("lmstudio/minimax-m2.1-gs32");
|
||||
|
||||
const aliases = { ...cfg.agent?.modelAliases };
|
||||
if (!aliases.Opus) aliases.Opus = "anthropic/claude-opus-4-5";
|
||||
if (!aliases.Minimax) aliases.Minimax = "lmstudio/minimax-m2.1-gs32";
|
||||
const models = { ...cfg.agent?.models };
|
||||
models["anthropic/claude-opus-4-5"] = {
|
||||
...models["anthropic/claude-opus-4-5"],
|
||||
alias: models["anthropic/claude-opus-4-5"]?.alias ?? "Opus",
|
||||
};
|
||||
models["lmstudio/minimax-m2.1-gs32"] = {
|
||||
...models["lmstudio/minimax-m2.1-gs32"],
|
||||
alias: models["lmstudio/minimax-m2.1-gs32"]?.alias ?? "Minimax",
|
||||
};
|
||||
|
||||
const providers = { ...cfg.models?.providers };
|
||||
if (!providers.lmstudio) {
|
||||
@@ -67,9 +93,12 @@ export function applyMinimaxConfig(cfg: ClawdbotConfig): ClawdbotConfig {
|
||||
...cfg,
|
||||
agent: {
|
||||
...cfg.agent,
|
||||
model: "Minimax",
|
||||
allowedModels: Array.from(allowed),
|
||||
modelAliases: aliases,
|
||||
model: {
|
||||
...((cfg.agent?.model as { primary?: string; fallbacks?: string[] }) ??
|
||||
{}),
|
||||
primary: "lmstudio/minimax-m2.1-gs32",
|
||||
},
|
||||
models,
|
||||
},
|
||||
models: {
|
||||
mode: cfg.models?.mode ?? "merge",
|
||||
|
||||
@@ -33,7 +33,13 @@ export function summarizeExistingConfig(config: ClawdbotConfig): string {
|
||||
const rows: string[] = [];
|
||||
if (config.agent?.workspace)
|
||||
rows.push(`workspace: ${config.agent.workspace}`);
|
||||
if (config.agent?.model) rows.push(`model: ${config.agent.model}`);
|
||||
if (config.agent?.model) {
|
||||
const model =
|
||||
typeof config.agent.model === "string"
|
||||
? config.agent.model
|
||||
: config.agent.model.primary;
|
||||
if (model) rows.push(`model: ${model}`);
|
||||
}
|
||||
if (config.gateway?.mode) rows.push(`gateway.mode: ${config.gateway.mode}`);
|
||||
if (typeof config.gateway?.port === "number") {
|
||||
rows.push(`gateway.port: ${config.gateway.port}`);
|
||||
|
||||
@@ -14,7 +14,11 @@ import type { RuntimeEnv } from "../runtime.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { resolveUserPath, sleep } from "../utils.js";
|
||||
import { healthCommand } from "./health.js";
|
||||
import { applyMinimaxConfig, setAnthropicApiKey } from "./onboard-auth.js";
|
||||
import {
|
||||
applyAuthProfileConfig,
|
||||
applyMinimaxConfig,
|
||||
setAnthropicApiKey,
|
||||
} from "./onboard-auth.js";
|
||||
import {
|
||||
applyWizardMetadata,
|
||||
DEFAULT_WORKSPACE,
|
||||
@@ -98,6 +102,11 @@ export async function runNonInteractiveOnboarding(
|
||||
return;
|
||||
}
|
||||
await setAnthropicApiKey(key);
|
||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||
profileId: "anthropic:default",
|
||||
provider: "anthropic",
|
||||
mode: "api_key",
|
||||
});
|
||||
} else if (authChoice === "minimax") {
|
||||
nextConfig = applyMinimaxConfig(nextConfig);
|
||||
} else if (
|
||||
|
||||
@@ -12,7 +12,11 @@ vi.mock("../config/config.js", async (importOriginal) => {
|
||||
return {
|
||||
...actual,
|
||||
loadConfig: () => ({
|
||||
agent: { model: "pi:opus", contextTokens: 32000 },
|
||||
agent: {
|
||||
model: { primary: "pi:opus" },
|
||||
models: { "pi:opus": {} },
|
||||
contextTokens: 32000,
|
||||
},
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
ensureAgentWorkspace,
|
||||
} from "../agents/workspace.js";
|
||||
import { type ClawdbotConfig, CONFIG_PATH_CLAWDBOT } from "../config/config.js";
|
||||
import { applyModelAliasDefaults } from "../config/defaults.js";
|
||||
import { applyModelDefaults } from "../config/defaults.js";
|
||||
import { resolveSessionTranscriptsDir } from "../config/sessions.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
@@ -31,7 +31,7 @@ async function readConfigFileRaw(): Promise<{
|
||||
|
||||
async function writeConfigFile(cfg: ClawdbotConfig) {
|
||||
await fs.mkdir(path.dirname(CONFIG_PATH_CLAWDBOT), { recursive: true });
|
||||
const json = JSON.stringify(applyModelAliasDefaults(cfg), null, 2)
|
||||
const json = JSON.stringify(applyModelDefaults(cfg), null, 2)
|
||||
.trimEnd()
|
||||
.concat("\n");
|
||||
await fs.writeFile(CONFIG_PATH_CLAWDBOT, json, "utf-8");
|
||||
|
||||
Reference in New Issue
Block a user