diff --git a/packages/opencode/src/command/index.ts b/packages/opencode/src/command/index.ts index 976f1cd51e9..ccd4ec8db09 100644 --- a/packages/opencode/src/command/index.ts +++ b/packages/opencode/src/command/index.ts @@ -26,6 +26,7 @@ export namespace Command { description: z.string().optional(), agent: z.string().optional(), model: z.string().optional(), + variant: z.string().optional(), mcp: z.boolean().optional(), // workaround for zod not supporting async functions natively so we use getters // https://zod.dev/v4/changelog?id=zfunction @@ -83,6 +84,7 @@ export namespace Command { name, agent: command.agent, model: command.model, + variant: command.variant, description: command.description, get template() { return command.template diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index ead3a0149b4..a5362b74ec7 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -525,6 +525,7 @@ export namespace Config { description: z.string().optional(), agent: z.string().optional(), model: z.string().optional(), + variant: z.string().optional(), subtask: z.boolean().optional(), }) export type Command = z.infer diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index f891612272c..6152f736a62 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1506,6 +1506,16 @@ export namespace SessionPrompt { template = template.trim() const model = await (async () => { + if (!command.model && command.variant) { + const error = new NamedError.Unknown({ + message: `Command "${input.command}" has a variant "${command.variant}" but no model defined.`, + }) + Bus.publish(Session.Event.Error, { + sessionID: input.sessionID, + error: error.toObject(), + }) + throw error + } if (command.model) { return Provider.parseModel(command.model) } @@ -1519,19 +1529,38 @@ export namespace SessionPrompt { return await lastModel(input.sessionID) })() - try { - await Provider.getModel(model.providerID, model.modelID) - } catch (e) { - if (Provider.ModelNotFoundError.isInstance(e)) { - const { providerID, modelID, suggestions } = e.data - const hint = suggestions?.length ? ` Did you mean: ${suggestions.join(", ")}?` : "" + const info = await (async () => { + try { + return await Provider.getModel(model.providerID, model.modelID) + } catch (e) { + if (Provider.ModelNotFoundError.isInstance(e)) { + const { providerID, modelID, suggestions } = e.data + const hint = suggestions?.length ? ` Did you mean: ${suggestions.join(", ")}?` : "" + Bus.publish(Session.Event.Error, { + sessionID: input.sessionID, + error: new NamedError.Unknown({ message: `Model not found: ${providerID}/${modelID}.${hint}` }).toObject(), + }) + } + throw e + } + })() + + if (command.variant) { + const variants = info.variants ?? {} + const available = Object.keys(variants) + if (!variants[command.variant]) { + const hint = available.length ? ` Available variants: ${available.join(", ")}` : "" + const error = new NamedError.Unknown({ + message: `Variant not found: ${model.providerID}/${model.modelID} with variant "${command.variant}".${hint}`, + }) Bus.publish(Session.Event.Error, { sessionID: input.sessionID, - error: new NamedError.Unknown({ message: `Model not found: ${providerID}/${modelID}.${hint}` }).toObject(), + error: error.toObject(), }) + throw error } - throw e } + const agent = await Agent.get(agentName) if (!agent) { const available = await Agent.list().then((agents) => agents.filter((a) => !a.hidden).map((a) => a.name)) @@ -1565,7 +1594,7 @@ export namespace SessionPrompt { model, agent: agentName, parts, - variant: input.variant, + variant: command.variant ?? input.variant, })) as MessageV2.WithParts Bus.publish(Command.Event.Executed, { diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index 087eb0c628c..386a780d20c 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -229,6 +229,8 @@ test("handles command configuration", async () => { template: "test template", description: "test command", agent: "test_agent", + model: "test_model", + variant: "test_variant", }, }, }), @@ -243,6 +245,8 @@ test("handles command configuration", async () => { template: "test template", description: "test command", agent: "test_agent", + model: "test_model", + variant: "test_variant", }) }, })