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
12 changes: 7 additions & 5 deletions packages/app/src/components/prompt-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1051,7 +1051,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 @@ -1539,9 +1539,11 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<Match when={store.mode === "normal"}>
<TooltipKeybind placement="top" title="Cycle agent" keybind={command.keybind("agent.cycle")}>
<Select
options={local.agent.list().map((agent) => agent.name)}
current={local.agent.current()?.name ?? ""}
onSelect={local.agent.set}
options={local.agent.list()}
value={(a) => a.name}
label={(a) => (a.variant ? `${a.name} (${a.variant})` : a.name)}
current={local.agent.current()}
onSelect={(a) => a && local.agent.set(a.name)}
class="capitalize"
variant="ghost"
/>
Expand Down Expand Up @@ -1577,7 +1579,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
class="text-text-base _hidden group-hover/prompt-input:inline-block"
onClick={() => local.model.variant.cycle()}
>
<span class="capitalize text-12-regular">{local.model.variant.current() ?? "Default"}</span>
<span class="capitalize text-12-regular">{local.model.variant.effective() ?? "Default"}</span>
</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 @@ -33,6 +33,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 @@ -197,6 +198,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
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 @@ -697,8 +697,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 @@ -944,7 +944,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
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
2 changes: 2 additions & 0 deletions packages/opencode/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,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 @@ -563,6 +564,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 @@ -88,8 +88,8 @@ export namespace LLM {
const auth = await Auth.get(input.model.providerID)
const isCodex = provider.id === "openai" && auth?.type === "oauth"

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
2 changes: 1 addition & 1 deletion packages/opencode/src/session/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -814,7 +814,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
5 changes: 5 additions & 0 deletions packages/opencode/src/tool/task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,11 @@ export const TaskTool = Tool.define("task", async (ctx) => {
providerID: model.providerID,
},
agent: agent.name,
// Use subagent's configured variant (respects explicit per-agent config).
// Parent's --variant override does NOT cascade to subagents intentionally:
// if user configured oracle with variant: "high", that intent should be honored
// even when parent session uses --variant low.
variant: agent.variant,
tools: {
todowrite: false,
todoread: false,
Expand Down
5 changes: 5 additions & 0 deletions packages/sdk/js/src/v2/gen/types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1315,6 +1315,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 @@ -2001,6 +2005,7 @@ export type Agent = {
providerID: string
}
prompt?: string
variant?: string
options: {
[key: string]: unknown
}
Expand Down
7 changes: 7 additions & 0 deletions packages/sdk/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -8654,6 +8654,10 @@
"model": {
"type": "string"
},
"variant": {
"description": "Default variant to use for this agent",
"type": "string"
},
"temperature": {
"type": "number"
},
Expand Down Expand Up @@ -10291,6 +10295,9 @@
"prompt": {
"type": "string"
},
"variant": {
"type": "string"
},
"options": {
"type": "object",
"propertyNames": {
Expand Down