authorgravatar for 132888838+arhxam@users.noreply.github.comArham Amin <132888838+arhxam@users.noreply.github.com> 2026-08-11 09:01:31+05:30
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-10 23:31:31-04:00
log2ea51bd31f494ec3214a0c9f9adc3262c71e6530
tree35a81e732ac131cad370ef2315a946e662af4a78
parente5c82d79a09e010d1a87c715f405635f8c45f1f7
signature Signed by PGP key B5690EEEBB952194

fix(server): OpenCode model parsing drops models with a slash in the JSON body (#5072)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

2 files changed, 32 insertions(+), 1 deletions(-)

apps/server/src/provider/opencodeRuntime.cliParsers.test.ts+25
......@@ -125,6 +125,31 @@ describe("parseModelsCliOutput", () => {
125125 NodeAssert.ok(model.variants);
126126 NodeAssert.equal(model.variants!["medium"] !== undefined, true);
127127 });
128
129 it("keeps a model whose JSON body has a slash and no interior whitespace", () => {
130 // OpenRouter-style: the model id contains a `/` and no string value has a
131 // space, so the JSON body line itself matches the slug regex. It must still
132 // be treated as the body of the preceding slug, not a new slug.
133 const stdout = [
134 "openrouter/qwen/qwen3-coder",
135 JSON.stringify({
136 id: "qwen/qwen3-coder",
137 providerID: "openrouter",
138 name: "qwen3-coder",
139 status: "active",
140 }),
141 ].join("\n");
142
143 const result = parseModelsCliOutput(stdout);
144 NodeAssert.equal(result.providers.size, 1);
145 NodeAssert.deepEqual([...result.connected], ["openrouter"]);
146 const provider = result.providers.get("openrouter")!;
147 NodeAssert.ok(provider);
148 const model = provider.models["qwen/qwen3-coder"]!;
149 NodeAssert.ok(model);
150 NodeAssert.equal(model.id, "qwen/qwen3-coder");
151 NodeAssert.equal(model.providerID, "openrouter");
152 });
128153});
129154
130155describe("parseAgentListCliOutput", () => {
apps/server/src/provider/opencodeRuntime.ts+7-1
......@@ -216,7 +216,13 @@ export function parseModelsCliOutput(stdout: string): {
216216 };
217217
218218 for (const line of lines) {
219 const slugMatch = SLUG_LINE_RE.exec(line);
219 // A model's JSON body is a single `JSON.stringify` line starting with `{`,
220 // while a provider/model slug is a bare `provider/model` header. Only the
221 // latter can be a slug: without this guard a body line with no interior
222 // whitespace and a `/` in one of its values (e.g. an OpenRouter model whose
223 // `id` is `vendor/model`) matches SLUG_LINE_RE, so flushModel runs against
224 // an empty body and the model is silently dropped.
225 const slugMatch = line.trimStart().startsWith("{") ? null : SLUG_LINE_RE.exec(line);
220226 if (slugMatch) {
221227 flushModel();
222228 currentSlug = slugMatch[1]!;