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