diff --git a/src/router.ts b/src/router.ts index 599ec9bb9..260617463 100644 --- a/src/router.ts +++ b/src/router.ts @@ -605,6 +605,15 @@ function routeModelInternal( throw new Error(`No provider configured for model: ${modelId}`); } if (hasOwnProvider(config.providers, provName)) { + const conflictingDefault = activeProviderEntries(config) + .find(([candidateName, candidate]) => candidateName !== provName && candidate.defaultModel === modelId); + if (conflictingDefault) { + throw new Error( + `Ambiguous model selector ${modelId}: it is both the default model for provider ` + + `${conflictingDefault[0]} and a ${provName} provider namespace. Use an encoded ` + + `${conflictingDefault[0]}/ selector to choose the default-model provider explicitly.`, + ); + } const prov = config.providers[provName]; if (prov.disabled === true) throw new Error(`Provider is disabled: ${provName}`); const known = knownModelIdsForProvider(provName, prov); diff --git a/tests/commandcode-provider.test.ts b/tests/commandcode-provider.test.ts index c1daadbc7..730476b0e 100644 --- a/tests/commandcode-provider.test.ts +++ b/tests/commandcode-provider.test.ts @@ -170,6 +170,28 @@ describe("Command Code provider", () => { expect(body.parallel_tool_calls).toBe(true); }); + test("rejects an ambiguous native default instead of routing it through DeepSeek", () => { + const config = commandcodeConfig({ + defaultModel: "deepseek/deepseek-v4-flash", + models: ["deepseek/deepseek-v4-flash"], + }); + config.providers.deepseek = { + adapter: "openai-chat", + baseUrl: "https://api.deepseek.com", + authMode: "key", + apiKey: "deepseek-test-key", + defaultModel: "deepseek-v4-flash", + models: ["deepseek-v4-flash"], + }; + + expect(() => routeModel(config, "deepseek/deepseek-v4-flash")) + .toThrow("Ambiguous model selector deepseek/deepseek-v4-flash"); + expect(routeModel(config, "commandcode/deepseek-deepseek-v4-flash")).toMatchObject({ + providerName: "commandcode", + modelId: "deepseek/deepseek-v4-flash", + }); + }); + test("discovers the live catalog with context windows and preserves slash ids", async () => { globalThis.fetch = (async (input, init) => { expect(String(input)).toBe("https://api.commandcode.ai/provider/v1/models");