Skip to content

Commit 2ea51bd

Browse files
arhxamclaude
andauthored
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>
1 parent e5c82d7 commit 2ea51bd

2 files changed

Lines changed: 32 additions & 1 deletion

File tree

apps/server/src/provider/opencodeRuntime.cliParsers.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

130155
describe("parseAgentListCliOutput", () => {

apps/server/src/provider/opencodeRuntime.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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]!;

0 commit comments

Comments
 (0)