fix(opencode): scope agent variant to model (#11410)
This commit is contained in:
@@ -37,6 +37,7 @@ export namespace Agent {
|
|||||||
providerID: z.string(),
|
providerID: z.string(),
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
|
variant: z.string().optional(),
|
||||||
prompt: z.string().optional(),
|
prompt: z.string().optional(),
|
||||||
options: z.record(z.string(), z.any()),
|
options: z.record(z.string(), z.any()),
|
||||||
steps: z.number().int().positive().optional(),
|
steps: z.number().int().positive().optional(),
|
||||||
@@ -214,6 +215,7 @@ export namespace Agent {
|
|||||||
native: false,
|
native: false,
|
||||||
}
|
}
|
||||||
if (value.model) item.model = Provider.parseModel(value.model)
|
if (value.model) item.model = Provider.parseModel(value.model)
|
||||||
|
item.variant = value.variant ?? item.variant
|
||||||
item.prompt = value.prompt ?? item.prompt
|
item.prompt = value.prompt ?? item.prompt
|
||||||
item.description = value.description ?? item.description
|
item.description = value.description ?? item.description
|
||||||
item.temperature = value.temperature ?? item.temperature
|
item.temperature = value.temperature ?? item.temperature
|
||||||
|
|||||||
@@ -593,6 +593,10 @@ export namespace Config {
|
|||||||
export const Agent = z
|
export const Agent = z
|
||||||
.object({
|
.object({
|
||||||
model: z.string().optional(),
|
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(),
|
temperature: z.number().optional(),
|
||||||
top_p: z.number().optional(),
|
top_p: z.number().optional(),
|
||||||
prompt: z.string().optional(),
|
prompt: z.string().optional(),
|
||||||
@@ -624,6 +628,7 @@ export namespace Config {
|
|||||||
const knownKeys = new Set([
|
const knownKeys = new Set([
|
||||||
"name",
|
"name",
|
||||||
"model",
|
"model",
|
||||||
|
"variant",
|
||||||
"prompt",
|
"prompt",
|
||||||
"description",
|
"description",
|
||||||
"temperature",
|
"temperature",
|
||||||
|
|||||||
@@ -827,6 +827,17 @@ export namespace SessionPrompt {
|
|||||||
|
|
||||||
async function createUserMessage(input: PromptInput) {
|
async function createUserMessage(input: PromptInput) {
|
||||||
const agent = await Agent.get(input.agent ?? (await Agent.defaultAgent()))
|
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 = {
|
const info: MessageV2.Info = {
|
||||||
id: input.messageID ?? Identifier.ascending("message"),
|
id: input.messageID ?? Identifier.ascending("message"),
|
||||||
role: "user",
|
role: "user",
|
||||||
@@ -836,9 +847,9 @@ export namespace SessionPrompt {
|
|||||||
},
|
},
|
||||||
tools: input.tools,
|
tools: input.tools,
|
||||||
agent: agent.name,
|
agent: agent.name,
|
||||||
model: input.model ?? agent.model ?? (await lastModel(input.sessionID)),
|
model,
|
||||||
system: input.system,
|
system: input.system,
|
||||||
variant: input.variant,
|
variant,
|
||||||
}
|
}
|
||||||
using _ = defer(() => InstructionPrompt.clear(info.id))
|
using _ = defer(() => InstructionPrompt.clear(info.id))
|
||||||
|
|
||||||
|
|||||||
@@ -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 () => {
|
test("handles command configuration", async () => {
|
||||||
await using tmp = await tmpdir({
|
await using tmp = await tmpdir({
|
||||||
init: async (dir) => {
|
init: async (dir) => {
|
||||||
|
|||||||
60
packages/opencode/test/session/prompt-variant.test.ts
Normal file
60
packages/opencode/test/session/prompt-variant.test.ts
Normal file
@@ -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)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user