summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorneavo <[email protected]>2026-02-02 04:12:30 +0800
committerGitHub <[email protected]>2026-02-01 14:12:30 -0600
commitf15755684f6de98f9953b5c1a04bac3202cb0e20 (patch)
tree4e9ba3a5f4f4dccb9ebdca4cae7cc02a3e97afa7
parent16145af480a520906b0659a85e9a871781b69089 (diff)
downloadopencode-f15755684f6de98f9953b5c1a04bac3202cb0e20.tar.gz
opencode-f15755684f6de98f9953b5c1a04bac3202cb0e20.zip
fix(opencode): scope agent variant to model (#11410)
-rw-r--r--packages/opencode/src/agent/agent.ts2
-rw-r--r--packages/opencode/src/config/config.ts5
-rw-r--r--packages/opencode/src/session/prompt.ts15
-rw-r--r--packages/opencode/test/config/config.test.ts31
-rw-r--r--packages/opencode/test/session/prompt-variant.test.ts60
5 files changed, 111 insertions, 2 deletions
diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts
index 1d90a4c36..72e7f8985 100644
--- a/packages/opencode/src/agent/agent.ts
+++ b/packages/opencode/src/agent/agent.ts
@@ -37,6 +37,7 @@ export namespace Agent {
providerID: z.string(),
})
.optional(),
+ variant: z.string().optional(),
prompt: z.string().optional(),
options: z.record(z.string(), z.any()),
steps: z.number().int().positive().optional(),
@@ -214,6 +215,7 @@ export namespace Agent {
native: false,
}
if (value.model) item.model = Provider.parseModel(value.model)
+ item.variant = value.variant ?? item.variant
item.prompt = value.prompt ?? item.prompt
item.description = value.description ?? item.description
item.temperature = value.temperature ?? item.temperature
diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts
index 7969e3079..98970ba39 100644
--- a/packages/opencode/src/config/config.ts
+++ b/packages/opencode/src/config/config.ts
@@ -593,6 +593,10 @@ export namespace Config {
export const Agent = z
.object({
model: z.string().optional(),
+ variant: z
+ .string()
+ .optional()
+ .describe("Default model variant for this agent (applies only when using the agent's configured model)."),
temperature: z.number().optional(),
top_p: z.number().optional(),
prompt: z.string().optional(),
@@ -624,6 +628,7 @@ export namespace Config {
const knownKeys = new Set([
"name",
"model",
+ "variant",
"prompt",
"description",
"temperature",
diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts
index 94eabdef7..ba77cd7ca 100644
--- a/packages/opencode/src/session/prompt.ts
+++ b/packages/opencode/src/session/prompt.ts
@@ -827,6 +827,17 @@ export namespace SessionPrompt {
async function createUserMessage(input: PromptInput) {
const agent = await Agent.get(input.agent ?? (await Agent.defaultAgent()))
+
+ const model = input.model ?? agent.model ?? (await lastModel(input.sessionID))
+ const variant =
+ input.variant ??
+ (agent.variant &&
+ agent.model &&
+ model.providerID === agent.model.providerID &&
+ model.modelID === agent.model.modelID
+ ? agent.variant
+ : undefined)
+
const info: MessageV2.Info = {
id: input.messageID ?? Identifier.ascending("message"),
role: "user",
@@ -836,9 +847,9 @@ export namespace SessionPrompt {
},
tools: input.tools,
agent: agent.name,
- model: input.model ?? agent.model ?? (await lastModel(input.sessionID)),
+ model,
system: input.system,
- variant: input.variant,
+ variant,
}
using _ = defer(() => InstructionPrompt.clear(info.id))
diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts
index 1752e22e0..8611d8296 100644
--- a/packages/opencode/test/config/config.test.ts
+++ b/packages/opencode/test/config/config.test.ts
@@ -255,6 +255,37 @@ test("handles agent configuration", async () => {
})
})
+test("treats agent variant as model-scoped setting (not provider option)", async () => {
+ await using tmp = await tmpdir({
+ init: async (dir) => {
+ await writeConfig(dir, {
+ $schema: "https://opencode.ai/config.json",
+ agent: {
+ test_agent: {
+ model: "openai/gpt-5.2",
+ variant: "xhigh",
+ max_tokens: 123,
+ },
+ },
+ })
+ },
+ })
+
+ await Instance.provide({
+ directory: tmp.path,
+ fn: async () => {
+ const config = await Config.get()
+ const agent = config.agent?.["test_agent"]
+
+ expect(agent?.variant).toBe("xhigh")
+ expect(agent?.options).toMatchObject({
+ max_tokens: 123,
+ })
+ expect(agent?.options).not.toHaveProperty("variant")
+ },
+ })
+})
+
test("handles command configuration", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
diff --git a/packages/opencode/test/session/prompt-variant.test.ts b/packages/opencode/test/session/prompt-variant.test.ts
new file mode 100644
index 000000000..16e8a2244
--- /dev/null
+++ b/packages/opencode/test/session/prompt-variant.test.ts
@@ -0,0 +1,60 @@
+import { describe, expect, test } from "bun:test"
+import { Instance } from "../../src/project/instance"
+import { Session } from "../../src/session"
+import { SessionPrompt } from "../../src/session/prompt"
+import { tmpdir } from "../fixture/fixture"
+
+describe("session.prompt agent variant", () => {
+ test("applies agent variant only when using agent model", async () => {
+ await using tmp = await tmpdir({
+ git: true,
+ config: {
+ agent: {
+ build: {
+ model: "openai/gpt-5.2",
+ variant: "xhigh",
+ },
+ },
+ },
+ })
+
+ await Instance.provide({
+ directory: tmp.path,
+ fn: async () => {
+ const session = await Session.create({})
+
+ const other = await SessionPrompt.prompt({
+ sessionID: session.id,
+ agent: "build",
+ model: { providerID: "opencode", modelID: "kimi-k2.5-free" },
+ noReply: true,
+ parts: [{ type: "text", text: "hello" }],
+ })
+ if (other.info.role !== "user") throw new Error("expected user message")
+ expect(other.info.variant).toBeUndefined()
+
+ const match = await SessionPrompt.prompt({
+ sessionID: session.id,
+ agent: "build",
+ noReply: true,
+ parts: [{ type: "text", text: "hello again" }],
+ })
+ if (match.info.role !== "user") throw new Error("expected user message")
+ expect(match.info.model).toEqual({ providerID: "openai", modelID: "gpt-5.2" })
+ expect(match.info.variant).toBe("xhigh")
+
+ const override = await SessionPrompt.prompt({
+ sessionID: session.id,
+ agent: "build",
+ noReply: true,
+ variant: "high",
+ parts: [{ type: "text", text: "hello third" }],
+ })
+ if (override.info.role !== "user") throw new Error("expected user message")
+ expect(override.info.variant).toBe("high")
+
+ await Session.remove(session.id)
+ },
+ })
+ })
+})