mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-01 15:32:11 +08:00
Merge pull request #13546 from Kilo-Org/validate-and-improve-pr-13539-fix
fix(vscode): honor configured reasoning defaults
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Use configured agent reasoning in new sessions instead of outdated saved defaults, and preserve explicit Default selections.
|
||||
@@ -2,7 +2,7 @@ import { describe, expect, it } from "bun:test"
|
||||
import { initialMessage, initialVariant, seedInitialVariant } from "../../webview-ui/agent-manager/initial-message"
|
||||
|
||||
describe("Agent Manager initial message", () => {
|
||||
it("forwards the selected variant to sendMessage", () => {
|
||||
it.each(["high", ""])("forwards the selected variant %s to sendMessage", (variant) => {
|
||||
const msg = initialMessage({
|
||||
type: "agentManager.sendInitialMessage",
|
||||
projectId: "project-a",
|
||||
@@ -12,7 +12,7 @@ describe("Agent Manager initial message", () => {
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-sonnet-4",
|
||||
agent: "code",
|
||||
variant: "high",
|
||||
variant,
|
||||
})
|
||||
|
||||
expect(msg).toEqual({
|
||||
@@ -23,7 +23,7 @@ describe("Agent Manager initial message", () => {
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-sonnet-4",
|
||||
agent: "code",
|
||||
variant: "high",
|
||||
variant,
|
||||
files: undefined,
|
||||
})
|
||||
})
|
||||
@@ -38,7 +38,7 @@ describe("Agent Manager initial message", () => {
|
||||
).toBeUndefined()
|
||||
})
|
||||
|
||||
it("builds the initial session variant state", () => {
|
||||
it.each(["medium", ""])("builds the initial session variant state for %s", (variant) => {
|
||||
const state = initialVariant(
|
||||
{
|
||||
type: "agentManager.sendInitialMessage",
|
||||
@@ -46,7 +46,7 @@ describe("Agent Manager initial message", () => {
|
||||
worktreeId: "wt-a",
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-sonnet-4",
|
||||
variant: "medium",
|
||||
variant,
|
||||
},
|
||||
"code",
|
||||
)
|
||||
@@ -56,7 +56,7 @@ describe("Agent Manager initial message", () => {
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-sonnet-4",
|
||||
agent: "code",
|
||||
value: "medium",
|
||||
value: variant,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -75,7 +75,7 @@ describe("Agent Manager initial message", () => {
|
||||
).toBeUndefined()
|
||||
})
|
||||
|
||||
it("seeds initial variant state into the session store", () => {
|
||||
it.each(["medium", ""])("seeds initial variant %s into the session store", (variant) => {
|
||||
const calls: unknown[] = []
|
||||
|
||||
seedInitialVariant(
|
||||
@@ -89,10 +89,10 @@ describe("Agent Manager initial message", () => {
|
||||
worktreeId: "wt-a",
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-sonnet-4",
|
||||
variant: "medium",
|
||||
variant,
|
||||
},
|
||||
)
|
||||
|
||||
expect(calls).toEqual([["session-a", "anthropic", "claude-sonnet-4", "medium", "code"]])
|
||||
expect(calls).toEqual([["session-a", "anthropic", "claude-sonnet-4", variant, "code"]])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -39,6 +39,18 @@ describe("session preference recovery", () => {
|
||||
})
|
||||
})
|
||||
|
||||
it.each([undefined, ""])("restores model default %s instead of an older effort", (variant) => {
|
||||
const prefs = resolveMessagePrefs(
|
||||
[
|
||||
msg({ model: { providerID: "anthropic", modelID: "claude-sonnet-4", variant: "high" } }),
|
||||
msg({ model: { providerID: "anthropic", modelID: "claude-sonnet-4", variant } }),
|
||||
],
|
||||
agents,
|
||||
)
|
||||
|
||||
expect(prefs.variant).toBe("")
|
||||
})
|
||||
|
||||
it("ignores assistant-only model data and invalid agents", () => {
|
||||
const prefs = resolveMessagePrefs(
|
||||
[
|
||||
@@ -66,7 +78,7 @@ describe("session preference recovery", () => {
|
||||
expect(prefs).toEqual({
|
||||
agent: "code",
|
||||
model: { providerID: "anthropic", modelID: "claude-sonnet-4" },
|
||||
variant: undefined,
|
||||
variant: "",
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -52,6 +52,31 @@ describe("per-session variant selection", () => {
|
||||
expect(getAgentVariant(store, model, { variants: { low: {}, high: {} } }, "ask")).toBe("high")
|
||||
})
|
||||
|
||||
it("falls back to the configured mode variant", () => {
|
||||
expect(getAgentVariant({}, model, { variants: { high: {}, max: {} } }, "code", "max")).toBe("max")
|
||||
})
|
||||
|
||||
it.each(["anthropic/claude-sonnet-4", variantKey(model, "code")])(
|
||||
"prefers the configured variant over the remembered preference %s",
|
||||
(key) => {
|
||||
const store = { [key]: "high" }
|
||||
expect(getVariant(store, model, ["high", "max"], "code", "pending-new", "max")).toBe("max")
|
||||
expect(getAgentVariant(store, model, { variants: { high: {}, max: {} } }, "code", "max")).toBe("max")
|
||||
},
|
||||
)
|
||||
|
||||
it.each(["low", ""])("preserves a session choice %s above configured and remembered variants", (value) => {
|
||||
const store = {
|
||||
[variantKey(model, "code")]: "high",
|
||||
[variantKey(model, "code", "session-a")]: value,
|
||||
}
|
||||
expect(getVariant(store, model, ["low", "high", "max"], "code", "session-a", "max")).toBe(value || undefined)
|
||||
})
|
||||
|
||||
it("ignores a configured variant that the model does not support", () => {
|
||||
expect(getAgentVariant({}, model, { variants: { low: {}, high: {} } }, "code", "max")).toBeUndefined()
|
||||
})
|
||||
|
||||
it("uses the model default when no variant is selected", () => {
|
||||
expect(getVariant({}, model, variants, "code")).toBeUndefined()
|
||||
expect(getVariant({ [variantKey(model, "code")]: "" }, model, variants, "code")).toBeUndefined()
|
||||
|
||||
@@ -4,7 +4,8 @@ import type { ExtensionMessage, ModelSelection } from "../../webview-ui/src/type
|
||||
|
||||
const model: ModelSelection = { providerID: "anthropic", modelID: "claude-sonnet-4" }
|
||||
|
||||
function setup(session?: string) {
|
||||
function setup(session?: string, configured?: string) {
|
||||
const config = { model: "anthropic/claude-sonnet-4", variant: configured }
|
||||
const selections: Record<string, string> = {}
|
||||
const messages: Array<{ type: string; key?: string; value?: string }> = []
|
||||
const order: string[] = []
|
||||
@@ -17,7 +18,8 @@ function setup(session?: string) {
|
||||
selected: () => model,
|
||||
session: () => session,
|
||||
agent: () => "code",
|
||||
find: () => ({ variants: { low: {}, high: {} } }),
|
||||
config: () => config,
|
||||
find: () => ({ variants: { low: {}, high: {}, max: {} } }),
|
||||
post: (message) => {
|
||||
order.push("post")
|
||||
messages.push(message)
|
||||
@@ -28,7 +30,7 @@ function setup(session?: string) {
|
||||
return () => order.push("unsub")
|
||||
},
|
||||
})
|
||||
return { variants, selections, messages, order, dispatch: (message: ExtensionMessage) => handler?.(message) }
|
||||
return { variants, config, selections, messages, order, dispatch: (message: ExtensionMessage) => handler?.(message) }
|
||||
}
|
||||
|
||||
describe("session variants", () => {
|
||||
@@ -51,6 +53,48 @@ describe("session variants", () => {
|
||||
expect(state.selections).toEqual({ "agent/code/anthropic/claude-sonnet-4": "high" })
|
||||
})
|
||||
|
||||
it("uses the configured agent variant when no picker selection exists", () => {
|
||||
const state = setup(undefined, "max")
|
||||
expect(state.variants.agent("code", model)).toBe("max")
|
||||
expect(state.variants.current()).toBe("max")
|
||||
expect(state.variants.request()).toBe("max")
|
||||
})
|
||||
|
||||
it("uses updated configuration ahead of remembered defaults for new tabs", () => {
|
||||
const state = setup("pending-new", "high")
|
||||
state.selections["agent/code/anthropic/claude-sonnet-4"] = "low"
|
||||
expect(state.variants.current()).toBe("high")
|
||||
state.config.variant = "max"
|
||||
expect(state.variants.current()).toBe("max")
|
||||
expect(state.variants.request()).toBe("max")
|
||||
expect(state.variants.agent("code", model)).toBe("max")
|
||||
})
|
||||
|
||||
it("does not apply a configured variant to another model", () => {
|
||||
const state = setup("pending-new", "max")
|
||||
state.config.model = "anthropic/another-model"
|
||||
expect(state.variants.current()).toBeUndefined()
|
||||
expect(state.variants.agent("code", model)).toBeUndefined()
|
||||
})
|
||||
|
||||
it("sends an explicit model default instead of inheriting the configured agent variant", () => {
|
||||
const state = setup("session-a", "max")
|
||||
state.variants.select(undefined)
|
||||
expect(state.variants.current()).toBeUndefined()
|
||||
expect(state.variants.request()).toBe("")
|
||||
expect(state.variants.current("session-b")).toBe("max")
|
||||
expect(state.variants.request("session-b")).toBe("max")
|
||||
})
|
||||
|
||||
it.each(["sidebar-pending:new", "pending:new"])("keeps a pre-submit Default choice scoped to %s", (id) => {
|
||||
const state = setup(undefined, "max")
|
||||
state.variants.select(undefined, id)
|
||||
expect(state.variants.current(id)).toBeUndefined()
|
||||
expect(state.variants.request(id)).toBe("")
|
||||
expect(state.variants.current("another-draft")).toBe("max")
|
||||
expect(state.messages).toEqual([])
|
||||
})
|
||||
|
||||
it("persists global selections but keeps session selections local", () => {
|
||||
const global = setup()
|
||||
global.variants.select("high")
|
||||
|
||||
@@ -23,7 +23,7 @@ import { useServer } from "../src/context/server"
|
||||
import { useSession } from "../src/context/session"
|
||||
import { useProvider } from "../src/context/provider"
|
||||
import { useConfig } from "../src/context/config"
|
||||
import { cycleVariant, preserveVariant } from "../src/context/session-variant-store"
|
||||
import { DEFAULT_VARIANT, cycleVariant, preserveVariant } from "../src/context/session-variant-store"
|
||||
import { ModelSelectorBase } from "../src/components/shared/ModelSelector"
|
||||
import { ModeSwitcherBase } from "../src/components/shared/ModeSwitcher"
|
||||
import { SpeechToTextButton } from "../src/components/speech-to-text/SpeechToTextButton"
|
||||
@@ -184,9 +184,7 @@ export const NewWorktreeDialog: Component<{
|
||||
const [baseBranchOpen, setBaseBranchOpen] = createSignal(false)
|
||||
const [compareOpen, setCompareOpen] = createSignal(false)
|
||||
const [highlightedIndex, setHighlightedIndex] = createSignal(0)
|
||||
const [variant, setVariant] = createSignal<string | undefined>(
|
||||
fallback(saved.variant, () => session.variantForAgent(initialAgent, initialModel)),
|
||||
)
|
||||
const [variant, setVariant] = createSignal<string | undefined>(saved.variant)
|
||||
const [sandbox, setSandbox] = createSignal<boolean | undefined>(saved.sandbox)
|
||||
const [sandboxDefault, setSandboxDefault] = createSignal<boolean | undefined>()
|
||||
const [sandboxOverride, setSandboxOverride] = createSignal<boolean | undefined>()
|
||||
@@ -211,7 +209,7 @@ export const NewWorktreeDialog: Component<{
|
||||
setAgent(name)
|
||||
const sel = session.modelForAgent(name)
|
||||
setModel(sel)
|
||||
setVariant(session.variantForAgent(name, sel))
|
||||
setVariant(undefined)
|
||||
}
|
||||
|
||||
const cycle = (direction: 1 | -1) => {
|
||||
@@ -238,11 +236,10 @@ export const NewWorktreeDialog: Component<{
|
||||
return Object.keys(found.variants)
|
||||
})
|
||||
|
||||
// Current effective variant — an absent or invalid selection uses the model default.
|
||||
const effectiveVariant = createMemo(() => {
|
||||
const list = variants()
|
||||
if (list.length === 0) return undefined
|
||||
const stored = variant()
|
||||
const stored = variant() ?? session.variantForAgent(agent(), model())
|
||||
return stored && list.includes(stored) ? stored : undefined
|
||||
})
|
||||
|
||||
@@ -460,7 +457,7 @@ export const NewWorktreeDialog: Component<{
|
||||
providerID: sel?.providerID,
|
||||
modelID: sel?.modelID,
|
||||
agent: selectedAgent,
|
||||
variant: isCompare ? undefined : effectiveVariant(),
|
||||
variant: isCompare ? undefined : (effectiveVariant() ?? (variants().length > 0 ? DEFAULT_VARIANT : undefined)),
|
||||
baseBranch: effectiveBaseBranch(),
|
||||
branchName: customBranch,
|
||||
modelAllocations: allocations,
|
||||
@@ -513,7 +510,7 @@ export const NewWorktreeDialog: Component<{
|
||||
if (list.length === 0) return
|
||||
const next = cycleVariant(effectiveVariant(), list)
|
||||
e.preventDefault()
|
||||
setVariant(next)
|
||||
setVariant(next ?? DEFAULT_VARIANT)
|
||||
return
|
||||
}
|
||||
undo(e)
|
||||
@@ -860,7 +857,7 @@ export const NewWorktreeDialog: Component<{
|
||||
const next = { providerID: pid, modelID: mid }
|
||||
const list = Object.keys(provider.findModel(next)?.variants ?? {})
|
||||
setModel(next)
|
||||
setVariant(preserveVariant(current, list))
|
||||
setVariant(preserveVariant(current, list) ?? DEFAULT_VARIANT)
|
||||
}}
|
||||
onPick={restorePrompt}
|
||||
onCancel={restorePrompt}
|
||||
@@ -873,7 +870,7 @@ export const NewWorktreeDialog: Component<{
|
||||
variants={variants()}
|
||||
value={effectiveVariant()}
|
||||
onSelect={setVariant}
|
||||
onClear={() => setVariant(undefined)}
|
||||
onClear={() => setVariant(DEFAULT_VARIANT)}
|
||||
allowClear
|
||||
clearLabel={t("common.default")}
|
||||
trigger={WORKTREE_PROMPT_SCOPE}
|
||||
|
||||
@@ -21,7 +21,7 @@ export function initialMessage(ev: AgentManagerSendInitialMessage): SendMessageR
|
||||
}
|
||||
|
||||
export function initialVariant(ev: AgentManagerSendInitialMessage, agent: string) {
|
||||
if (!ev.providerID || !ev.modelID || !ev.variant) return undefined
|
||||
if (!ev.providerID || !ev.modelID || ev.variant === undefined) return undefined
|
||||
return {
|
||||
sessionID: ev.sessionId,
|
||||
providerID: ev.providerID,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Message, ModelSelection } from "../types/messages"
|
||||
import { DEFAULT_VARIANT } from "./session-variant-store"
|
||||
|
||||
export interface MessagePrefs {
|
||||
agent?: string
|
||||
@@ -17,7 +18,7 @@ export function resolveMessagePrefs(messages: Message[], names: Set<string>): Me
|
||||
}
|
||||
if (!prefs.model && msg.role === "user" && msg.model?.providerID && msg.model.modelID) {
|
||||
prefs.model = { providerID: msg.model.providerID, modelID: msg.model.modelID }
|
||||
prefs.variant = msg.model.variant
|
||||
prefs.variant = msg.model.variant ?? DEFAULT_VARIANT
|
||||
}
|
||||
if (prefs.agent && prefs.model) break
|
||||
}
|
||||
|
||||
@@ -39,11 +39,12 @@ export function getVariant(
|
||||
variants: string[],
|
||||
agent: string,
|
||||
session?: string,
|
||||
configured?: string,
|
||||
) {
|
||||
if (variants.length === 0) return undefined
|
||||
const key = variantKey(sel, agent, session)
|
||||
const fallback = session ? store[variantKey(sel, agent)] : undefined
|
||||
const stored = store[key] ?? fallback ?? store[legacyVariantKey(sel)]
|
||||
const scoped = session ? store[variantKey(sel, agent, session)] : undefined
|
||||
const preset = configured && variants.includes(configured) ? configured : undefined
|
||||
const stored = scoped ?? preset ?? store[variantKey(sel, agent)] ?? store[legacyVariantKey(sel)]
|
||||
if (stored === undefined || stored === DEFAULT_VARIANT) return undefined
|
||||
return preserveVariant(stored, variants)
|
||||
}
|
||||
@@ -53,9 +54,10 @@ export function getAgentVariant(
|
||||
sel: ModelSelection,
|
||||
model: { variants?: Record<string, unknown> } | undefined,
|
||||
agent: string,
|
||||
configured?: string,
|
||||
) {
|
||||
if (!model?.variants) return undefined
|
||||
return getVariant(store, sel, Object.keys(model.variants), agent)
|
||||
return getVariant(store, sel, Object.keys(model.variants), agent, undefined, configured)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Accessor } from "solid-js"
|
||||
import type { ExtensionMessage, ModelSelection } from "../types/messages"
|
||||
import type { AgentConfig, ExtensionMessage, ModelSelection } from "../types/messages"
|
||||
import { DEFAULT_VARIANT, getAgentVariant, getVariant, preserveVariant, variantKey } from "./session-variant-store"
|
||||
|
||||
interface Model {
|
||||
@@ -14,6 +14,7 @@ interface Options {
|
||||
selected: (sessionID?: string) => ModelSelection | null
|
||||
session: Accessor<string | undefined>
|
||||
agent: (sessionID?: string) => string
|
||||
config: (agent: string) => Pick<AgentConfig, "model" | "variant"> | undefined
|
||||
find: (selection: ModelSelection) => Model | undefined
|
||||
post: (message: Message) => void
|
||||
listen: (handler: (message: ExtensionMessage) => void) => () => void
|
||||
@@ -26,9 +27,15 @@ export function createSessionVariants(options: Options) {
|
||||
return Object.keys(options.find(selection)?.variants ?? {})
|
||||
}
|
||||
|
||||
const configured = (name: string, selection: ModelSelection) => {
|
||||
const config = options.config(name)
|
||||
if (config?.model !== `${selection.providerID}/${selection.modelID}`) return undefined
|
||||
return config.variant ?? undefined
|
||||
}
|
||||
|
||||
const agent = (name: string, selection: ModelSelection | null) => {
|
||||
if (!selection) return undefined
|
||||
return getAgentVariant(options.selections(), selection, options.find(selection), name)
|
||||
return getAgentVariant(options.selections(), selection, options.find(selection), name, configured(name, selection))
|
||||
}
|
||||
|
||||
const current = (sessionID?: string) => {
|
||||
@@ -37,9 +44,13 @@ export function createSessionVariants(options: Options) {
|
||||
if (!selection) return undefined
|
||||
const variants = list(sid)
|
||||
if (variants.length === 0) return undefined
|
||||
return getVariant(options.selections(), selection, variants, options.agent(sid), sid)
|
||||
const name = options.agent(sid)
|
||||
return getVariant(options.selections(), selection, variants, name, sid, configured(name, selection))
|
||||
}
|
||||
|
||||
const request = (sessionID?: string) =>
|
||||
current(sessionID) ?? (list(sessionID).length > 0 ? DEFAULT_VARIANT : undefined)
|
||||
|
||||
const select = (value: string | undefined, sessionID?: string) => {
|
||||
const sid = sessionID ?? options.session()
|
||||
const selection = options.selected(sid)
|
||||
@@ -75,5 +86,5 @@ export function createSessionVariants(options: Options) {
|
||||
return unsub
|
||||
}
|
||||
|
||||
return { carry, list, agent, current, select, load }
|
||||
return { carry, list, agent, current, request, select, load }
|
||||
}
|
||||
|
||||
@@ -493,6 +493,7 @@ export const SessionProvider: ParentComponent = (props) => {
|
||||
selected,
|
||||
session: currentSessionID,
|
||||
agent: agentForScope,
|
||||
config: (agent) => config().agent?.[agent],
|
||||
find: provider.findModel,
|
||||
post: vscode.postMessage,
|
||||
listen: vscode.onMessage,
|
||||
@@ -1206,7 +1207,7 @@ export const SessionProvider: ParentComponent = (props) => {
|
||||
if (prefs.model && !store.sessionOverrides[sessionID]) {
|
||||
setStore("sessionOverrides", sessionID, prefs.model)
|
||||
}
|
||||
if (prefs.model && prefs.variant) {
|
||||
if (prefs.model && prefs.variant !== undefined) {
|
||||
const agent = prefs.agent ?? store.agentSelections[sessionID] ?? defaultAgent()
|
||||
const key = variantKey(prefs.model, agent, sessionID)
|
||||
if (store.variantSelections[key] === undefined) setStore("variantSelections", key, prefs.variant)
|
||||
@@ -2162,7 +2163,7 @@ export const SessionProvider: ParentComponent = (props) => {
|
||||
providerID,
|
||||
modelID,
|
||||
agent,
|
||||
variant: currentVariant(scope),
|
||||
variant: variants.request(scope),
|
||||
files,
|
||||
review,
|
||||
})
|
||||
@@ -2198,7 +2199,7 @@ export const SessionProvider: ParentComponent = (props) => {
|
||||
providerID,
|
||||
modelID,
|
||||
agent,
|
||||
variant: currentVariant(scope),
|
||||
variant: variants.request(scope),
|
||||
files,
|
||||
review,
|
||||
agentManagerContext: context,
|
||||
@@ -2260,7 +2261,7 @@ export const SessionProvider: ParentComponent = (props) => {
|
||||
providerID: effectiveProvider,
|
||||
modelID: effectiveModel,
|
||||
agent,
|
||||
variant: currentVariant(scope),
|
||||
variant: variants.request(scope),
|
||||
files,
|
||||
command,
|
||||
commandArgs: args,
|
||||
@@ -2296,7 +2297,7 @@ export const SessionProvider: ParentComponent = (props) => {
|
||||
providerID: effectiveProvider,
|
||||
modelID: effectiveModel,
|
||||
agent,
|
||||
variant: currentVariant(scope),
|
||||
variant: variants.request(scope),
|
||||
files,
|
||||
agentManagerContext: context,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user