feat(config): allow provider/model shorthand
This commit is contained in:
@@ -120,8 +120,7 @@ Controls the embedded agent runtime (provider/model/thinking/verbose/timeouts).
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
provider: "anthropic",
|
||||
model: "claude-opus-4-5",
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
allowedModels: [
|
||||
"anthropic/claude-opus-4-5",
|
||||
"anthropic/claude-sonnet-4-1"
|
||||
@@ -142,6 +141,9 @@ Controls the embedded agent runtime (provider/model/thinking/verbose/timeouts).
|
||||
}
|
||||
```
|
||||
|
||||
`agent.model` can be set as `provider/model` (e.g. `anthropic/claude-opus-4-5`).
|
||||
When present, it overrides `agent.provider` (which becomes optional).
|
||||
|
||||
`agent.bash` configures background bash defaults:
|
||||
- `backgroundMs`: time before auto-background (ms, default 20000)
|
||||
- `timeoutSec`: auto-kill after this runtime (seconds, default 1800)
|
||||
|
||||
@@ -26,6 +26,22 @@ export function parseModelRef(
|
||||
return { provider, model };
|
||||
}
|
||||
|
||||
export function resolveConfiguredModelRef(params: {
|
||||
cfg: ClawdisConfig;
|
||||
defaultProvider: string;
|
||||
defaultModel: string;
|
||||
}): ModelRef {
|
||||
const rawProvider = params.cfg.agent?.provider?.trim() || "";
|
||||
const rawModel = params.cfg.agent?.model?.trim() || "";
|
||||
const providerFallback = rawProvider || params.defaultProvider;
|
||||
if (rawModel) {
|
||||
const parsed = parseModelRef(rawModel, providerFallback);
|
||||
if (parsed) return parsed;
|
||||
return { provider: providerFallback, model: rawModel };
|
||||
}
|
||||
return { provider: providerFallback, model: params.defaultModel };
|
||||
}
|
||||
|
||||
export function buildAllowedModelSet(params: {
|
||||
cfg: ClawdisConfig;
|
||||
catalog: ModelCatalogEntry[];
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
buildAllowedModelSet,
|
||||
modelKey,
|
||||
parseModelRef,
|
||||
resolveConfiguredModelRef,
|
||||
} from "../agents/model-selection.js";
|
||||
import {
|
||||
queueEmbeddedPiMessage,
|
||||
@@ -168,8 +169,12 @@ export async function getReplyFromConfig(
|
||||
const agentCfg = cfg.agent;
|
||||
const sessionCfg = cfg.session;
|
||||
|
||||
const defaultProvider = agentCfg?.provider?.trim() || DEFAULT_PROVIDER;
|
||||
const defaultModel = agentCfg?.model?.trim() || DEFAULT_MODEL;
|
||||
const { provider: defaultProvider, model: defaultModel } =
|
||||
resolveConfiguredModelRef({
|
||||
cfg,
|
||||
defaultProvider: DEFAULT_PROVIDER,
|
||||
defaultModel: DEFAULT_MODEL,
|
||||
});
|
||||
let provider = defaultProvider;
|
||||
let model = defaultModel;
|
||||
let contextTokens =
|
||||
@@ -1048,8 +1053,7 @@ export async function getReplyFromConfig(
|
||||
|
||||
if (sessionStore && sessionKey) {
|
||||
const usage = runResult.meta.agentMeta?.usage;
|
||||
const modelUsed =
|
||||
runResult.meta.agentMeta?.model ?? agentCfg?.model ?? DEFAULT_MODEL;
|
||||
const modelUsed = runResult.meta.agentMeta?.model ?? defaultModel;
|
||||
const contextTokensUsed =
|
||||
agentCfg?.contextTokens ??
|
||||
lookupContextTokens(modelUsed) ??
|
||||
|
||||
@@ -2,7 +2,12 @@ import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
|
||||
import { lookupContextTokens } from "../agents/context.js";
|
||||
import { DEFAULT_CONTEXT_TOKENS, DEFAULT_MODEL } from "../agents/defaults.js";
|
||||
import {
|
||||
DEFAULT_CONTEXT_TOKENS,
|
||||
DEFAULT_MODEL,
|
||||
DEFAULT_PROVIDER,
|
||||
} from "../agents/defaults.js";
|
||||
import { resolveConfiguredModelRef } from "../agents/model-selection.js";
|
||||
import {
|
||||
derivePromptTokens,
|
||||
normalizeUsage,
|
||||
@@ -129,7 +134,12 @@ const readUsageFromSessionLog = (
|
||||
export function buildStatusMessage(args: StatusArgs): string {
|
||||
const now = args.now ?? Date.now();
|
||||
const entry = args.sessionEntry;
|
||||
let model = entry?.model ?? args.agent?.model ?? DEFAULT_MODEL;
|
||||
const resolved = resolveConfiguredModelRef({
|
||||
cfg: { agent: args.agent ?? {} },
|
||||
defaultProvider: DEFAULT_PROVIDER,
|
||||
defaultModel: DEFAULT_MODEL,
|
||||
});
|
||||
let model = entry?.model ?? resolved.model ?? DEFAULT_MODEL;
|
||||
let contextTokens =
|
||||
entry?.contextTokens ??
|
||||
args.agent?.contextTokens ??
|
||||
|
||||
@@ -47,12 +47,14 @@ function mockConfig(
|
||||
home: string,
|
||||
storePath: string,
|
||||
routingOverrides?: Partial<NonNullable<ClawdisConfig["routing"]>>,
|
||||
agentOverrides?: Partial<NonNullable<ClawdisConfig["agent"]>>,
|
||||
) {
|
||||
configSpy.mockReturnValue({
|
||||
agent: {
|
||||
provider: "anthropic",
|
||||
model: "claude-opus-4-5",
|
||||
workspace: path.join(home, "clawd"),
|
||||
...agentOverrides,
|
||||
},
|
||||
session: { store: storePath, mainKey: "main" },
|
||||
routing: routingOverrides ? { ...routingOverrides } : undefined,
|
||||
@@ -141,6 +143,22 @@ describe("agentCommand", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("resolves provider from agent.model when prefixed", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const store = path.join(home, "sessions.json");
|
||||
mockConfig(home, store, undefined, {
|
||||
provider: "openai",
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
});
|
||||
|
||||
await agentCommand({ message: "hi", to: "+1555" }, runtime);
|
||||
|
||||
const callArgs = vi.mocked(runEmbeddedPiAgent).mock.calls.at(-1)?.[0];
|
||||
expect(callArgs?.provider).toBe("anthropic");
|
||||
expect(callArgs?.model).toBe("claude-opus-4-5");
|
||||
});
|
||||
});
|
||||
|
||||
it("prints JSON payload when requested", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||
|
||||
@@ -6,7 +6,11 @@ import {
|
||||
DEFAULT_PROVIDER,
|
||||
} from "../agents/defaults.js";
|
||||
import { loadModelCatalog } from "../agents/model-catalog.js";
|
||||
import { buildAllowedModelSet, modelKey } from "../agents/model-selection.js";
|
||||
import {
|
||||
buildAllowedModelSet,
|
||||
modelKey,
|
||||
resolveConfiguredModelRef,
|
||||
} from "../agents/model-selection.js";
|
||||
import { runEmbeddedPiAgent } from "../agents/pi-embedded.js";
|
||||
import { buildWorkspaceSkillSnapshot } from "../agents/skills.js";
|
||||
import {
|
||||
@@ -247,8 +251,12 @@ export async function agentCommand(
|
||||
await saveSessionStore(storePath, sessionStore);
|
||||
}
|
||||
|
||||
const defaultProvider = agentCfg?.provider?.trim() || DEFAULT_PROVIDER;
|
||||
const defaultModel = agentCfg?.model?.trim() || DEFAULT_MODEL;
|
||||
const { provider: defaultProvider, model: defaultModel } =
|
||||
resolveConfiguredModelRef({
|
||||
cfg,
|
||||
defaultProvider: DEFAULT_PROVIDER,
|
||||
defaultModel: DEFAULT_MODEL,
|
||||
});
|
||||
let provider = defaultProvider;
|
||||
let model = defaultModel;
|
||||
const hasAllowlist = (agentCfg?.allowedModels?.length ?? 0) > 0;
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import chalk from "chalk";
|
||||
|
||||
import { lookupContextTokens } from "../agents/context.js";
|
||||
import { DEFAULT_CONTEXT_TOKENS, DEFAULT_MODEL } from "../agents/defaults.js";
|
||||
import {
|
||||
DEFAULT_CONTEXT_TOKENS,
|
||||
DEFAULT_MODEL,
|
||||
DEFAULT_PROVIDER,
|
||||
} from "../agents/defaults.js";
|
||||
import { resolveConfiguredModelRef } from "../agents/model-selection.js";
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import {
|
||||
loadSessionStore,
|
||||
@@ -151,11 +156,16 @@ export async function sessionsCommand(
|
||||
runtime: RuntimeEnv,
|
||||
) {
|
||||
const cfg = loadConfig();
|
||||
const resolved = resolveConfiguredModelRef({
|
||||
cfg,
|
||||
defaultProvider: DEFAULT_PROVIDER,
|
||||
defaultModel: DEFAULT_MODEL,
|
||||
});
|
||||
const configContextTokens =
|
||||
cfg.agent?.contextTokens ??
|
||||
lookupContextTokens(cfg.agent?.model) ??
|
||||
lookupContextTokens(resolved.model) ??
|
||||
DEFAULT_CONTEXT_TOKENS;
|
||||
const configModel = cfg.agent?.model ?? DEFAULT_MODEL;
|
||||
const configModel = resolved.model ?? DEFAULT_MODEL;
|
||||
const storePath = resolveStorePath(opts.store ?? cfg.session?.store);
|
||||
const store = loadSessionStore(storePath);
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { lookupContextTokens } from "../agents/context.js";
|
||||
import { DEFAULT_CONTEXT_TOKENS, DEFAULT_MODEL } from "../agents/defaults.js";
|
||||
import {
|
||||
DEFAULT_CONTEXT_TOKENS,
|
||||
DEFAULT_MODEL,
|
||||
DEFAULT_PROVIDER,
|
||||
} from "../agents/defaults.js";
|
||||
import { resolveConfiguredModelRef } from "../agents/model-selection.js";
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import {
|
||||
loadSessionStore,
|
||||
@@ -60,7 +65,12 @@ export async function getStatusSummary(): Promise<StatusSummary> {
|
||||
const providerSummary = await buildProviderSummary(cfg);
|
||||
const queuedSystemEvents = peekSystemEvents();
|
||||
|
||||
const configModel = cfg.agent?.model ?? DEFAULT_MODEL;
|
||||
const resolved = resolveConfiguredModelRef({
|
||||
cfg,
|
||||
defaultProvider: DEFAULT_PROVIDER,
|
||||
defaultModel: DEFAULT_MODEL,
|
||||
});
|
||||
const configModel = resolved.model ?? DEFAULT_MODEL;
|
||||
const configContextTokens =
|
||||
cfg.agent?.contextTokens ??
|
||||
lookupContextTokens(configModel) ??
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
DEFAULT_MODEL,
|
||||
DEFAULT_PROVIDER,
|
||||
} from "../agents/defaults.js";
|
||||
import { resolveConfiguredModelRef } from "../agents/model-selection.js";
|
||||
import { runEmbeddedPiAgent } from "../agents/pi-embedded.js";
|
||||
import { buildWorkspaceSkillSnapshot } from "../agents/skills.js";
|
||||
import {
|
||||
@@ -154,8 +155,11 @@ export async function runCronIsolatedAgentTurn(params: {
|
||||
});
|
||||
const workspaceDir = workspace.dir;
|
||||
|
||||
const provider = agentCfg?.provider?.trim() || DEFAULT_PROVIDER;
|
||||
const model = agentCfg?.model?.trim() || DEFAULT_MODEL;
|
||||
const { provider, model } = resolveConfiguredModelRef({
|
||||
cfg: params.cfg,
|
||||
defaultProvider: DEFAULT_PROVIDER,
|
||||
defaultModel: DEFAULT_MODEL,
|
||||
});
|
||||
const now = Date.now();
|
||||
const cronSession = resolveCronSession({
|
||||
cfg: params.cfg,
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
type ModelCatalogEntry,
|
||||
resetModelCatalogCacheForTest,
|
||||
} from "../agents/model-catalog.js";
|
||||
import { resolveConfiguredModelRef } from "../agents/model-selection.js";
|
||||
import { installSkill } from "../agents/skills-install.js";
|
||||
import { buildWorkspaceSkillStatus } from "../agents/skills-status.js";
|
||||
import { DEFAULT_AGENT_WORKSPACE_DIR } from "../agents/workspace.js";
|
||||
@@ -865,12 +866,16 @@ function classifySessionKey(key: string): GatewaySessionRow["kind"] {
|
||||
}
|
||||
|
||||
function getSessionDefaults(cfg: ClawdisConfig): GatewaySessionsDefaults {
|
||||
const model = cfg.agent?.model ?? DEFAULT_MODEL;
|
||||
const resolved = resolveConfiguredModelRef({
|
||||
cfg,
|
||||
defaultProvider: DEFAULT_PROVIDER,
|
||||
defaultModel: DEFAULT_MODEL,
|
||||
});
|
||||
const contextTokens =
|
||||
cfg.agent?.contextTokens ??
|
||||
lookupContextTokens(model) ??
|
||||
lookupContextTokens(resolved.model) ??
|
||||
DEFAULT_CONTEXT_TOKENS;
|
||||
return { model: model ?? null, contextTokens: contextTokens ?? null };
|
||||
return { model: resolved.model ?? null, contextTokens: contextTokens ?? null };
|
||||
}
|
||||
|
||||
function listSessionsFromStore(params: {
|
||||
@@ -5858,8 +5863,12 @@ export async function startGatewayServer(
|
||||
});
|
||||
});
|
||||
|
||||
const agentProvider = cfgAtStart.agent?.provider?.trim() || DEFAULT_PROVIDER;
|
||||
const agentModel = cfgAtStart.agent?.model?.trim() || DEFAULT_MODEL;
|
||||
const { provider: agentProvider, model: agentModel } =
|
||||
resolveConfiguredModelRef({
|
||||
cfg: cfgAtStart,
|
||||
defaultProvider: DEFAULT_PROVIDER,
|
||||
defaultModel: DEFAULT_MODEL,
|
||||
});
|
||||
log.info(`agent model: ${agentProvider}/${agentModel}`);
|
||||
log.info(`listening on ws://${bindHost}:${port} (PID ${process.pid})`);
|
||||
log.info(`log file: ${getResolvedLoggerSettings().file}`);
|
||||
|
||||
Reference in New Issue
Block a user