From 984cf6643d303cd414a9fe883a01c212caf40b22 Mon Sep 17 00:00:00 2001 From: "robopipi[bot]" <336283572+robopipi[bot]@users.noreply.github.com> Date: Sun, 25 Oct 2026 08:48:27 +0100 Subject: [PATCH] fix(coding-agent): resolve provider/id references when the provider id contains a slash (#11397) `--model "/"` returned "not found" when the provider id itself contains a `/`. URL-derived ids such as `llama-server=http://host:10800` are one example. `splitProviderPrefix()` in `model-resolver.ts` split the reference at the first `/`, which turned the prefix into `llama-server=http:`. No provider has that id, so resolution fell through to not-found. `splitProviderPrefix()` now compares the text against the provider ids in the catalog. A provider matches when the text starts with its id followed by `/`, and the longest matching id wins. Ordinary provider ids resolve exactly as before. Testing: - I added two cases to the `resolveModelReference` table in `test/model-resolver.test.ts`, using a provider `llama=http://host:10800`: one plain, one with a `:high` thinking suffix. Both fail without the change and pass with it. The test runs every case against the catalog in both orders. - `node ../../node_modules/vitest/dist/cli.js --run test/model-resolver.test.ts`: 64 passed. - `npm run check`: passes. - Before the change, a standalone check of `resolveModelReference` with the reporter's exact provider and model (`llama-server=http://host:10800/unsloth/Qwen3.8-27B-GGUF:BF16`) returned `not-found`. It now returns `resolved`. - One test, `test/model-runtime-classifiers.test.ts` ("applies provider compat from models.json to classifier models"), fails in my environment both with and without this change. It is unrelated to this fix. Fixes #11394 Requested by @badlogic Co-authored-by: triagent --- .../coding-agent/src/core/model-resolver.ts | 19 ++++++++++++------- .../coding-agent/test/model-resolver.test.ts | 12 ++++++++++++ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/packages/coding-agent/src/core/model-resolver.ts b/packages/coding-agent/src/core/model-resolver.ts index e6657e9eb..bd331f690 100644 --- a/packages/coding-agent/src/core/model-resolver.ts +++ b/packages/coding-agent/src/core/model-resolver.ts @@ -128,13 +128,18 @@ function splitProviderPrefix( text: string, models: readonly Model[], ): { provider: string; modelText: string } | undefined { - const slashIndex = text.indexOf("/"); - if (slashIndex === -1) return undefined; - const prefix = text.substring(0, slashIndex).trim().toLowerCase(); - const modelText = text.substring(slashIndex + 1).trim(); - if (!prefix || !modelText) return undefined; - const provider = models.find((model) => model.provider.toLowerCase() === prefix)?.provider; - return provider ? { provider, modelText } : undefined; + // Provider ids may contain "/" themselves (e.g. URL-derived ids), so match known ids, longest first + const lowerText = text.toLowerCase(); + let best: { provider: string; modelText: string } | undefined; + for (const { provider } of models) { + const prefix = provider.trim().toLowerCase(); + if (!prefix || (best && best.provider.length >= provider.length)) continue; + const slashIndex = lowerText.indexOf("/", prefix.length); + if (slashIndex === -1 || lowerText.substring(0, slashIndex).trim() !== prefix) continue; + const modelText = text.substring(slashIndex + 1).trim(); + if (modelText) best = { provider, modelText }; + } + return best; } /** diff --git a/packages/coding-agent/test/model-resolver.test.ts b/packages/coding-agent/test/model-resolver.test.ts index f8226a2be..018f4c8d1 100644 --- a/packages/coding-agent/test/model-resolver.test.ts +++ b/packages/coding-agent/test/model-resolver.test.ts @@ -115,6 +115,8 @@ const referenceCatalog = [ referenceModel("gw-b", "claude-opus-4-8"), referenceModel("gw-b", "gw-b/free"), referenceModel("gw-b", "zz:free"), + // URL-derived provider id containing "/" (#11394) + referenceModel("llama=http://host:10800", "unsloth/qwen-gguf:BF16"), ]; type ReferenceExpected = @@ -152,6 +154,16 @@ const referenceCases: ReferenceCase[] = [ text: "openrouter/qwen/qwen3-coder:exacto:high", expected: { model: "openrouter/qwen/qwen3-coder:exacto", thinkingLevel: "high" }, }, + { + name: "provider id containing a slash", + text: "llama=http://host:10800/unsloth/qwen-gguf:BF16", + expected: { model: "llama=http://host:10800/unsloth/qwen-gguf:BF16" }, + }, + { + name: "provider id containing a slash, with thinking suffix", + text: "llama=http://host:10800/unsloth/qwen-gguf:BF16:high", + expected: { model: "llama=http://host:10800/unsloth/qwen-gguf:BF16", thinkingLevel: "high" }, + }, { name: "raw id wins when the provider prefix names no matching model", text: "openai/gpt-4o:extended",