feat: add per-session model selection
This commit is contained in:
@@ -49,9 +49,12 @@ function mockConfig(
|
||||
inboundOverrides?: Partial<NonNullable<ClawdisConfig["inbound"]>>,
|
||||
) {
|
||||
configSpy.mockReturnValue({
|
||||
inbound: {
|
||||
agent: {
|
||||
provider: "anthropic",
|
||||
model: "claude-opus-4-5",
|
||||
workspace: path.join(home, "clawd"),
|
||||
agent: { provider: "anthropic", model: "claude-opus-4-5" },
|
||||
},
|
||||
inbound: {
|
||||
session: { store: storePath, mainKey: "main" },
|
||||
...inboundOverrides,
|
||||
},
|
||||
|
||||
@@ -5,6 +5,8 @@ import {
|
||||
DEFAULT_MODEL,
|
||||
DEFAULT_PROVIDER,
|
||||
} from "../agents/defaults.js";
|
||||
import { loadModelCatalog } from "../agents/model-catalog.js";
|
||||
import { buildAllowedModelSet, modelKey } from "../agents/model-selection.js";
|
||||
import { runEmbeddedPiAgent } from "../agents/pi-embedded.js";
|
||||
import { buildWorkspaceSkillSnapshot } from "../agents/skills.js";
|
||||
import {
|
||||
@@ -140,8 +142,8 @@ export async function agentCommand(
|
||||
}
|
||||
|
||||
const cfg = loadConfig();
|
||||
const agentCfg = cfg.inbound?.agent;
|
||||
const workspaceDirRaw = cfg.inbound?.workspace ?? DEFAULT_AGENT_WORKSPACE_DIR;
|
||||
const agentCfg = cfg.agent;
|
||||
const workspaceDirRaw = cfg.agent?.workspace ?? DEFAULT_AGENT_WORKSPACE_DIR;
|
||||
const workspace = await ensureAgentWorkspace({
|
||||
dir: workspaceDirRaw,
|
||||
ensureBootstrapFiles: true,
|
||||
@@ -245,8 +247,53 @@ export async function agentCommand(
|
||||
await saveSessionStore(storePath, sessionStore);
|
||||
}
|
||||
|
||||
const provider = agentCfg?.provider?.trim() || DEFAULT_PROVIDER;
|
||||
const model = agentCfg?.model?.trim() || DEFAULT_MODEL;
|
||||
const defaultProvider = agentCfg?.provider?.trim() || DEFAULT_PROVIDER;
|
||||
const defaultModel = agentCfg?.model?.trim() || DEFAULT_MODEL;
|
||||
let provider = defaultProvider;
|
||||
let model = defaultModel;
|
||||
const hasAllowlist = (agentCfg?.allowedModels?.length ?? 0) > 0;
|
||||
const hasStoredOverride = Boolean(
|
||||
sessionEntry?.modelOverride || sessionEntry?.providerOverride,
|
||||
);
|
||||
const needsModelCatalog = hasAllowlist || hasStoredOverride;
|
||||
let allowedModelKeys = new Set<string>();
|
||||
|
||||
if (needsModelCatalog) {
|
||||
const catalog = await loadModelCatalog({ config: cfg });
|
||||
const allowed = buildAllowedModelSet({
|
||||
cfg,
|
||||
catalog,
|
||||
defaultProvider,
|
||||
});
|
||||
allowedModelKeys = allowed.allowedKeys;
|
||||
}
|
||||
|
||||
if (sessionEntry && sessionStore && sessionKey && hasStoredOverride) {
|
||||
const overrideProvider =
|
||||
sessionEntry.providerOverride?.trim() || defaultProvider;
|
||||
const overrideModel = sessionEntry.modelOverride?.trim();
|
||||
if (overrideModel) {
|
||||
const key = modelKey(overrideProvider, overrideModel);
|
||||
if (allowedModelKeys.size > 0 && !allowedModelKeys.has(key)) {
|
||||
delete sessionEntry.providerOverride;
|
||||
delete sessionEntry.modelOverride;
|
||||
sessionEntry.updatedAt = Date.now();
|
||||
sessionStore[sessionKey] = sessionEntry;
|
||||
await saveSessionStore(storePath, sessionStore);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const storedProviderOverride = sessionEntry?.providerOverride?.trim();
|
||||
const storedModelOverride = sessionEntry?.modelOverride?.trim();
|
||||
if (storedModelOverride) {
|
||||
const candidateProvider = storedProviderOverride || defaultProvider;
|
||||
const key = modelKey(candidateProvider, storedModelOverride);
|
||||
if (allowedModelKeys.size === 0 || allowedModelKeys.has(key)) {
|
||||
provider = candidateProvider;
|
||||
model = storedModelOverride;
|
||||
}
|
||||
}
|
||||
const sessionFile = resolveSessionTranscriptPath(sessionId);
|
||||
|
||||
const startedAt = Date.now();
|
||||
|
||||
@@ -9,9 +9,7 @@ process.env.FORCE_COLOR = "0";
|
||||
|
||||
vi.mock("../config/config.js", () => ({
|
||||
loadConfig: () => ({
|
||||
inbound: {
|
||||
agent: { model: "pi:opus", contextTokens: 32000 },
|
||||
},
|
||||
agent: { model: "pi:opus", contextTokens: 32000 },
|
||||
}),
|
||||
}));
|
||||
|
||||
|
||||
@@ -152,10 +152,10 @@ export async function sessionsCommand(
|
||||
) {
|
||||
const cfg = loadConfig();
|
||||
const configContextTokens =
|
||||
cfg.inbound?.agent?.contextTokens ??
|
||||
lookupContextTokens(cfg.inbound?.agent?.model) ??
|
||||
cfg.agent?.contextTokens ??
|
||||
lookupContextTokens(cfg.agent?.model) ??
|
||||
DEFAULT_CONTEXT_TOKENS;
|
||||
const configModel = cfg.inbound?.agent?.model ?? DEFAULT_MODEL;
|
||||
const configModel = cfg.agent?.model ?? DEFAULT_MODEL;
|
||||
const storePath = resolveStorePath(opts.store ?? cfg.inbound?.session?.store);
|
||||
const store = loadSessionStore(storePath);
|
||||
|
||||
|
||||
@@ -46,24 +46,25 @@ export async function setupCommand(
|
||||
const existingRaw = await readConfigFileRaw();
|
||||
const cfg = existingRaw.parsed;
|
||||
const inbound = cfg.inbound ?? {};
|
||||
const agent = cfg.agent ?? {};
|
||||
|
||||
const workspace =
|
||||
desiredWorkspace ?? inbound.workspace ?? DEFAULT_AGENT_WORKSPACE_DIR;
|
||||
desiredWorkspace ?? agent.workspace ?? DEFAULT_AGENT_WORKSPACE_DIR;
|
||||
|
||||
const next: ClawdisConfig = {
|
||||
...cfg,
|
||||
inbound: {
|
||||
...inbound,
|
||||
agent: {
|
||||
...agent,
|
||||
workspace,
|
||||
},
|
||||
};
|
||||
|
||||
if (!existingRaw.exists || inbound.workspace !== workspace) {
|
||||
if (!existingRaw.exists || agent.workspace !== workspace) {
|
||||
await writeConfigFile(next);
|
||||
runtime.log(
|
||||
!existingRaw.exists
|
||||
? `Wrote ${CONFIG_PATH_CLAWDIS}`
|
||||
: `Updated ${CONFIG_PATH_CLAWDIS} (set inbound.workspace)`,
|
||||
: `Updated ${CONFIG_PATH_CLAWDIS} (set agent.workspace)`,
|
||||
);
|
||||
} else {
|
||||
runtime.log(`Config OK: ${CONFIG_PATH_CLAWDIS}`);
|
||||
|
||||
@@ -60,9 +60,9 @@ export async function getStatusSummary(): Promise<StatusSummary> {
|
||||
const providerSummary = await buildProviderSummary(cfg);
|
||||
const queuedSystemEvents = peekSystemEvents();
|
||||
|
||||
const configModel = cfg.inbound?.agent?.model ?? DEFAULT_MODEL;
|
||||
const configModel = cfg.agent?.model ?? DEFAULT_MODEL;
|
||||
const configContextTokens =
|
||||
cfg.inbound?.agent?.contextTokens ??
|
||||
cfg.agent?.contextTokens ??
|
||||
lookupContextTokens(configModel) ??
|
||||
DEFAULT_CONTEXT_TOKENS;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user