From 911db0000facc0f100984bbad8efea1febd263a0 Mon Sep 17 00:00:00 2001 From: Sam Biggins Date: Sun, 29 Mar 2026 17:48:31 +0200 Subject: [PATCH] fix: resolve --model provider/id to correct provider when ambiguous (#563) fixes #560 When the CLI input has provider/id format (e.g. zai/glm-5), the exact match now checks decomposed provider+id first (provider=zai, id=glm-5) before falling back to flat model.id string match. This prevents vercel-ai-gateway's 'zai/glm-5' model from winning over the zai provider's 'glm-5' model due to Array.find catalog ordering. Keeps getAll() rather than switching to getAvailable() to preserve the --api-key ephemeral flow, where auth is injected after resolution. --- packages/coding-agent/CHANGELOG.md | 4 ++ .../coding-agent/src/config/model-resolver.ts | 21 +++++++-- .../coding-agent/test/model-resolver.test.ts | 43 +++++++++++++++++++ 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 56db5265c..089cd3e81 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed `--model provider/id` resolving to wrong provider when model ID exists in multiple catalogs ([#560](https://github.com/can1357/oh-my-pi/issues/560)) + ## [13.16.4] - 2026-03-28 ### Changed diff --git a/packages/coding-agent/src/config/model-resolver.ts b/packages/coding-agent/src/config/model-resolver.ts index 82d20b3b5..f4c1c3f7c 100644 --- a/packages/coding-agent/src/config/model-resolver.ts +++ b/packages/coding-agent/src/config/model-resolver.ts @@ -730,9 +730,24 @@ export function resolveCliModel(options: { if (!provider) { const lower = cliModel.toLowerCase(); - const exact = availableModels.find( - model => model.id.toLowerCase() === lower || `${model.provider}/${model.id}`.toLowerCase() === lower, - ); + // When input has provider/id format (e.g. "zai/glm-5"), prefer decomposed + // provider+id match over flat id match. Without this, a model with id + // "zai/glm-5" on provider "vercel-ai-gateway" wins over provider "zai" + // with id "glm-5", because Array.find returns the first catalog hit. + const slashIdx = lower.indexOf("/"); + let exact: (typeof availableModels)[number] | undefined; + if (slashIdx !== -1) { + const prefix = lower.substring(0, slashIdx); + const suffix = lower.substring(slashIdx + 1); + exact = availableModels.find( + model => model.provider.toLowerCase() === prefix && model.id.toLowerCase() === suffix, + ); + } + if (!exact) { + exact = availableModels.find( + model => model.id.toLowerCase() === lower || `${model.provider}/${model.id}`.toLowerCase() === lower, + ); + } if (exact) { return { model: exact, warning: undefined, thinkingLevel: undefined, error: undefined }; } diff --git a/packages/coding-agent/test/model-resolver.test.ts b/packages/coding-agent/test/model-resolver.test.ts index 10b0996e3..41ed6b689 100644 --- a/packages/coding-agent/test/model-resolver.test.ts +++ b/packages/coding-agent/test/model-resolver.test.ts @@ -551,6 +551,49 @@ describe("resolveCliModel", () => { expect(result.model?.provider).toBe("openrouter"); expect(result.model?.id).toBe("qwen/qwen3-coder:exacto"); }); + + test("prefers decomposed provider+id over flat id match when ambiguous", () => { + // Simulates the zai/glm-5 bug: vercel-ai-gateway has id="zai/glm-5", + // zai has id="glm-5". Input "zai/glm-5" should resolve to provider=zai. + const ambiguousModels: Model<"anthropic-messages">[] = [ + { + id: "zai/glm-5", + name: "GLM-5 (Vercel)", + api: "anthropic-messages", + provider: "vercel-ai-gateway", + baseUrl: "https://vercel.ai", + reasoning: false, + input: ["text"], + cost: { input: 1, output: 2, cacheRead: 0.1, cacheWrite: 1 }, + contextWindow: 128000, + maxTokens: 4096, + }, + { + id: "glm-5", + name: "GLM-5", + api: "anthropic-messages", + provider: "zai", + baseUrl: "https://api.z.ai", + reasoning: false, + input: ["text"], + cost: { input: 1, output: 2, cacheRead: 0.1, cacheWrite: 1 }, + contextWindow: 128000, + maxTokens: 4096, + }, + ]; + const registry = { + getAll: () => ambiguousModels, + } as unknown as Parameters[0]["modelRegistry"]; + + const result = resolveCliModel({ + cliModel: "zai/glm-5", + modelRegistry: registry, + }); + + expect(result.error).toBeUndefined(); + expect(result.model?.provider).toBe("zai"); + expect(result.model?.id).toBe("glm-5"); + }); }); describe("parseModelString", () => {