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.
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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<typeof resolveCliModel>[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", () => {
|
||||
|
||||
Reference in New Issue
Block a user