refactor(config): drop agent.provider
This commit is contained in:
@@ -117,8 +117,7 @@ Example:
|
||||
{
|
||||
logging: { level: "info" },
|
||||
agent: {
|
||||
provider: "anthropic",
|
||||
model: "claude-opus-4-5",
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: "~/clawd",
|
||||
thinkingDefault: "high",
|
||||
timeoutSeconds: 1800,
|
||||
|
||||
@@ -113,7 +113,7 @@ Controls inbound/outbound prefixes and timestamps.
|
||||
|
||||
### `agent`
|
||||
|
||||
Controls the embedded agent runtime (provider/model/thinking/verbose/timeouts).
|
||||
Controls the embedded agent runtime (model/thinking/verbose/timeouts).
|
||||
`allowedModels` lets `/model` list/filter and enforce a per-session allowlist
|
||||
(omit to show the full catalog).
|
||||
|
||||
@@ -141,8 +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.model` should be set as `provider/model` (e.g. `anthropic/claude-opus-4-5`).
|
||||
If you omit the provider, CLAWDIS currently assumes `anthropic` as a temporary
|
||||
deprecation fallback.
|
||||
|
||||
`agent.bash` configures background bash defaults:
|
||||
- `backgroundMs`: time before auto-background (ms, default 20000)
|
||||
@@ -165,11 +166,11 @@ When `models.providers` is present, Clawdis writes/merges a `models.json` into
|
||||
- default behavior: **merge** (keeps existing providers, overrides on name)
|
||||
- set `models.mode: "replace"` to overwrite the file contents
|
||||
|
||||
Select the model via `agent.provider` + `agent.model`.
|
||||
Select the model via `agent.model` (provider/model).
|
||||
|
||||
```json5
|
||||
{
|
||||
agent: { provider: "custom-proxy", model: "llama-3.1-8b" },
|
||||
agent: { model: "custom-proxy/llama-3.1-8b" },
|
||||
models: {
|
||||
mode: "merge",
|
||||
providers: {
|
||||
|
||||
@@ -47,14 +47,14 @@ Offer an “API key” option, but for now it is **instructions only**:
|
||||
|
||||
Note: environment variables are often confusing when the Gateway is launched by a GUI app (launchd environment != your shell).
|
||||
|
||||
### Provider/model safety rule
|
||||
### Model safety rule
|
||||
|
||||
Clawdis should **always pass** `--provider` and `--model` when invoking the embedded agent (don’t rely on defaults).
|
||||
Clawdis should **always pass** `--model` when invoking the embedded agent (don’t rely on defaults).
|
||||
|
||||
Example (CLI):
|
||||
|
||||
```bash
|
||||
clawdis agent --mode rpc --provider anthropic --model claude-opus-4-5 "<message>"
|
||||
clawdis agent --mode rpc --model anthropic/claude-opus-4-5 "<message>"
|
||||
```
|
||||
|
||||
If the user skips auth, onboarding should be clear: the agent likely won’t respond until auth is configured.
|
||||
|
||||
53
src/agents/model-selection.test.ts
Normal file
53
src/agents/model-selection.test.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import type { ClawdisConfig } from "../config/config.js";
|
||||
import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "./defaults.js";
|
||||
import { resolveConfiguredModelRef } from "./model-selection.js";
|
||||
|
||||
describe("resolveConfiguredModelRef", () => {
|
||||
it("parses provider/model from agent.model", () => {
|
||||
const cfg = {
|
||||
agent: { model: "openai/gpt-4.1-mini" },
|
||||
} satisfies ClawdisConfig;
|
||||
|
||||
const resolved = resolveConfiguredModelRef({
|
||||
cfg,
|
||||
defaultProvider: DEFAULT_PROVIDER,
|
||||
defaultModel: DEFAULT_MODEL,
|
||||
});
|
||||
|
||||
expect(resolved).toEqual({ provider: "openai", model: "gpt-4.1-mini" });
|
||||
});
|
||||
|
||||
it("falls back to default provider when agent.model omits it", () => {
|
||||
const cfg = {
|
||||
agent: { model: "claude-opus-4-5" },
|
||||
} satisfies ClawdisConfig;
|
||||
|
||||
const resolved = resolveConfiguredModelRef({
|
||||
cfg,
|
||||
defaultProvider: DEFAULT_PROVIDER,
|
||||
defaultModel: DEFAULT_MODEL,
|
||||
});
|
||||
|
||||
expect(resolved).toEqual({
|
||||
provider: DEFAULT_PROVIDER,
|
||||
model: "claude-opus-4-5",
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to defaults when agent.model is missing", () => {
|
||||
const cfg = {} satisfies ClawdisConfig;
|
||||
|
||||
const resolved = resolveConfiguredModelRef({
|
||||
cfg,
|
||||
defaultProvider: DEFAULT_PROVIDER,
|
||||
defaultModel: DEFAULT_MODEL,
|
||||
});
|
||||
|
||||
expect(resolved).toEqual({
|
||||
provider: DEFAULT_PROVIDER,
|
||||
model: DEFAULT_MODEL,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -31,15 +31,17 @@ export function resolveConfiguredModelRef(params: {
|
||||
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 };
|
||||
const trimmed = rawModel.trim();
|
||||
if (trimmed.includes("/")) {
|
||||
const parsed = parseModelRef(trimmed, params.defaultProvider);
|
||||
if (parsed) return parsed;
|
||||
}
|
||||
// TODO(steipete): drop this fallback once provider-less agent.model is fully deprecated.
|
||||
return { provider: params.defaultProvider, model: trimmed };
|
||||
}
|
||||
return { provider: providerFallback, model: params.defaultModel };
|
||||
return { provider: params.defaultProvider, model: params.defaultModel };
|
||||
}
|
||||
|
||||
export function buildAllowedModelSet(params: {
|
||||
|
||||
@@ -102,8 +102,7 @@ describe("directive parsing", () => {
|
||||
{},
|
||||
{
|
||||
agent: {
|
||||
provider: "anthropic",
|
||||
model: "claude-opus-4-5",
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: path.join(home, "clawd"),
|
||||
},
|
||||
routing: {
|
||||
@@ -130,8 +129,7 @@ describe("directive parsing", () => {
|
||||
{},
|
||||
{
|
||||
agent: {
|
||||
provider: "anthropic",
|
||||
model: "claude-opus-4-5",
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: path.join(home, "clawd"),
|
||||
},
|
||||
session: { store: path.join(home, "sessions.json") },
|
||||
@@ -183,8 +181,7 @@ describe("directive parsing", () => {
|
||||
{},
|
||||
{
|
||||
agent: {
|
||||
provider: "anthropic",
|
||||
model: "claude-opus-4-5",
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: path.join(home, "clawd"),
|
||||
},
|
||||
routing: {
|
||||
@@ -245,8 +242,7 @@ describe("directive parsing", () => {
|
||||
{},
|
||||
{
|
||||
agent: {
|
||||
provider: "anthropic",
|
||||
model: "claude-opus-4-5",
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: path.join(home, "clawd"),
|
||||
},
|
||||
routing: {
|
||||
@@ -274,8 +270,7 @@ describe("directive parsing", () => {
|
||||
{},
|
||||
{
|
||||
agent: {
|
||||
provider: "anthropic",
|
||||
model: "claude-opus-4-5",
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: path.join(home, "clawd"),
|
||||
allowedModels: ["anthropic/claude-opus-4-5", "openai/gpt-4.1-mini"],
|
||||
},
|
||||
@@ -301,8 +296,7 @@ describe("directive parsing", () => {
|
||||
{},
|
||||
{
|
||||
agent: {
|
||||
provider: "anthropic",
|
||||
model: "claude-opus-4-5",
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: path.join(home, "clawd"),
|
||||
allowedModels: ["openai/gpt-4.1-mini"],
|
||||
},
|
||||
@@ -340,8 +334,7 @@ describe("directive parsing", () => {
|
||||
{},
|
||||
{
|
||||
agent: {
|
||||
provider: "anthropic",
|
||||
model: "claude-opus-4-5",
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: path.join(home, "clawd"),
|
||||
allowedModels: ["openai/gpt-4.1-mini"],
|
||||
},
|
||||
|
||||
@@ -35,8 +35,7 @@ async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
||||
function makeCfg(home: string) {
|
||||
return {
|
||||
agent: {
|
||||
provider: "anthropic",
|
||||
model: "claude-opus-4-5",
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: join(home, "clawd"),
|
||||
},
|
||||
routing: {
|
||||
@@ -168,8 +167,7 @@ describe("trigger handling", () => {
|
||||
{},
|
||||
{
|
||||
agent: {
|
||||
provider: "anthropic",
|
||||
model: "claude-opus-4-5",
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: join(home, "clawd"),
|
||||
},
|
||||
routing: {
|
||||
@@ -210,8 +208,7 @@ describe("trigger handling", () => {
|
||||
{},
|
||||
{
|
||||
agent: {
|
||||
provider: "anthropic",
|
||||
model: "claude-opus-4-5",
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: join(home, "clawd"),
|
||||
},
|
||||
routing: {
|
||||
@@ -250,8 +247,7 @@ describe("trigger handling", () => {
|
||||
{},
|
||||
{
|
||||
agent: {
|
||||
provider: "anthropic",
|
||||
model: "claude-opus-4-5",
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: join(home, "clawd"),
|
||||
},
|
||||
routing: {
|
||||
|
||||
@@ -730,7 +730,6 @@ export async function getReplyFromConfig(
|
||||
const heartbeatSeconds = resolveHeartbeatSeconds(cfg, undefined);
|
||||
const statusText = buildStatusMessage({
|
||||
agent: {
|
||||
provider,
|
||||
model,
|
||||
contextTokens,
|
||||
thinkingDefault: agentCfg?.thinkingDefault,
|
||||
|
||||
@@ -11,7 +11,10 @@ afterEach(() => {
|
||||
describe("buildStatusMessage", () => {
|
||||
it("summarizes agent readiness and context usage", () => {
|
||||
const text = buildStatusMessage({
|
||||
agent: { provider: "anthropic", model: "pi:opus", contextTokens: 32_000 },
|
||||
agent: {
|
||||
model: "anthropic/pi:opus",
|
||||
contextTokens: 32_000,
|
||||
},
|
||||
sessionEntry: {
|
||||
sessionId: "abc",
|
||||
updatedAt: 0,
|
||||
@@ -112,8 +115,7 @@ describe("buildStatusMessage", () => {
|
||||
|
||||
const text = buildStatusMessageDynamic({
|
||||
agent: {
|
||||
provider: "anthropic",
|
||||
model: "claude-opus-4-5",
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
contextTokens: 32_000,
|
||||
},
|
||||
sessionEntry: {
|
||||
|
||||
@@ -202,11 +202,9 @@ export function buildStatusMessage(args: StatusArgs): string {
|
||||
|
||||
const optionsLine = `Options: thinking=${thinkLevel} | verbose=${verboseLevel} (set with /think <level>, /verbose on|off, /model <id>)`;
|
||||
|
||||
const modelLabel = args.agent?.provider?.trim()
|
||||
? `${args.agent.provider}/${args.agent?.model ?? model}`
|
||||
: model
|
||||
? model
|
||||
: "unknown";
|
||||
const modelLabel = model
|
||||
? `${resolved.provider}/${model}`
|
||||
: "unknown";
|
||||
|
||||
const agentLine = `Agent: embedded pi • ${modelLabel}`;
|
||||
|
||||
|
||||
@@ -51,8 +51,7 @@ function mockConfig(
|
||||
) {
|
||||
configSpy.mockReturnValue({
|
||||
agent: {
|
||||
provider: "anthropic",
|
||||
model: "claude-opus-4-5",
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: path.join(home, "clawd"),
|
||||
...agentOverrides,
|
||||
},
|
||||
@@ -143,19 +142,18 @@ describe("agentCommand", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("resolves provider from agent.model when prefixed", async () => {
|
||||
it("uses provider/model from agent.model", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const store = path.join(home, "sessions.json");
|
||||
mockConfig(home, store, undefined, {
|
||||
provider: "openai",
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
model: "openai/gpt-4.1-mini",
|
||||
});
|
||||
|
||||
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");
|
||||
expect(callArgs?.provider).toBe("openai");
|
||||
expect(callArgs?.model).toBe("gpt-4.1-mini");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -306,8 +306,7 @@ export type ClawdisConfig = {
|
||||
models?: ModelsConfig;
|
||||
agent?: {
|
||||
/** Provider id, e.g. "anthropic" or "openai" (pi-ai catalog). */
|
||||
provider?: string;
|
||||
/** Model id within provider, e.g. "claude-opus-4-5". */
|
||||
/** Model id (provider/model), e.g. "anthropic/claude-opus-4-5". */
|
||||
model?: string;
|
||||
/** Agent working directory (preferred). Used as the default cwd for agent runs. */
|
||||
workspace?: string;
|
||||
@@ -565,7 +564,6 @@ const ClawdisSchema = z.object({
|
||||
models: ModelsConfigSchema,
|
||||
agent: z
|
||||
.object({
|
||||
provider: z.string().optional(),
|
||||
model: z.string().optional(),
|
||||
workspace: z.string().optional(),
|
||||
allowedModels: z.array(z.string()).optional(),
|
||||
|
||||
@@ -53,8 +53,7 @@ async function writeSessionStore(home: string) {
|
||||
function makeCfg(home: string, storePath: string): ClawdisConfig {
|
||||
return {
|
||||
agent: {
|
||||
provider: "anthropic",
|
||||
model: "claude-opus-4-5",
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: path.join(home, "clawd"),
|
||||
},
|
||||
session: { store: storePath, mainKey: "main" },
|
||||
|
||||
@@ -191,8 +191,7 @@ vi.mock("../config/config.js", () => {
|
||||
CONFIG_PATH_CLAWDIS: resolveConfigPath(),
|
||||
loadConfig: () => ({
|
||||
agent: {
|
||||
provider: "anthropic",
|
||||
model: "claude-opus-4-5",
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: path.join(os.tmpdir(), "clawd-gateway-test"),
|
||||
},
|
||||
routing: {
|
||||
|
||||
Reference in New Issue
Block a user