diff --git a/src/selfhost/ai.ts b/src/selfhost/ai.ts index bba446bf46..284e7a2d5a 100644 --- a/src/selfhost/ai.ts +++ b/src/selfhost/ai.ts @@ -436,13 +436,16 @@ export function resolveRequiredCliProviders(env: Record): SelfHostAi | undefined { const providers = buildProviders(env); if (providers.length === 0) return undefined; - if (providers.length === 1) return providers[0]?.ai; return routeProviders(providers); } diff --git a/test/unit/selfhost-ai.test.ts b/test/unit/selfhost-ai.test.ts index 3781675a9a..52e1e2b391 100644 --- a/test/unit/selfhost-ai.test.ts +++ b/test/unit/selfhost-ai.test.ts @@ -206,6 +206,20 @@ describe("routeProviders (#dual-ai-combiner — address one provider by name for const ai = createSelfHostAi({ AI_PROVIDER: "anthropic,ollama", ANTHROPIC_API_KEY: "sk-ant", AI_BASE_URL: "http://o/v1" }); expect(typeof ai?.run).toBe("function"); }); + + it("createSelfHostAi routes a SINGLE provider through the router too — a name address yields the provider default, never `--model ` (#1610)", async () => { + // Regression (#1610): a single-provider self-host returned env.AI as the BARE provider, so the reviewer plan's + // name address ({ model: "openai-compatible" } — or "claude-code") reached it as a model id. `claude --model + // claude-code` 404'd and broke EVERY review. The router must strip the name to the provider's own default. + let sentModel = ""; + vi.stubGlobal("fetch", vi.fn(async (_u: string, init: { body: string }) => { + sentModel = (JSON.parse(init.body) as { model: string }).model; + return new Response(JSON.stringify({ choices: [{ message: { content: "ok" } }] }), { status: 200 }); + })); + const ai = createSelfHostAi({ AI_PROVIDER: "openai-compatible", AI_BASE_URL: "http://o/v1" }); + await ai?.run("openai-compatible", { prompt: "x" }); // the single-provider reviewer-plan address IS the provider name + expect(sentModel).toBe("llama3.1"); // resolveModel(undefined, "", "llama3.1") — NOT the literal "openai-compatible" + }); }); describe("resolveProviderNames + resolveAiReviewerPlan (#dual-ai-combiner)", () => {