diff --git a/.changeset/calm-agent-variant-clear.md b/.changeset/calm-agent-variant-clear.md new file mode 100644 index 00000000000..9d29ee4c074 --- /dev/null +++ b/.changeset/calm-agent-variant-clear.md @@ -0,0 +1,5 @@ +--- +"@kilocode/cli": patch +--- + +Allow clearing agent model and variant overrides from settings. diff --git a/packages/opencode/src/config/agent.ts b/packages/opencode/src/config/agent.ts index a725ee32f32..a7ec3922c08 100644 --- a/packages/opencode/src/config/agent.ts +++ b/packages/opencode/src/config/agent.ts @@ -30,9 +30,11 @@ const Color = Schema.Union([ const AgentSchema = Schema.StructWithRest( Schema.Struct({ model: Schema.optional(Schema.NullOr(ConfigModelID)), // kilocode_change - nullable for delete sentinel - variant: Schema.optional(Schema.String).annotate({ + // kilocode_change start - nullable for delete sentinel + variant: Schema.optional(Schema.NullOr(Schema.String)).annotate({ description: "Default model variant for this agent (applies only when using the agent's configured model).", }), + // kilocode_change end temperature: Schema.optional(Schema.NullOr(Schema.Finite)), // kilocode_change - nullable for delete sentinel top_p: Schema.optional(Schema.NullOr(Schema.Finite)), // kilocode_change - nullable for delete sentinel prompt: Schema.optional(Schema.NullOr(Schema.String)), // kilocode_change - nullable for delete sentinel diff --git a/packages/opencode/test/kilocode/config/config.test.ts b/packages/opencode/test/kilocode/config/config.test.ts index 32327bbdc8b..bcf3651f6aa 100644 --- a/packages/opencode/test/kilocode/config/config.test.ts +++ b/packages/opencode/test/kilocode/config/config.test.ts @@ -47,6 +47,8 @@ const layer = Config.layer.pipe( const load = () => Effect.runPromise(Config.Service.use((svc) => svc.get()).pipe(Effect.scoped, Effect.provide(layer))) const clear = () => Effect.runPromise(Config.Service.use((svc) => svc.invalidate()).pipe(Effect.scoped, Effect.provide(layer))) +const saveGlobal = (config: Config.Info) => + Effect.runPromise(Config.Service.use((svc) => svc.updateGlobal(config)).pipe(Effect.scoped, Effect.provide(layer))) async function writeConfig(dir: string, config: object, name = "kilo.json") { await Filesystem.write(path.join(dir, name), JSON.stringify(config)) @@ -202,3 +204,82 @@ describe("kilocode indexing config", () => { expect(input.modelDimension).toBeUndefined() }) }) + +describe("agent config", () => { + test("accepts delete sentinels for agent model and variant overrides", () => { + const patch = Config.Info.zod.parse({ agent: { explore: { model: null, variant: null } } }) + const merged = KilocodeConfig.mergeConfig( + { + agent: { + explore: { + model: "kilo/anthropic/claude-sonnet-4-6", + variant: "high", + }, + }, + }, + patch, + ) + + expect(patch.agent?.explore?.model).toBeNull() + expect(patch.agent?.explore?.variant).toBeNull() + expect(merged.agent).toBeUndefined() + }) + + test("removes an agent variant override without removing its model", () => { + const patch = Config.Info.zod.parse({ agent: { explore: { variant: null } } }) + const merged = KilocodeConfig.mergeConfig( + { + agent: { + explore: { + model: "kilo/anthropic/claude-sonnet-4-6", + variant: "high", + }, + }, + }, + patch, + ) + + expect(patch.agent?.explore?.variant).toBeNull() + expect(merged.agent?.explore).toEqual({ model: "kilo/anthropic/claude-sonnet-4-6" }) + }) + + test("removes agent model and variant overrides from global JSONC config", async () => { + await using globalTmp = await tmpdir() + const file = path.join(globalTmp.path, "kilo.jsonc") + const prev = Global.Path.config + ;(Global.Path as { config: string }).config = globalTmp.path + await clear() + await disposeAllInstances() + + try { + await Filesystem.write( + file, + [ + "{", + " // Preserve this comment while clearing overrides.", + ' "agent": {', + ' "explore": {', + ' "model": "kilo/anthropic/claude-sonnet-4-6",', + ' "variant": "high",', + ' "description": "Keep me"', + " }", + " }", + "}", + ].join("\n"), + ) + const patch = Config.Info.zod.parse({ agent: { explore: { model: null, variant: null } } }) + + await saveGlobal(patch) + + const written = await Bun.file(file).text() + expect(written).toContain("// Preserve this comment while clearing overrides.") + expect(written).not.toContain('"model"') + expect(written).not.toContain('"variant"') + expect(written).toContain('"description": "Keep me"') + } finally { + ;(Global.Path as { config: string }).config = prev + await clear() + await disposeAllInstances() + } + }) +})