Skip to content

Commit b6c454b

Browse files
byapparovclaude
andcommitted
fix(acp): make the no-provider default-model error actionable (#40 review)
Covers the ACP no-provider path and the GLM Coding Plan variants with tests, and isolates the no-network test's config and cache directories. Co-Authored-By: Claude Opus 5.5 <[email protected]>
1 parent 423b6d9 commit b6c454b

4 files changed

Lines changed: 68 additions & 3 deletions

File tree

‎packages/cli/src/acp/agent.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1525,7 +1525,7 @@ export namespace ACP {
15251525
}
15261526
}
15271527

1528-
async function defaultModel(config: ACPConfig, cwd?: string) {
1528+
export async function defaultModel(config: ACPConfig, cwd?: string) {
15291529
const sdk = config.sdk
15301530
const configured = config.defaultModel
15311531
if (configured) return configured
@@ -1574,7 +1574,9 @@ export namespace ACP {
15741574

15751575
if (specified) return specified
15761576

1577-
throw new Error("no providers found")
1577+
throw new Error(
1578+
'No providers found. Run `aictrl auth login`, set a provider API key, or define a provider under "provider" in aictrl.json.',
1579+
)
15781580
}
15791581

15801582
function parseUri(

‎packages/cli/test/acp/agent-interface.test.ts‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,35 @@ describe("acp.agent interface compliance", () => {
4949
}
5050
})
5151
})
52+
53+
describe("acp default model", () => {
54+
type Sdk = Parameters<typeof ACP.defaultModel>[0]["sdk"]
55+
56+
// A client whose server reports no config model and the given providers.
57+
function sdkWith(providers: unknown[]) {
58+
return {
59+
config: {
60+
get: async () => ({ data: {} }),
61+
providers: async () => ({ data: { providers } }),
62+
},
63+
} as unknown as Sdk
64+
}
65+
66+
test("fails with an actionable error when no provider is configured", async () => {
67+
const error = await ACP.defaultModel({ sdk: sdkWith([]) }, "/tmp").catch((e: Error) => e)
68+
expect(error).toBeInstanceOf(Error)
69+
expect((error as Error).message).toContain("aictrl auth login")
70+
expect((error as Error).message).toContain('"provider" in aictrl.json')
71+
})
72+
73+
test("resolves a configured provider instead of a hard-coded model", async () => {
74+
const anthropic = {
75+
id: "anthropic",
76+
models: { "claude-sonnet-4-5": { id: "claude-sonnet-4-5", providerID: "anthropic" } },
77+
}
78+
expect(await ACP.defaultModel({ sdk: sdkWith([anthropic]) }, "/tmp")).toEqual({
79+
providerID: "anthropic",
80+
modelID: "claude-sonnet-4-5",
81+
})
82+
})
83+
})

‎packages/cli/test/cli/no-network-startup.test.ts‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,17 @@ async function runGuarded(entry: string, args: string[], env: Record<string, str
1212
await using tmp = await tmpdir()
1313
const proc = Bun.spawn(["bun", "--preload", guard, "--conditions=browser", entry, ...args], {
1414
cwd: tmp.path,
15-
env: { ...process.env, ...env },
15+
// Config, auth, cache and state live under the fresh tmpdir, so nothing shared can change what loads.
16+
env: {
17+
...process.env,
18+
XDG_DATA_HOME: path.join(tmp.path, "data"),
19+
XDG_CACHE_HOME: path.join(tmp.path, "cache"),
20+
XDG_CONFIG_HOME: path.join(tmp.path, "config"),
21+
XDG_STATE_HOME: path.join(tmp.path, "state"),
22+
AICTRL_TEST_HOME: tmp.path,
23+
AICTRL_TEST_MANAGED_CONFIG_DIR: path.join(tmp.path, "managed"),
24+
...env,
25+
},
1626
stdout: "pipe",
1727
stderr: "pipe",
1828
})

‎packages/cli/test/provider/catalog.test.ts‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,27 @@ describe("provider registry", () => {
148148
})
149149
})
150150

151+
test("GLM Coding Plan models keep their forced reasoning-effort variants", async () => {
152+
await using tmp = await tmpdir({ config: {} })
153+
await Instance.provide({
154+
directory: tmp.path,
155+
init: async () => {
156+
Env.set("ZHIPU_API_KEY", "key")
157+
},
158+
fn: async () => {
159+
const models = (await Provider.list())["zai-coding-plan"].models
160+
expect(models["glm-5.2"].variants).toEqual({
161+
low: { reasoningEffort: "high" },
162+
medium: { reasoningEffort: "high" },
163+
high: { reasoningEffort: "high" },
164+
xhigh: { reasoningEffort: "max" },
165+
max: { reasoningEffort: "max" },
166+
})
167+
expect(models["glm-5.3"].variants?.["max"]).toEqual({ reasoningEffort: "max" })
168+
},
169+
})
170+
})
171+
151172
test("an openai-compatible provider defined in config works without the catalog", async () => {
152173
await using tmp = await tmpdir({
153174
config: {

0 commit comments

Comments
 (0)