From d672eb1c12ece1b02ca696f66ab94998c434b76b Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Tue, 17 Feb 2026 01:28:27 +0900 Subject: [PATCH] fix: recognize google-vertex-anthropic as Claude provider (#1700) --- src/hooks/anthropic-effort/hook.ts | 2 +- src/hooks/anthropic-effort/index.test.ts | 15 ++++++++++ src/hooks/context-window-monitor.test.ts | 36 +++++++++++++++++++++++ src/hooks/context-window-monitor.ts | 6 +++- src/hooks/preemptive-compaction.test.ts | 37 ++++++++++++++++++++++++ src/hooks/preemptive-compaction.ts | 6 +++- src/hooks/think-mode/index.test.ts | 21 ++++++++++++++ src/hooks/think-mode/switcher.test.ts | 29 +++++++++++++++++++ src/hooks/think-mode/switcher.ts | 8 +++++ 9 files changed, 157 insertions(+), 3 deletions(-) diff --git a/src/hooks/anthropic-effort/hook.ts b/src/hooks/anthropic-effort/hook.ts index 141933cb2..06a754d23 100644 --- a/src/hooks/anthropic-effort/hook.ts +++ b/src/hooks/anthropic-effort/hook.ts @@ -7,7 +7,7 @@ function normalizeModelID(modelID: string): string { } function isClaudeProvider(providerID: string, modelID: string): boolean { - if (["anthropic", "opencode"].includes(providerID)) return true + if (["anthropic", "google-vertex-anthropic", "opencode"].includes(providerID)) return true if (providerID === "github-copilot" && modelID.toLowerCase().includes("claude")) return true return false } diff --git a/src/hooks/anthropic-effort/index.test.ts b/src/hooks/anthropic-effort/index.test.ts index be965c0a9..13daaea5b 100644 --- a/src/hooks/anthropic-effort/index.test.ts +++ b/src/hooks/anthropic-effort/index.test.ts @@ -88,6 +88,21 @@ describe("createAnthropicEffortHook", () => { expect(output.options.effort).toBe("max") }) + it("should inject effort max for google-vertex-anthropic provider", async () => { + //#given google-vertex-anthropic provider with claude-opus-4-6 + const hook = createAnthropicEffortHook() + const { input, output } = createMockParams({ + providerID: "google-vertex-anthropic", + modelID: "claude-opus-4-6", + }) + + //#when chat.params hook is called + await hook["chat.params"](input, output) + + //#then effort should be injected + expect(output.options.effort).toBe("max") + }) + it("should handle normalized model ID with dots (opus-4.6)", async () => { //#given model ID with dots instead of hyphens const hook = createAnthropicEffortHook() diff --git a/src/hooks/context-window-monitor.test.ts b/src/hooks/context-window-monitor.test.ts index 6d3d56d6b..e2252fd02 100644 --- a/src/hooks/context-window-monitor.test.ts +++ b/src/hooks/context-window-monitor.test.ts @@ -113,6 +113,42 @@ describe("context-window-monitor", () => { expect(ctx.client.session.messages).not.toHaveBeenCalled() }) + it("should append context reminder for google-vertex-anthropic provider", async () => { + //#given cached usage for google-vertex-anthropic above threshold + const hook = createContextWindowMonitorHook(ctx as never) + const sessionID = "ses_vertex_anthropic_high_usage" + + await hook.event({ + event: { + type: "message.updated", + properties: { + info: { + role: "assistant", + sessionID, + providerID: "google-vertex-anthropic", + finish: true, + tokens: { + input: 150000, + output: 1000, + reasoning: 0, + cache: { read: 10000, write: 0 }, + }, + }, + }, + }, + }) + + //#when tool.execute.after runs + const output = { title: "", output: "original", metadata: null } + await hook["tool.execute.after"]( + { tool: "bash", sessionID, callID: "call_1" }, + output + ) + + //#then context reminder should be appended + expect(output.output).toContain("context remaining") + }) + // #given session is deleted // #when session.deleted event fires // #then cached data should be cleaned up diff --git a/src/hooks/context-window-monitor.ts b/src/hooks/context-window-monitor.ts index 83b29123d..e0caf0d1c 100644 --- a/src/hooks/context-window-monitor.ts +++ b/src/hooks/context-window-monitor.ts @@ -27,6 +27,10 @@ interface CachedTokenState { tokens: TokenInfo } +function isAnthropicProvider(providerID: string): boolean { + return providerID === "anthropic" || providerID === "google-vertex-anthropic" +} + export function createContextWindowMonitorHook(_ctx: PluginInput) { const remindedSessions = new Set() const tokenCache = new Map() @@ -42,7 +46,7 @@ export function createContextWindowMonitorHook(_ctx: PluginInput) { const cached = tokenCache.get(sessionID) if (!cached) return - if (cached.providerID !== "anthropic") return + if (!isAnthropicProvider(cached.providerID)) return const lastTokens = cached.tokens const totalInputTokens = (lastTokens?.input ?? 0) + (lastTokens?.cache?.read ?? 0) diff --git a/src/hooks/preemptive-compaction.test.ts b/src/hooks/preemptive-compaction.test.ts index 4ef001e36..c6c38fa5f 100644 --- a/src/hooks/preemptive-compaction.test.ts +++ b/src/hooks/preemptive-compaction.test.ts @@ -123,6 +123,43 @@ describe("preemptive-compaction", () => { expect(ctx.client.session.summarize).toHaveBeenCalled() }) + it("should trigger compaction for google-vertex-anthropic provider", async () => { + //#given google-vertex-anthropic usage above threshold + const hook = createPreemptiveCompactionHook(ctx as never) + const sessionID = "ses_vertex_anthropic_high" + + await hook.event({ + event: { + type: "message.updated", + properties: { + info: { + role: "assistant", + sessionID, + providerID: "google-vertex-anthropic", + modelID: "claude-sonnet-4-5", + finish: true, + tokens: { + input: 170000, + output: 1000, + reasoning: 0, + cache: { read: 10000, write: 0 }, + }, + }, + }, + }, + }) + + //#when tool.execute.after runs + const output = { title: "", output: "test", metadata: null } + await hook["tool.execute.after"]( + { tool: "bash", sessionID, callID: "call_1" }, + output + ) + + //#then summarize should be triggered + expect(ctx.client.session.summarize).toHaveBeenCalled() + }) + // #given session deleted // #then cache should be cleaned up it("should clean up cache on session.deleted", async () => { diff --git a/src/hooks/preemptive-compaction.ts b/src/hooks/preemptive-compaction.ts index fd617ccf9..a3f76914d 100644 --- a/src/hooks/preemptive-compaction.ts +++ b/src/hooks/preemptive-compaction.ts @@ -23,6 +23,10 @@ interface CachedCompactionState { tokens: TokenInfo } +function isAnthropicProvider(providerID: string): boolean { + return providerID === "anthropic" || providerID === "google-vertex-anthropic" +} + type PluginInput = { client: { session: { @@ -55,7 +59,7 @@ export function createPreemptiveCompactionHook(ctx: PluginInput) { if (!cached) return const actualLimit = - cached.providerID === "anthropic" + isAnthropicProvider(cached.providerID) ? ANTHROPIC_ACTUAL_LIMIT : DEFAULT_ACTUAL_LIMIT diff --git a/src/hooks/think-mode/index.test.ts b/src/hooks/think-mode/index.test.ts index 5641eb40e..25613cbbd 100644 --- a/src/hooks/think-mode/index.test.ts +++ b/src/hooks/think-mode/index.test.ts @@ -214,6 +214,27 @@ describe("createThinkModeHook integration", () => { expect(message.thinking).toBeDefined() }) + it("should work for direct google-vertex-anthropic provider", async () => { + //#given direct google-vertex-anthropic provider + const hook = createThinkModeHook() + const input = createMockInput( + "google-vertex-anthropic", + "claude-opus-4-6", + "think deeply" + ) + + //#when the chat.params hook is called + await hook["chat.params"](input, sessionID) + + //#then should upgrade model and inject Claude thinking config + const message = input.message as MessageWithInjectedProps + expect(input.message.model?.modelID).toBe("claude-opus-4-6-high") + expect(message.thinking).toBeDefined() + expect((message.thinking as Record)?.budgetTokens).toBe( + 64000 + ) + }) + it("should still work for direct google provider", async () => { // given direct google provider const hook = createThinkModeHook() diff --git a/src/hooks/think-mode/switcher.test.ts b/src/hooks/think-mode/switcher.test.ts index dc968dbfc..5f4c73afd 100644 --- a/src/hooks/think-mode/switcher.test.ts +++ b/src/hooks/think-mode/switcher.test.ts @@ -266,6 +266,24 @@ describe("think-mode switcher", () => { expect((config?.thinking as Record)?.type).toBe("enabled") }) + it("should work for direct google-vertex-anthropic provider", () => { + //#given direct google-vertex-anthropic provider + const config = getThinkingConfig( + "google-vertex-anthropic", + "claude-opus-4-6" + ) + + //#when thinking config is resolved + + //#then it should return anthropic-style thinking config + expect(config).not.toBeNull() + expect(config?.thinking).toBeDefined() + expect((config?.thinking as Record)?.type).toBe("enabled") + expect((config?.thinking as Record)?.budgetTokens).toBe( + 64000 + ) + }) + it("should still work for direct google provider", () => { // given direct google provider const config = getThinkingConfig("google", "gemini-3-pro") @@ -314,6 +332,17 @@ describe("think-mode switcher", () => { expect(config.maxTokens).toBe(128000) }) + it("should have correct structure for google-vertex-anthropic", () => { + //#given google-vertex-anthropic config entry + const config = THINKING_CONFIGS["google-vertex-anthropic"] + + //#when structure is validated + + //#then it should match anthropic style structure + expect(config.thinking).toBeDefined() + expect(config.maxTokens).toBe(128000) + }) + it("should have correct structure for google", () => { const config = THINKING_CONFIGS.google expect(config.providerOptions).toBeDefined() diff --git a/src/hooks/think-mode/switcher.ts b/src/hooks/think-mode/switcher.ts index b17fddec3..a39860163 100644 --- a/src/hooks/think-mode/switcher.ts +++ b/src/hooks/think-mode/switcher.ts @@ -121,6 +121,13 @@ export const THINKING_CONFIGS = { }, maxTokens: 128000, }, + "google-vertex-anthropic": { + thinking: { + type: "enabled", + budgetTokens: 64000, + }, + maxTokens: 128000, + }, "amazon-bedrock": { reasoningConfig: { type: "enabled", @@ -164,6 +171,7 @@ export const THINKING_CONFIGS = { const THINKING_CAPABLE_MODELS = { anthropic: ["claude-sonnet-4", "claude-opus-4", "claude-3"], + "google-vertex-anthropic": ["claude-sonnet-4", "claude-opus-4", "claude-3"], "amazon-bedrock": ["claude", "anthropic"], google: ["gemini-2", "gemini-3"], "google-vertex": ["gemini-2", "gemini-3"],