diff --git a/src/open/launchers/opencode.ts b/src/open/launchers/opencode.ts index 944fea7..084c924 100644 --- a/src/open/launchers/opencode.ts +++ b/src/open/launchers/opencode.ts @@ -118,7 +118,7 @@ export function buildInlineConfig( }); } -const MODEL_SHAPE = /^[^/\s]+\/[^/\s]+$/; +const MODEL_SHAPE = /^[^/\s]+\/[^/\s]+(?:\/[^/\s]+)*$/; export function buildArgs( role: Role, diff --git a/tests/open-opencode.test.ts b/tests/open-opencode.test.ts index 3be94f5..daa4790 100644 --- a/tests/open-opencode.test.ts +++ b/tests/open-opencode.test.ts @@ -264,6 +264,20 @@ describe("buildArgs", () => { expect(() => buildArgs("general", { model: bad }, [])).toThrow(/must be provider\/model/); } }); + + it.each([ + ["an empty middle segment", "a//b"], + ["a trailing slash after nesting", "a/b/"], + ["whitespace after nesting", "a/b c/d"], + ] as const)("rejects a model with %s before spawn", (_name, model) => { + expect(() => buildArgs("general", { model }, [])).toThrow(/must be provider\/model/); + }); + + it("accepts provider models with nested model paths", () => { + for (const model of ["openrouter/meta/muse-spark-1.3-contributor", "a/b/c/d"]) { + expect(() => buildArgs("general", { model }, [])).not.toThrow(); + } + }); }); describe("opencode theme", () => {