fix: preserve user-selected variant on first message instead of overriding with fallback chain default
First message variant gate was unconditionally overwriting message.variant with the fallback chain value (e.g. 'medium' for Hephaestus), ignoring any variant the user had already selected via OpenCode UI. Now checks message.variant === undefined before applying the resolved variant, matching the behavior already used for subsequent messages. Closes #1861
This commit is contained in:
118
src/plugin/chat-message.test.ts
Normal file
118
src/plugin/chat-message.test.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { describe, test, expect } from "bun:test"
|
||||
|
||||
import { createChatMessageHandler } from "./chat-message"
|
||||
|
||||
type ChatMessagePart = { type: string; text?: string; [key: string]: unknown }
|
||||
type ChatMessageHandlerOutput = { message: Record<string, unknown>; parts: ChatMessagePart[] }
|
||||
|
||||
function createMockHandlerArgs(overrides?: {
|
||||
pluginConfig?: Record<string, unknown>
|
||||
shouldOverride?: boolean
|
||||
}) {
|
||||
const appliedSessions: string[] = []
|
||||
return {
|
||||
ctx: { client: { tui: { showToast: async () => {} } } } as any,
|
||||
pluginConfig: (overrides?.pluginConfig ?? {}) as any,
|
||||
firstMessageVariantGate: {
|
||||
shouldOverride: () => overrides?.shouldOverride ?? false,
|
||||
markApplied: (sessionID: string) => { appliedSessions.push(sessionID) },
|
||||
},
|
||||
hooks: {
|
||||
stopContinuationGuard: null,
|
||||
keywordDetector: null,
|
||||
claudeCodeHooks: null,
|
||||
autoSlashCommand: null,
|
||||
startWork: null,
|
||||
ralphLoop: null,
|
||||
} as any,
|
||||
_appliedSessions: appliedSessions,
|
||||
}
|
||||
}
|
||||
|
||||
function createMockInput(agent?: string, model?: { providerID: string; modelID: string }) {
|
||||
return {
|
||||
sessionID: "test-session",
|
||||
agent,
|
||||
model,
|
||||
}
|
||||
}
|
||||
|
||||
function createMockOutput(variant?: string): ChatMessageHandlerOutput {
|
||||
const message: Record<string, unknown> = {}
|
||||
if (variant !== undefined) {
|
||||
message["variant"] = variant
|
||||
}
|
||||
return { message, parts: [] }
|
||||
}
|
||||
|
||||
describe("createChatMessageHandler - first message variant", () => {
|
||||
test("first message: sets variant from fallback chain when user has no selection", async () => {
|
||||
//#given - first message, no user-selected variant, hephaestus with medium in chain
|
||||
const args = createMockHandlerArgs({ shouldOverride: true })
|
||||
const handler = createChatMessageHandler(args)
|
||||
const input = createMockInput("hephaestus", { providerID: "openai", modelID: "gpt-5.3-codex" })
|
||||
const output = createMockOutput() // no variant set
|
||||
|
||||
//#when
|
||||
await handler(input, output)
|
||||
|
||||
//#then - should set variant from fallback chain
|
||||
expect(output.message["variant"]).toBeDefined()
|
||||
})
|
||||
|
||||
test("first message: preserves user-selected variant when already set", async () => {
|
||||
//#given - first message, user already selected "xhigh" variant in OpenCode UI
|
||||
const args = createMockHandlerArgs({ shouldOverride: true })
|
||||
const handler = createChatMessageHandler(args)
|
||||
const input = createMockInput("hephaestus", { providerID: "openai", modelID: "gpt-5.3-codex" })
|
||||
const output = createMockOutput("xhigh") // user selected xhigh
|
||||
|
||||
//#when
|
||||
await handler(input, output)
|
||||
|
||||
//#then - user's xhigh must be preserved, not overwritten to "medium"
|
||||
expect(output.message["variant"]).toBe("xhigh")
|
||||
})
|
||||
|
||||
test("first message: preserves user-selected 'high' variant", async () => {
|
||||
//#given - user selected "high" variant
|
||||
const args = createMockHandlerArgs({ shouldOverride: true })
|
||||
const handler = createChatMessageHandler(args)
|
||||
const input = createMockInput("hephaestus", { providerID: "openai", modelID: "gpt-5.3-codex" })
|
||||
const output = createMockOutput("high")
|
||||
|
||||
//#when
|
||||
await handler(input, output)
|
||||
|
||||
//#then
|
||||
expect(output.message["variant"]).toBe("high")
|
||||
})
|
||||
|
||||
test("subsequent message: does not override existing variant", async () => {
|
||||
//#given - not first message, variant already set
|
||||
const args = createMockHandlerArgs({ shouldOverride: false })
|
||||
const handler = createChatMessageHandler(args)
|
||||
const input = createMockInput("hephaestus", { providerID: "openai", modelID: "gpt-5.3-codex" })
|
||||
const output = createMockOutput("xhigh")
|
||||
|
||||
//#when
|
||||
await handler(input, output)
|
||||
|
||||
//#then
|
||||
expect(output.message["variant"]).toBe("xhigh")
|
||||
})
|
||||
|
||||
test("first message: marks gate as applied regardless of variant presence", async () => {
|
||||
//#given - first message with user-selected variant
|
||||
const args = createMockHandlerArgs({ shouldOverride: true })
|
||||
const handler = createChatMessageHandler(args)
|
||||
const input = createMockInput("hephaestus", { providerID: "openai", modelID: "gpt-5.3-codex" })
|
||||
const output = createMockOutput("xhigh")
|
||||
|
||||
//#when
|
||||
await handler(input, output)
|
||||
|
||||
//#then - gate should still be marked as applied
|
||||
expect(args._appliedSessions).toContain("test-session")
|
||||
})
|
||||
})
|
||||
@@ -56,12 +56,14 @@ export function createChatMessageHandler(args: {
|
||||
const message = output.message
|
||||
|
||||
if (firstMessageVariantGate.shouldOverride(input.sessionID)) {
|
||||
const variant =
|
||||
input.model && input.agent
|
||||
? resolveVariantForModel(pluginConfig, input.agent, input.model)
|
||||
: resolveAgentVariant(pluginConfig, input.agent)
|
||||
if (variant !== undefined) {
|
||||
message["variant"] = variant
|
||||
if (message["variant"] === undefined) {
|
||||
const variant =
|
||||
input.model && input.agent
|
||||
? resolveVariantForModel(pluginConfig, input.agent, input.model)
|
||||
: resolveAgentVariant(pluginConfig, input.agent)
|
||||
if (variant !== undefined) {
|
||||
message["variant"] = variant
|
||||
}
|
||||
}
|
||||
firstMessageVariantGate.markApplied(input.sessionID)
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user