diff --git a/src/hooks/anthropic-effort/hook.ts b/src/hooks/anthropic-effort/hook.ts index ef6c98529..47c4d7c87 100644 --- a/src/hooks/anthropic-effort/hook.ts +++ b/src/hooks/anthropic-effort/hook.ts @@ -1,6 +1,6 @@ import { log, normalizeModelID } from "../../shared" -const OPUS_4_6_PATTERN = /claude-opus-4[-.]6/i +const OPUS_PATTERN = /claude-opus/i function isClaudeProvider(providerID: string, modelID: string): boolean { if (["anthropic", "google-vertex-anthropic", "opencode"].includes(providerID)) return true @@ -8,9 +8,9 @@ function isClaudeProvider(providerID: string, modelID: string): boolean { return false } -function isOpus46(modelID: string): boolean { +function isOpusModel(modelID: string): boolean { const normalized = normalizeModelID(modelID) - return OPUS_4_6_PATTERN.test(normalized) + return OPUS_PATTERN.test(normalized) } interface ChatParamsInput { @@ -54,7 +54,7 @@ export function createAnthropicEffortHook() { if (!isClaudeProvider(model.providerID, model.modelID)) return if (output.options.effort !== undefined) return - const opus = isOpus46(model.modelID) + const opus = isOpusModel(model.modelID) const clamped = clampVariant(message.variant, opus) output.options.effort = clamped diff --git a/src/hooks/anthropic-effort/index.test.ts b/src/hooks/anthropic-effort/index.test.ts index 666a5c2ac..a82665cdc 100644 --- a/src/hooks/anthropic-effort/index.test.ts +++ b/src/hooks/anthropic-effort/index.test.ts @@ -116,6 +116,21 @@ describe("createAnthropicEffortHook", () => { //#then should normalize and inject effort expect(output.options.effort).toBe("max") }) + + it("should preserve max for other opus model IDs such as opus-4-5", async () => { + //#given another opus model id that is not 4.6 + const hook = createAnthropicEffortHook() + const { input, output } = createMockParams({ + modelID: "claude-opus-4-5", + }) + + //#when chat.params hook is called + await hook["chat.params"](input, output) + + //#then max should still be treated as valid for opus family + expect(output.options.effort).toBe("max") + expect(input.message.variant).toBe("max") + }) }) describe("conditions NOT met - should skip", () => { diff --git a/src/plugin/chat-params.test.ts b/src/plugin/chat-params.test.ts index 91d194b9e..4abcfbe93 100644 --- a/src/plugin/chat-params.test.ts +++ b/src/plugin/chat-params.test.ts @@ -35,4 +35,37 @@ describe("createChatParamsHandler", () => { //#then expect(called).toBe(true) }) + + test("passes the original mutable message object to chat.params hooks", async () => { + //#given + const handler = createChatParamsHandler({ + anthropicEffort: { + "chat.params": async (input) => { + input.message.variant = "high" + }, + }, + }) + + const message = { variant: "max" } + const input = { + sessionID: "ses_chat_params", + agent: { name: "sisyphus" }, + model: { providerID: "opencode", modelID: "claude-sonnet-4-6" }, + provider: { id: "opencode" }, + message, + } + + const output = { + temperature: 0.1, + topP: 1, + topK: 1, + options: {}, + } + + //#when + await handler(input, output) + + //#then + expect(message.variant).toBe("high") + }) }) diff --git a/src/plugin/chat-params.ts b/src/plugin/chat-params.ts index 14ff4ed8e..c4bd5e626 100644 --- a/src/plugin/chat-params.ts +++ b/src/plugin/chat-params.ts @@ -6,6 +6,10 @@ export type ChatParamsInput = { message: { variant?: string } } +type ChatParamsHookInput = ChatParamsInput & { + rawMessage?: Record +} + export type ChatParamsOutput = { temperature?: number topP?: number @@ -17,7 +21,7 @@ function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null } -function buildChatParamsInput(raw: unknown): ChatParamsInput | null { +function buildChatParamsInput(raw: unknown): ChatParamsHookInput | null { if (!isRecord(raw)) return null const sessionID = raw.sessionID @@ -56,7 +60,9 @@ function buildChatParamsInput(raw: unknown): ChatParamsInput | null { agent: { name: agentName }, model: { providerID, modelID }, provider: { id: providerId }, - message: typeof variant === "string" ? { variant } : {}, + message, + rawMessage: message, + ...(typeof variant === "string" ? {} : {}), } } @@ -69,7 +75,7 @@ function isChatParamsOutput(raw: unknown): raw is ChatParamsOutput { } export function createChatParamsHandler(args: { - anthropicEffort: { "chat.params"?: (input: ChatParamsInput, output: ChatParamsOutput) => Promise } | null + anthropicEffort: { "chat.params"?: (input: ChatParamsHookInput, output: ChatParamsOutput) => Promise } | null }): (input: unknown, output: unknown) => Promise { return async (input, output): Promise => { const normalizedInput = buildChatParamsInput(input)