Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .opencode/agent/duplicate-pr.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mode: primary
hidden: true
model: opencode/claude-haiku-4-5
color: "#E67E22"
variant: "high"
tools:
"*": false
"github-pr-search": true
Expand Down
1 change: 1 addition & 0 deletions .opencode/agent/triage.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mode: primary
hidden: true
model: opencode/claude-haiku-4-5
color: "#44BA81"
variant: "high"
tools:
"*": false
"github-triage": true
Expand Down
4 changes: 2 additions & 2 deletions packages/app/src/components/prompt-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1066,7 +1066,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
providerID: currentModel.provider.id,
}
const agent = currentAgent.name
const variant = local.model.variant.current()
const variant = local.model.variant.effective()

const clearInput = () => {
prompt.reset()
Expand Down Expand Up @@ -1604,7 +1604,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
class="text-text-base _hidden group-hover/prompt-input:inline-block capitalize text-12-regular"
onClick={() => local.model.variant.cycle()}
>
{local.model.variant.current() ?? "Default"}
{local.model.variant.effective() ?? "Default"}
</Button>
</TooltipKeybind>
</Show>
Expand Down
9 changes: 6 additions & 3 deletions packages/app/src/context/local.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,9 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
const key = `${m.provider.id}/${m.id}`
return store.variant?.[key]
},
effective() {
return this.current() ?? agent.current()?.variant
},
list() {
const m = current()
if (!m) return []
Expand All @@ -313,12 +316,12 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
cycle() {
const variants = this.list()
if (variants.length === 0) return
const currentVariant = this.current()
if (!currentVariant) {
const effective = this.effective()
if (!effective) {
this.set(variants[0])
return
}
const index = variants.indexOf(currentVariant)
const index = variants.indexOf(effective)
if (index === -1 || index === variants.length - 1) {
this.set(undefined)
return
Expand Down
2 changes: 2 additions & 0 deletions packages/opencode/src/agent/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export namespace Agent {
})
.optional(),
prompt: z.string().optional(),
variant: z.string().optional(),
options: z.record(z.string(), z.any()),
steps: z.number().int().positive().optional(),
})
Expand Down Expand Up @@ -209,6 +210,7 @@ export namespace Agent {
native: false,
}
if (value.model) item.model = Provider.parseModel(value.model)
if (value.variant) item.variant = value.variant
item.prompt = value.prompt ?? item.prompt
item.description = value.description ?? item.description
item.temperature = value.temperature ?? item.temperature
Expand Down
2 changes: 1 addition & 1 deletion packages/opencode/src/cli/cmd/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export const RunCommand = cmd({
})
.option("variant", {
type: "string",
describe: "model variant (provider-specific reasoning effort, e.g., high, max, minimal)",
describe: "model variant (provider-specific reasoning effort, e.g., none, low, medium, high, xhigh, max)",
})
},
handler: async (args) => {
Expand Down
3 changes: 3 additions & 0 deletions packages/opencode/src/cli/cmd/tui/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,9 @@ function App() {
})
local.model.set({ providerID, modelID }, { recent: true })
}
if (args.variant) {
local.model.variant.set(args.variant)
}
if (args.sessionID) {
route.navigate({
type: "session",
Expand Down
4 changes: 3 additions & 1 deletion packages/opencode/src/cli/cmd/tui/component/dialog-agent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ export function DialogAgent() {

const options = createMemo(() =>
local.agent.list().map((item) => {
const desc = item.native ? "native" : item.description
const variant = item.variant ? ` (${item.variant})` : ""
return {
value: item.name,
title: item.name,
description: item.native ? "native" : item.description,
description: desc ? desc + variant : variant.trim(),
}
}),
)
Expand Down
6 changes: 3 additions & 3 deletions packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -695,8 +695,8 @@ export function Prompt(props: PromptProps) {
const showVariant = createMemo(() => {
const variants = local.model.variant.list()
if (variants.length === 0) return false
const current = local.model.variant.current()
return !!current
const effective = local.model.variant.effective()
return !!effective
})

const spinnerDef = createMemo(() => {
Expand Down Expand Up @@ -945,7 +945,7 @@ export function Prompt(props: PromptProps) {
<Show when={showVariant()}>
<text fg={theme.textMuted}>·</text>
<text>
<span style={{ fg: theme.warning, bold: true }}>{local.model.variant.current()}</span>
<span style={{ fg: theme.warning, bold: true }}>{local.model.variant.effective()}</span>
</text>
</Show>
</box>
Expand Down
1 change: 1 addition & 0 deletions packages/opencode/src/cli/cmd/tui/context/args.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createSimpleContext } from "./helper"
export interface Args {
model?: string
agent?: string
variant?: string
prompt?: string
continue?: boolean
sessionID?: string
Expand Down
9 changes: 6 additions & 3 deletions packages/opencode/src/cli/cmd/tui/context/local.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,9 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
const key = `${m.providerID}/${m.modelID}`
return modelStore.variant[key]
},
effective() {
return this.current() ?? agent.current().variant
},
list() {
const m = currentModel()
if (!m) return []
Expand All @@ -332,12 +335,12 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
cycle() {
const variants = this.list()
if (variants.length === 0) return
const current = this.current()
if (!current) {
const effective = this.effective()
if (!effective) {
this.set(variants[0])
return
}
const index = variants.indexOf(current)
const index = variants.indexOf(effective)
if (index === -1 || index === variants.length - 1) {
this.set(undefined)
return
Expand Down
8 changes: 7 additions & 1 deletion packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1244,7 +1244,13 @@ function AssistantMessage(props: { message: AssistantMessage; parts: Part[]; las
▣{" "}
</span>{" "}
<span style={{ fg: theme.text }}>{Locale.titlecase(props.message.mode)}</span>
<span style={{ fg: theme.textMuted }}> · {props.message.modelID}</span>
<span style={{ fg: theme.textMuted }}>
{" "}
· {props.message.providerID}/{props.message.modelID}
</span>
<Show when={props.message.variant}>
<span style={{ fg: theme.textMuted }}> · {props.message.variant}</span>
</Show>
<Show when={duration()}>
<span style={{ fg: theme.textMuted }}> · {Locale.duration(duration())}</span>
</Show>
Expand Down
5 changes: 5 additions & 0 deletions packages/opencode/src/cli/cmd/tui/thread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ export const TuiThreadCommand = cmd({
.option("agent", {
type: "string",
describe: "agent to use",
})
.option("variant", {
type: "string",
describe: "model variant (provider-specific reasoning effort, e.g., none, low, medium, high, xhigh, max)",
}),
handler: async (args) => {
// Resolve relative paths against PWD to preserve behavior when using --cwd flag
Expand Down Expand Up @@ -149,6 +153,7 @@ export const TuiThreadCommand = cmd({
sessionID: args.session,
agent: args.agent,
model: args.model,
variant: args.variant,
prompt,
},
onExit: async () => {
Expand Down
2 changes: 2 additions & 0 deletions packages/opencode/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,7 @@ export namespace Config {
export const Agent = z
.object({
model: z.string().optional(),
variant: z.string().optional().describe("Default variant to use for this agent"),
temperature: z.number().optional(),
top_p: z.number().optional(),
prompt: z.string().optional(),
Expand Down Expand Up @@ -558,6 +559,7 @@ export namespace Config {
const knownKeys = new Set([
"name",
"model",
"variant",
"prompt",
"description",
"temperature",
Expand Down
4 changes: 2 additions & 2 deletions packages/opencode/src/session/llm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ export namespace LLM {
system.push(header, rest.join("\n"))
}

const variant =
!input.small && input.model.variants && input.user.variant ? input.model.variants[input.user.variant] : {}
const selectedVariant = input.user.variant ?? input.agent.variant
const variant = !input.small && input.model.variants && selectedVariant ? input.model.variants[selectedVariant] : {}
const base = input.small
? ProviderTransform.smallOptions(input.model)
: ProviderTransform.options(input.model, input.sessionID, provider.options)
Expand Down
1 change: 1 addition & 0 deletions packages/opencode/src/session/message-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ export namespace MessageV2 {
}),
}),
finish: z.string().optional(),
variant: z.string().optional(),
}).meta({
ref: "AssistantMessage",
})
Expand Down
4 changes: 3 additions & 1 deletion packages/opencode/src/session/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ export namespace SessionPrompt {
},
modelID: model.id,
providerID: model.providerID,
variant: lastUser.variant,
time: {
created: Date.now(),
},
Expand Down Expand Up @@ -536,6 +537,7 @@ export namespace SessionPrompt {
},
modelID: model.id,
providerID: model.providerID,
variant: lastUser.variant,
time: {
created: Date.now(),
},
Expand Down Expand Up @@ -830,7 +832,7 @@ export namespace SessionPrompt {
agent: agent.name,
model: input.model ?? agent.model ?? (await lastModel(input.sessionID)),
system: input.system,
variant: input.variant,
variant: input.variant ?? agent.variant,
}

const parts = await Promise.all(
Expand Down
27 changes: 23 additions & 4 deletions packages/opencode/src/tool/task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ const parameters = z.object({
subagent_type: z.string().describe("The type of specialized agent to use for this task"),
session_id: z.string().describe("Existing Task session to continue").optional(),
command: z.string().describe("The command that triggered this task").optional(),
model: z.string().describe("Override the subagent's model (format: providerID/modelID)").optional(),
variant: z.string().describe("Override the subagent's variant (e.g. 'low', 'medium', 'high')").optional(),
})

export const TaskTool = Tool.define("task", async (ctx) => {
Expand Down Expand Up @@ -130,10 +132,23 @@ export const TaskTool = Tool.define("task", async (ctx) => {
})
})

const model = agent.model ?? {
modelID: msg.info.modelID,
providerID: msg.info.providerID,
}
// Model priority: param override > agent config > parent context
const model = (() => {
if (params.model) {
const [providerID, ...rest] = params.model.split("/")
const modelID = rest.join("/")
if (!providerID || !modelID) {
throw new Error(`Invalid model format: ${params.model}. Expected: providerID/modelID`)
}
return { providerID, modelID }
}
return (
agent.model ?? {
modelID: msg.info.modelID,
providerID: msg.info.providerID,
}
)
})()

function cancel() {
SessionPrompt.cancel(session.id)
Expand All @@ -150,6 +165,10 @@ export const TaskTool = Tool.define("task", async (ctx) => {
providerID: model.providerID,
},
agent: agent.name,
// Variant priority: param override > subagent's configured variant > undefined.
// Model priority: param override > subagent's configured model > parent's model.
// This allows orchestrator agents to dynamically assign model+variant per task.
variant: params.variant ?? agent.variant,
tools: {
todowrite: false,
todoread: false,
Expand Down
6 changes: 6 additions & 0 deletions packages/opencode/src/tool/task.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ Available agent types and the tools they have access to:

When using the Task tool, you must specify a subagent_type parameter to select which agent type to use.

Optional override parameters:
- model: Override the subagent's default model (format: providerID/modelID, e.g. "anthropic/claude-haiku-4-5")
- variant: Override the subagent's default variant (e.g. "low", "medium", "high")

These overrides take priority over the agent's configured defaults, allowing dynamic model/variant selection per task.

When to use the Task tool:
- When you are instructed to execute custom slash commands. Use the Task tool with the slash command invocation as the entire prompt. The slash command can take arguments. For example: Task(description="Check the file", prompt="/check-file path/to/file.py")

Expand Down
6 changes: 6 additions & 0 deletions packages/sdk/js/src/v2/gen/types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ export type AssistantMessage = {
}
}
finish?: string
variant?: string
}

export type Message = UserMessage | AssistantMessage
Expand Down Expand Up @@ -1320,6 +1321,10 @@ export type PermissionConfig =

export type AgentConfig = {
model?: string
/**
* Default variant to use for this agent
*/
variant?: string
temperature?: number
top_p?: number
prompt?: string
Expand Down Expand Up @@ -2006,6 +2011,7 @@ export type Agent = {
providerID: string
}
prompt?: string
variant?: string
options: {
[key: string]: unknown
}
Expand Down
10 changes: 10 additions & 0 deletions packages/sdk/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -6077,6 +6077,9 @@
},
"finish": {
"type": "string"
},
"variant": {
"type": "string"
}
},
"required": [
Expand Down Expand Up @@ -8670,6 +8673,10 @@
"model": {
"type": "string"
},
"variant": {
"description": "Default variant to use for this agent",
"type": "string"
},
"temperature": {
"type": "number"
},
Expand Down Expand Up @@ -10307,6 +10314,9 @@
"prompt": {
"type": "string"
},
"variant": {
"type": "string"
},
"options": {
"type": "object",
"propertyNames": {
Expand Down