refactor(cli): parse variants from model refs

This commit is contained in:
Dax Raad 2026-07-08 23:25:37 -04:00
commit 67f2393f83
10 changed files with 79 additions and 34 deletions

View file

@ -171,7 +171,7 @@ export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCO
),
model: Flag.string("model").pipe(
Flag.withAlias("m"),
Flag.withDescription("Model to use in the format provider/model"),
Flag.withDescription("Model to use in the format provider/model#variant"),
Flag.optional,
),
agent: Flag.string("agent").pipe(Flag.withDescription("Agent to use"), Flag.optional),
@ -185,7 +185,6 @@ export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCO
Flag.atMost(100),
),
title: Flag.string("title").pipe(Flag.withDescription("Session title"), Flag.optional),
variant: Flag.string("variant").pipe(Flag.withDescription("Model variant"), Flag.optional),
thinking: Flag.boolean("thinking").pipe(
Flag.withDescription("Show thinking blocks"),
Flag.withDefault(false),
@ -195,10 +194,6 @@ export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCO
Flag.withDefault(false),
),
yolo: Flag.boolean("yolo").pipe(Flag.withDefault(false), Flag.withHidden),
dangerouslySkipPermissions: Flag.boolean("dangerously-skip-permissions").pipe(
Flag.withDefault(false),
Flag.withHidden,
),
},
}),
Spec.make("service", {

View file

@ -23,9 +23,8 @@ export default Runtime.handler(Commands.commands.run, (input) =>
format: input.format,
file: [...input.file],
title: Option.getOrUndefined(input.title),
variant: Option.getOrUndefined(input.variant),
thinking: input.thinking,
dangerouslySkipPermissions: input.auto || input.yolo || input.dangerouslySkipPermissions,
auto: input.auto || input.yolo,
}),
)
}),

View file

@ -3,5 +3,6 @@ export {
runNonInteractive,
mergeInput as mergeNonInteractiveInput,
pickRunModel,
parseRunModel,
type RunCommandInput,
} from "./run"

View file

@ -25,7 +25,7 @@ type Input = {
variant?: string
thinking: boolean
format: "default" | "json"
dangerouslySkipPermissions: boolean
auto: boolean
/** True when the client is attached to a shared server rather than an exclusive in-process one. */
attached: boolean
renderTool: (part: ToolPart) => Promise<void>
@ -91,7 +91,7 @@ export async function runNonInteractivePrompt(input: Input) {
}
const replyPermission = async (request: { id: string; action: string; resources: ReadonlyArray<string> }) => {
if (!input.dangerouslySkipPermissions) {
if (!input.auto) {
permissionRejected = true
UI.println(
UI.Style.TEXT_WARNING_BOLD + "!",
@ -103,10 +103,10 @@ export async function runNonInteractivePrompt(input: Input) {
.reply({
sessionID: input.sessionID,
requestID: request.id,
reply: input.dangerouslySkipPermissions ? "once" : "reject",
reply: input.auto ? "once" : "reject",
})
.catch(() => {})
if (!input.dangerouslySkipPermissions) {
if (!input.auto) {
await input.client.session.interrupt({ sessionID: input.sessionID }).catch(() => {})
}
}

View file

@ -1,6 +1,7 @@
import { Service } from "@opencode-ai/client/effect"
import { OpenCode, type OpenCodeClient } from "@opencode-ai/client/promise"
import { FSUtil } from "@opencode-ai/core/fs-util"
import { Model } from "@opencode-ai/schema/model"
import type { ToolPart } from "@opencode-ai/sdk/v2"
import { open } from "node:fs/promises"
import path from "node:path"
@ -21,9 +22,8 @@ export type RunCommandInput = {
format: "default" | "json"
file: string[]
title?: string
variant?: string
thinking?: boolean
dangerouslySkipPermissions?: boolean
auto?: boolean
}
type FilePart = {
@ -62,7 +62,9 @@ async function execute(input: RunCommandInput, prepared: Prepared, endpoint: Ser
const session = await selectSession(client, requestedDirectory, input)
const cwd = session?.location.directory ?? requestedDirectory
const workspace = session?.location.workspaceID
const explicitModel = parseModel(input.model)
const explicit = parseRunModel(input.model)
const explicitModel = explicit?.model
const variant = explicit?.variant
const sessionModel = session?.model ? { providerID: session.model.providerID, modelID: session.model.id } : undefined
const defaultModel =
!explicitModel && !sessionModel
@ -70,8 +72,8 @@ async function execute(input: RunCommandInput, prepared: Prepared, endpoint: Ser
.default({ location: { directory: cwd, workspace } })
.then((result) => (result.data ? { providerID: result.data.providerID, modelID: result.data.id } : undefined))
: undefined
const model = pickRunModel(explicitModel, input.variant, sessionModel, defaultModel)
if (input.variant && !model)
const model = pickRunModel(explicitModel, variant, sessionModel, defaultModel)
if (variant && !model)
return reportError(input, "Cannot select a variant before selecting a model", session?.id)
if (model) {
await waitForCatalogReady({ sdk: client, directory: cwd, workspace, model })
@ -84,7 +86,7 @@ async function execute(input: RunCommandInput, prepared: Prepared, endpoint: Ser
session ??
(await client.session.create({
agent,
model: model ? { providerID: model.providerID, id: model.modelID, variant: input.variant } : undefined,
model: model ? { providerID: model.providerID, id: model.modelID, variant } : undefined,
location: { directory: cwd },
}))
if (!session && input.title !== undefined) {
@ -101,10 +103,10 @@ async function execute(input: RunCommandInput, prepared: Prepared, endpoint: Ser
files: prepared.files,
agent,
model,
variant: input.variant,
variant,
thinking: input.thinking ?? false,
format: input.format,
dangerouslySkipPermissions: input.dangerouslySkipPermissions ?? false,
auto: input.auto ?? false,
attached: true,
renderTool,
renderToolError,
@ -142,12 +144,13 @@ function localDirectory(root: string) {
}
}
function parseModel(value?: string) {
export function parseRunModel(value?: string) {
if (!value) return
const [providerID, ...rest] = value.split("/")
const modelID = rest.join("/")
if (!providerID || !modelID) fail("--model must use the format provider/model")
return { providerID, modelID }
const ref = Model.Ref.parse(value)
return {
model: { providerID: ref.providerID, modelID: ref.id },
variant: ref.variant,
}
}
async function validateAgent(client: OpenCodeClient, directory: string, name?: string) {