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

@@ -1,19 +1,28 @@
export function extractModelDirective(body?: string): {
cleaned: string;
rawModel?: string;
rawProfile?: string;
hasDirective: boolean;
} {
if (!body) return { cleaned: "", hasDirective: false };
const match = body.match(
/(?:^|\s)\/model(?=$|\s|:)\s*:?\s*([A-Za-z0-9_.:-]+(?:\/[A-Za-z0-9_.:-]+)?)?/i,
/(?:^|\s)\/model(?=$|\s|:)\s*:?\s*([A-Za-z0-9_.:@-]+(?:\/[A-Za-z0-9_.:@-]+)?)?/i,
);
const rawModel = match?.[1]?.trim();
const raw = match?.[1]?.trim();
let rawModel = raw;
let rawProfile: string | undefined;
if (raw?.includes("@")) {
const parts = raw.split("@");
rawModel = parts[0]?.trim();
rawProfile = parts.slice(1).join("@").trim() || undefined;
}
const cleaned = match
? body.replace(match[0], "").replace(/\s+/g, " ").trim()
: body.trim();
return {
cleaned,
rawModel,
rawProfile,
hasDirective: !!match,
};
}

View File

@@ -37,11 +37,24 @@ vi.mock("../agents/model-catalog.js", () => ({
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
const base = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-reply-"));
const previousHome = process.env.HOME;
const previousStateDir = process.env.CLAWDBOT_STATE_DIR;
const previousAgentDir = process.env.CLAWDBOT_AGENT_DIR;
const previousPiAgentDir = process.env.PI_CODING_AGENT_DIR;
process.env.HOME = base;
process.env.CLAWDBOT_STATE_DIR = path.join(base, ".clawdbot");
process.env.CLAWDBOT_AGENT_DIR = path.join(base, ".clawdbot", "agent");
process.env.PI_CODING_AGENT_DIR = process.env.CLAWDBOT_AGENT_DIR;
try {
return await fn(base);
} finally {
process.env.HOME = previousHome;
if (previousStateDir === undefined) delete process.env.CLAWDBOT_STATE_DIR;
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;
await fs.rm(base, { recursive: true, force: true });
}
}
@@ -566,9 +579,12 @@ describe("directive parsing", () => {
{},
{
agent: {
model: "anthropic/claude-opus-4-5",
model: { primary: "anthropic/claude-opus-4-5" },
workspace: path.join(home, "clawd"),
allowedModels: ["anthropic/claude-opus-4-5", "openai/gpt-4.1-mini"],
models: {
"anthropic/claude-opus-4-5": {},
"openai/gpt-4.1-mini": {},
},
},
session: { store: storePath },
},
@@ -593,9 +609,12 @@ describe("directive parsing", () => {
{},
{
agent: {
model: "anthropic/claude-opus-4-5",
model: { primary: "anthropic/claude-opus-4-5" },
workspace: path.join(home, "clawd"),
allowedModels: ["anthropic/claude-opus-4-5", "openai/gpt-4.1-mini"],
models: {
"anthropic/claude-opus-4-5": {},
"openai/gpt-4.1-mini": {},
},
},
session: { store: storePath },
},
@@ -620,9 +639,12 @@ describe("directive parsing", () => {
{},
{
agent: {
model: "anthropic/claude-opus-4-5",
model: { primary: "anthropic/claude-opus-4-5" },
workspace: path.join(home, "clawd"),
allowedModels: ["anthropic/claude-opus-4-5", "openai/gpt-4.1-mini"],
models: {
"anthropic/claude-opus-4-5": {},
"openai/gpt-4.1-mini": {},
},
},
session: { store: storePath },
},
@@ -646,9 +668,11 @@ describe("directive parsing", () => {
{},
{
agent: {
model: "anthropic/claude-opus-4-5",
model: { primary: "anthropic/claude-opus-4-5" },
workspace: path.join(home, "clawd"),
allowedModels: ["anthropic/claude-opus-4-5"],
models: {
"anthropic/claude-opus-4-5": {},
},
},
session: { store: storePath },
},
@@ -671,9 +695,12 @@ describe("directive parsing", () => {
{},
{
agent: {
model: "anthropic/claude-opus-4-5",
model: { primary: "anthropic/claude-opus-4-5" },
workspace: path.join(home, "clawd"),
allowedModels: ["openai/gpt-4.1-mini"],
models: {
"anthropic/claude-opus-4-5": {},
"openai/gpt-4.1-mini": {},
},
},
session: { store: storePath },
},
@@ -699,11 +726,11 @@ describe("directive parsing", () => {
{},
{
agent: {
model: "openai/gpt-4.1-mini",
model: { primary: "openai/gpt-4.1-mini" },
workspace: path.join(home, "clawd"),
allowedModels: ["openai/gpt-4.1-mini", "anthropic/claude-opus-4-5"],
modelAliases: {
Opus: "anthropic/claude-opus-4-5",
models: {
"openai/gpt-4.1-mini": {},
"anthropic/claude-opus-4-5": { alias: "Opus" },
},
},
session: { store: storePath },
@@ -721,6 +748,55 @@ describe("directive parsing", () => {
});
});
it("stores auth profile overrides on /model directive", async () => {
await withTempHome(async (home) => {
vi.mocked(runEmbeddedPiAgent).mockReset();
const storePath = path.join(home, "sessions.json");
const authDir = path.join(home, ".clawdbot", "agent");
await fs.mkdir(authDir, { recursive: true, mode: 0o700 });
await fs.writeFile(
path.join(authDir, "auth-profiles.json"),
JSON.stringify(
{
version: 1,
profiles: {
"anthropic:work": {
type: "api_key",
provider: "anthropic",
key: "sk-test-1234567890",
},
},
},
null,
2,
),
);
const res = await getReplyFromConfig(
{ Body: "/model Opus@anthropic:work", From: "+1222", To: "+1222" },
{},
{
agent: {
model: { primary: "openai/gpt-4.1-mini" },
workspace: path.join(home, "clawd"),
models: {
"openai/gpt-4.1-mini": {},
"anthropic/claude-opus-4-5": { alias: "Opus" },
},
},
session: { store: storePath },
},
);
const text = Array.isArray(res) ? res[0]?.text : res?.text;
expect(text).toContain("Auth profile set to anthropic:work");
const store = loadSessionStore(storePath);
const entry = store.main;
expect(entry.authProfileOverride).toBe("anthropic:work");
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
});
});
it("queues a system event when switching models", async () => {
await withTempHome(async (home) => {
drainSystemEvents();
@@ -732,11 +808,11 @@ describe("directive parsing", () => {
{},
{
agent: {
model: "openai/gpt-4.1-mini",
model: { primary: "openai/gpt-4.1-mini" },
workspace: path.join(home, "clawd"),
allowedModels: ["openai/gpt-4.1-mini", "anthropic/claude-opus-4-5"],
modelAliases: {
Opus: "anthropic/claude-opus-4-5",
models: {
"openai/gpt-4.1-mini": {},
"anthropic/claude-opus-4-5": { alias: "Opus" },
},
},
session: { store: storePath },
@@ -771,9 +847,12 @@ describe("directive parsing", () => {
{},
{
agent: {
model: "anthropic/claude-opus-4-5",
model: { primary: "anthropic/claude-opus-4-5" },
workspace: path.join(home, "clawd"),
allowedModels: ["openai/gpt-4.1-mini"],
models: {
"anthropic/claude-opus-4-5": {},
"openai/gpt-4.1-mini": {},
},
},
whatsapp: {
allowFrom: ["*"],

View File

@@ -361,7 +361,9 @@ export async function getReplyFromConfig(
: `Model switched to ${label}.`;
const isModelListAlias =
directives.hasModelDirective &&
directives.rawModelDirective?.trim().toLowerCase() === "status";
["status", "list"].includes(
directives.rawModelDirective?.trim().toLowerCase() ?? "",
);
const effectiveModelDirective = isModelListAlias
? undefined
: directives.rawModelDirective;
@@ -376,6 +378,7 @@ export async function getReplyFromConfig(
})
) {
const directiveReply = await handleDirectiveOnly({
cfg,
directives,
sessionEntry,
sessionStore,
@@ -401,6 +404,7 @@ export async function getReplyFromConfig(
const persisted = await persistInlineDirectives({
directives,
effectiveModelDirective,
cfg,
sessionEntry,
sessionStore,
sessionKey,
@@ -634,6 +638,7 @@ export async function getReplyFromConfig(
resolvedQueue.mode === "followup" ||
resolvedQueue.mode === "collect" ||
resolvedQueue.mode === "steer-backlog";
const authProfileId = sessionEntry?.authProfileOverride;
const followupRun = {
prompt: queuedBody,
summaryLine: baseBodyTrimmedRaw,
@@ -648,6 +653,7 @@ export async function getReplyFromConfig(
skillsSnapshot,
provider,
model,
authProfileId,
thinkLevel: resolvedThinkLevel,
verboseLevel: resolvedVerboseLevel,
elevatedLevel: resolvedElevatedLevel,

View File

@@ -195,6 +195,7 @@ export async function runReplyAgent(params: {
enforceFinalTag: followupRun.run.enforceFinalTag,
provider,
model,
authProfileId: followupRun.run.authProfileId,
thinkLevel: followupRun.run.thinkLevel,
verboseLevel: followupRun.run.verboseLevel,
bashElevated: followupRun.run.bashElevated,

View File

@@ -1,10 +1,12 @@
import fs from "node:fs";
import { getEnvApiKey } from "@mariozechner/pi-ai";
import { discoverAuthStorage } from "@mariozechner/pi-coding-agent";
import { resolveClawdbotAgentDir } from "../../agents/agent-paths.js";
import {
ensureAuthProfileStore,
listProfilesForProvider,
} from "../../agents/auth-profiles.js";
import {
getCustomProviderApiKey,
resolveEnvApiKey,
} from "../../agents/model-auth.js";
import type { ClawdbotConfig } from "../../config/config.js";
import { resolveOAuthPath } from "../../config/paths.js";
import {
type SessionEntry,
type SessionScope,
@@ -42,55 +44,32 @@ export type CommandContext = {
to?: string;
};
function hasOAuthCredentials(provider: string): boolean {
try {
const oauthPath = resolveOAuthPath();
if (!fs.existsSync(oauthPath)) return false;
const raw = fs.readFileSync(oauthPath, "utf8");
const parsed = JSON.parse(raw) as Record<string, unknown>;
const entry = parsed?.[provider] as
| {
refresh?: string;
refresh_token?: string;
refreshToken?: string;
access?: string;
access_token?: string;
accessToken?: string;
}
| undefined;
if (!entry) return false;
const refresh =
entry.refresh ?? entry.refresh_token ?? entry.refreshToken ?? "";
const access =
entry.access ?? entry.access_token ?? entry.accessToken ?? "";
return Boolean(refresh.trim() && access.trim());
} catch {
return false;
}
}
function resolveModelAuthLabel(provider?: string): string | undefined {
function resolveModelAuthLabel(
provider?: string,
cfg?: ClawdbotConfig,
): string | undefined {
const resolved = provider?.trim();
if (!resolved) return undefined;
try {
const authStorage = discoverAuthStorage(resolveClawdbotAgentDir());
const stored = authStorage.get(resolved);
if (stored?.type === "oauth") return "oauth";
if (stored?.type === "api_key") return "api-key";
} catch {
// ignore auth storage errors
const store = ensureAuthProfileStore();
const profiles = listProfilesForProvider(store, resolved);
if (profiles.length > 0) {
const modes = new Set(
profiles
.map((id) => store.profiles[id]?.type)
.filter((mode): mode is "api_key" | "oauth" => Boolean(mode)),
);
if (modes.has("oauth") && modes.has("api_key")) return "mixed";
if (modes.has("oauth")) return "oauth";
if (modes.has("api_key")) return "api-key";
}
if (resolved === "anthropic") {
const oauthEnv = process.env.ANTHROPIC_OAUTH_TOKEN;
if (oauthEnv?.trim()) return "oauth";
const envKey = resolveEnvApiKey(resolved);
if (envKey?.apiKey) {
return envKey.source.includes("OAUTH_TOKEN") ? "oauth" : "api-key";
}
if (hasOAuthCredentials(resolved)) return "oauth";
const envKey = getEnvApiKey(resolved);
if (envKey?.trim()) return "api-key";
if (getCustomProviderApiKey(cfg, resolved)) return "api-key";
return "unknown";
}
@@ -374,7 +353,7 @@ export async function handleCommands(params: {
resolvedThinkLevel ?? (await resolveDefaultThinkingLevel()),
resolvedVerbose: resolvedVerboseLevel,
resolvedElevated: resolvedElevatedLevel,
modelAuth: resolveModelAuthLabel(provider),
modelAuth: resolveModelAuthLabel(provider, cfg),
webLinked,
webAuthAgeMs,
heartbeatSeconds,

View File

@@ -1,13 +1,20 @@
import { getEnvApiKey } from "@mariozechner/pi-ai";
import { discoverAuthStorage } from "@mariozechner/pi-coding-agent";
import { resolveClawdbotAgentDir } from "../../agents/agent-paths.js";
import {
resolveAuthProfileDisplayLabel,
resolveAuthStorePathForDisplay,
} from "../../agents/auth-profiles.js";
import { lookupContextTokens } from "../../agents/context.js";
import {
DEFAULT_CONTEXT_TOKENS,
DEFAULT_MODEL,
DEFAULT_PROVIDER,
} from "../../agents/defaults.js";
import { hydrateAuthStorage } from "../../agents/model-auth.js";
import {
ensureAuthProfileStore,
getCustomProviderApiKey,
resolveAuthProfileOrder,
resolveEnvApiKey,
} from "../../agents/model-auth.js";
import {
buildModelAliasIndex,
type ModelAliasIndex,
@@ -53,43 +60,63 @@ const maskApiKey = (value: string): string => {
const resolveAuthLabel = async (
provider: string,
authStorage: ReturnType<typeof discoverAuthStorage>,
authPaths: { authPath: string; modelsPath: string },
cfg: ClawdbotConfig,
modelsPath: string,
): Promise<{ label: string; source: string }> => {
const formatPath = (value: string) => shortenHomePath(value);
const stored = authStorage.get(provider);
if (stored?.type === "oauth") {
const email = stored.email?.trim();
const store = ensureAuthProfileStore();
const order = resolveAuthProfileOrder({ cfg, store, provider });
if (order.length > 0) {
const labels = order.map((profileId) => {
const profile = store.profiles[profileId];
const configProfile = cfg.auth?.profiles?.[profileId];
if (
!profile ||
(configProfile?.provider &&
configProfile.provider !== profile.provider) ||
(configProfile?.mode && configProfile.mode !== profile.type)
) {
return `${profileId}=missing`;
}
if (profile.type === "api_key") {
return `${profileId}=${maskApiKey(profile.key)}`;
}
const display = resolveAuthProfileDisplayLabel({
cfg,
store,
profileId,
});
const suffix =
display === profileId
? ""
: display.startsWith(profileId)
? display.slice(profileId.length).trim()
: `(${display})`;
return `${profileId}=OAuth${suffix ? ` ${suffix}` : ""}`;
});
return {
label: email ? `OAuth ${email}` : "OAuth (unknown)",
source: `auth.json: ${formatPath(authPaths.authPath)}`,
label: labels.join(", "),
source: `auth-profiles.json: ${formatPath(
resolveAuthStorePathForDisplay(),
)}`,
};
}
if (stored?.type === "api_key") {
const envKey = resolveEnvApiKey(provider);
if (envKey) {
const isOAuthEnv =
envKey.source.includes("ANTHROPIC_OAUTH_TOKEN") ||
envKey.source.toLowerCase().includes("oauth");
const label = isOAuthEnv ? "OAuth (env)" : maskApiKey(envKey.apiKey);
return { label, source: envKey.source };
}
const customKey = getCustomProviderApiKey(cfg, provider);
if (customKey) {
return {
label: maskApiKey(stored.key),
source: `auth.json: ${formatPath(authPaths.authPath)}`,
label: maskApiKey(customKey),
source: `models.json: ${formatPath(modelsPath)}`,
};
}
const envKey = getEnvApiKey(provider);
if (envKey) return { label: maskApiKey(envKey), source: "env" };
if (provider === "anthropic") {
const oauthEnv = process.env.ANTHROPIC_OAUTH_TOKEN?.trim();
if (oauthEnv) {
return { label: "OAuth (env)", source: "env: ANTHROPIC_OAUTH_TOKEN" };
}
}
try {
const key = await authStorage.getApiKey(provider);
if (key) {
return {
label: maskApiKey(key),
source: `models.json: ${formatPath(authPaths.modelsPath)}`,
};
}
} catch {
// ignore missing auth
}
return { label: "missing", source: "missing" };
};
@@ -100,6 +127,26 @@ const formatAuthLabel = (auth: { label: string; source: string }) => {
return `${auth.label} (${auth.source})`;
};
const resolveProfileOverride = (params: {
rawProfile?: string;
provider: string;
cfg: ClawdbotConfig;
}): { profileId?: string; error?: string } => {
const raw = params.rawProfile?.trim();
if (!raw) return {};
const store = ensureAuthProfileStore();
const profile = store.profiles[raw];
if (!profile) {
return { error: `Auth profile "${raw}" not found.` };
}
if (profile.provider !== params.provider) {
return {
error: `Auth profile "${raw}" is for ${profile.provider}, not ${params.provider}.`,
};
}
return { profileId: raw };
};
export type InlineDirectives = {
cleaned: string;
hasThinkDirective: boolean;
@@ -114,6 +161,7 @@ export type InlineDirectives = {
hasStatusDirective: boolean;
hasModelDirective: boolean;
rawModelDirective?: string;
rawModelProfile?: string;
hasQueueDirective: boolean;
queueMode?: QueueMode;
queueReset: boolean;
@@ -151,6 +199,7 @@ export function parseInlineDirectives(body: string): InlineDirectives {
const {
cleaned: modelCleaned,
rawModel,
rawProfile,
hasDirective: hasModelDirective,
} = extractModelDirective(statusCleaned);
const {
@@ -182,6 +231,7 @@ export function parseInlineDirectives(body: string): InlineDirectives {
hasStatusDirective,
hasModelDirective,
rawModelDirective: rawModel,
rawModelProfile: rawProfile,
hasQueueDirective,
queueMode,
queueReset,
@@ -218,6 +268,7 @@ export function isDirectiveOnly(params: {
}
export async function handleDirectiveOnly(params: {
cfg: ClawdbotConfig;
directives: InlineDirectives;
sessionEntry?: SessionEntry;
sessionStore?: Record<string, SessionEntry>;
@@ -265,19 +316,14 @@ export async function handleDirectiveOnly(params: {
return { text: "No models available." };
}
const agentDir = resolveClawdbotAgentDir();
const authStorage = discoverAuthStorage(agentDir);
const authPaths = {
authPath: `${agentDir}/auth.json`,
modelsPath: `${agentDir}/models.json`,
};
hydrateAuthStorage(authStorage);
const modelsPath = `${agentDir}/models.json`;
const authByProvider = new Map<string, string>();
for (const entry of allowedModelCatalog) {
if (authByProvider.has(entry.provider)) continue;
const auth = await resolveAuthLabel(
entry.provider,
authStorage,
authPaths,
params.cfg,
modelsPath,
);
authByProvider.set(entry.provider, formatAuthLabel(auth));
}
@@ -306,6 +352,9 @@ export async function handleDirectiveOnly(params: {
}
return { text: lines.join("\n") };
}
if (directives.rawModelProfile && !modelDirective) {
throw new Error("Auth profile override requires a model selection.");
}
}
if (directives.hasThinkDirective && !directives.thinkLevel) {
@@ -378,6 +427,7 @@ export async function handleDirectiveOnly(params: {
}
let modelSelection: ModelDirectiveSelection | undefined;
let profileOverride: string | undefined;
if (directives.hasModelDirective && directives.rawModelDirective) {
const resolved = resolveModelDirectiveSelection({
raw: directives.rawModelDirective,
@@ -391,6 +441,17 @@ export async function handleDirectiveOnly(params: {
}
modelSelection = resolved.selection;
if (modelSelection) {
if (directives.rawModelProfile) {
const profileResolved = resolveProfileOverride({
rawProfile: directives.rawModelProfile,
provider: modelSelection.provider,
cfg: params.cfg,
});
if (profileResolved.error) {
return { text: profileResolved.error };
}
profileOverride = profileResolved.profileId;
}
const nextLabel = `${modelSelection.provider}/${modelSelection.model}`;
if (nextLabel !== initialModelLabel) {
enqueueSystemEvent(
@@ -402,6 +463,9 @@ export async function handleDirectiveOnly(params: {
}
}
}
if (directives.rawModelProfile && !modelSelection) {
return { text: "Auth profile override requires a model selection." };
}
if (sessionEntry && sessionStore && sessionKey) {
if (directives.hasThinkDirective && directives.thinkLevel) {
@@ -424,6 +488,11 @@ export async function handleDirectiveOnly(params: {
sessionEntry.providerOverride = modelSelection.provider;
sessionEntry.modelOverride = modelSelection.model;
}
if (profileOverride) {
sessionEntry.authProfileOverride = profileOverride;
} else if (directives.hasModelDirective) {
delete sessionEntry.authProfileOverride;
}
}
if (directives.hasQueueDirective && directives.queueReset) {
delete sessionEntry.queueMode;
@@ -481,6 +550,9 @@ export async function handleDirectiveOnly(params: {
? `Model reset to default (${labelWithAlias}).`
: `Model set to ${labelWithAlias}.`,
);
if (profileOverride) {
parts.push(`Auth profile set to ${profileOverride}.`);
}
}
if (directives.hasQueueDirective && directives.queueMode) {
parts.push(`${SYSTEM_MARK} Queue mode set to ${directives.queueMode}.`);
@@ -508,6 +580,7 @@ export async function handleDirectiveOnly(params: {
export async function persistInlineDirectives(params: {
directives: InlineDirectives;
effectiveModelDirective?: string;
cfg: ClawdbotConfig;
sessionEntry?: SessionEntry;
sessionStore?: Record<string, SessionEntry>;
sessionKey?: string;
@@ -526,6 +599,7 @@ export async function persistInlineDirectives(params: {
}): Promise<{ provider: string; model: string; contextTokens: number }> {
const {
directives,
cfg,
sessionEntry,
sessionStore,
sessionKey,
@@ -586,6 +660,18 @@ export async function persistInlineDirectives(params: {
if (resolved) {
const key = modelKey(resolved.ref.provider, resolved.ref.model);
if (allowedModelKeys.size === 0 || allowedModelKeys.has(key)) {
let profileOverride: string | undefined;
if (directives.rawModelProfile) {
const profileResolved = resolveProfileOverride({
rawProfile: directives.rawModelProfile,
provider: resolved.ref.provider,
cfg,
});
if (profileResolved.error) {
throw new Error(profileResolved.error);
}
profileOverride = profileResolved.profileId;
}
const isDefault =
resolved.ref.provider === defaultProvider &&
resolved.ref.model === defaultModel;
@@ -596,6 +682,11 @@ export async function persistInlineDirectives(params: {
sessionEntry.providerOverride = resolved.ref.provider;
sessionEntry.modelOverride = resolved.ref.model;
}
if (profileOverride) {
sessionEntry.authProfileOverride = profileOverride;
} else if (directives.hasModelDirective) {
delete sessionEntry.authProfileOverride;
}
provider = resolved.ref.provider;
model = resolved.ref.model;
const nextLabel = `${provider}/${model}`;

View File

@@ -84,6 +84,7 @@ export function createFollowupRunner(params: {
enforceFinalTag: queued.run.enforceFinalTag,
provider,
model,
authProfileId: queued.run.authProfileId,
thinkLevel: queued.run.thinkLevel,
verboseLevel: queued.run.verboseLevel,
bashElevated: queued.run.bashElevated,

View File

@@ -57,7 +57,8 @@ export async function createModelSelectionState(params: {
let provider = params.provider;
let model = params.model;
const hasAllowlist = (agentCfg?.allowedModels?.length ?? 0) > 0;
const hasAllowlist =
agentCfg?.models && Object.keys(agentCfg.models).length > 0;
const hasStoredOverride = Boolean(
sessionEntry?.modelOverride || sessionEntry?.providerOverride,
);
@@ -110,6 +111,27 @@ export async function createModelSelectionState(params: {
}
}
if (
sessionEntry &&
sessionStore &&
sessionKey &&
sessionEntry.authProfileOverride
) {
const { ensureAuthProfileStore } = await import(
"../../agents/auth-profiles.js"
);
const store = ensureAuthProfileStore();
const profile = store.profiles[sessionEntry.authProfileOverride];
if (!profile || profile.provider !== provider) {
delete sessionEntry.authProfileOverride;
sessionEntry.updatedAt = Date.now();
sessionStore[sessionKey] = sessionEntry;
if (storePath) {
await saveSessionStore(storePath, sessionStore);
}
}
}
let defaultThinkingLevel: ThinkLevel | undefined;
const resolveDefaultThinkingLevel = async () => {
if (defaultThinkingLevel) return defaultThinkingLevel;

View File

@@ -32,6 +32,7 @@ export type FollowupRun = {
skillsSnapshot?: SkillSnapshot;
provider: string;
model: string;
authProfileId?: string;
thinkLevel?: ThinkLevel;
verboseLevel?: VerboseLevel;
elevatedLevel?: ElevatedLevel;