From df5e415ef5959aece944cd537621050a9638779c Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Mon, 22 Dec 2025 23:23:33 -0700 Subject: [PATCH] feat: add support for Claude/Anthropic models in applyRouterToolPreferences function --- .../providers/__tests__/openrouter.spec.ts | 6 +- .../__tests__/router-tool-preferences.spec.ts | 173 ++++++++++++++++++ .../utils/router-tool-preferences.ts | 9 + 3 files changed, 185 insertions(+), 3 deletions(-) create mode 100644 src/api/providers/utils/__tests__/router-tool-preferences.spec.ts diff --git a/src/api/providers/__tests__/openrouter.spec.ts b/src/api/providers/__tests__/openrouter.spec.ts index c08405775f..330022af5b 100644 --- a/src/api/providers/__tests__/openrouter.spec.ts +++ b/src/api/providers/__tests__/openrouter.spec.ts @@ -187,7 +187,7 @@ describe("OpenRouterHandler", () => { expect(result.info.includedTools).toContain("existing_included") }) - it("does not set editToolVariant for non-OpenAI models", async () => { + it("sets editToolVariant to anthropic for Claude models", async () => { const handler = new OpenRouterHandler({ openRouterApiKey: "test-key", openRouterModelId: "anthropic/claude-sonnet-4", @@ -195,8 +195,8 @@ describe("OpenRouterHandler", () => { const result = await handler.fetchModel() expect(result.id).toBe("anthropic/claude-sonnet-4") - // Should NOT have editToolVariant set - expect(result.info.editToolVariant).toBeUndefined() + // Should have editToolVariant set to anthropic + expect(result.info.editToolVariant).toBe("anthropic") }) }) diff --git a/src/api/providers/utils/__tests__/router-tool-preferences.spec.ts b/src/api/providers/utils/__tests__/router-tool-preferences.spec.ts new file mode 100644 index 0000000000..8f18bc2f8e --- /dev/null +++ b/src/api/providers/utils/__tests__/router-tool-preferences.spec.ts @@ -0,0 +1,173 @@ +// npx vitest run api/providers/utils/__tests__/router-tool-preferences.spec.ts + +import type { ModelInfo } from "@roo-code/types" +import { applyRouterToolPreferences } from "../router-tool-preferences" + +describe("applyRouterToolPreferences", () => { + const baseModelInfo: ModelInfo = { + maxTokens: 4096, + contextWindow: 128000, + supportsImages: true, + supportsPromptCache: false, + } + + describe("OpenAI models", () => { + it("should apply codex variant and exclude write_to_file for openai models", () => { + const result = applyRouterToolPreferences("openai/gpt-4", baseModelInfo) + + expect(result.editToolVariant).toBe("codex") + expect(result.excludedTools).toContain("write_to_file") + }) + + it("should not override existing editToolVariant for openai models", () => { + const info: ModelInfo = { ...baseModelInfo, editToolVariant: "gemini" } + const result = applyRouterToolPreferences("openai/gpt-4", info) + + expect(result.editToolVariant).toBe("gemini") + }) + + it("should preserve existing excludedTools and add write_to_file", () => { + const info: ModelInfo = { ...baseModelInfo, excludedTools: ["some_tool"] } + const result = applyRouterToolPreferences("openai/gpt-4", info) + + expect(result.excludedTools).toContain("some_tool") + expect(result.excludedTools).toContain("write_to_file") + }) + + it("should not duplicate write_to_file in excludedTools", () => { + const info: ModelInfo = { ...baseModelInfo, excludedTools: ["write_to_file"] } + const result = applyRouterToolPreferences("openai/gpt-4", info) + + expect(result.excludedTools?.filter((t) => t === "write_to_file").length).toBe(1) + }) + }) + + describe("Gemini models", () => { + it("should apply gemini variant and include write_file for gemini models", () => { + const result = applyRouterToolPreferences("google/gemini-2.5-pro", baseModelInfo) + + expect(result.editToolVariant).toBe("gemini") + expect(result.includedTools).toContain("write_file") + }) + + it("should not override existing editToolVariant for gemini models", () => { + const info: ModelInfo = { ...baseModelInfo, editToolVariant: "codex" } + const result = applyRouterToolPreferences("google/gemini-2.5-pro", info) + + expect(result.editToolVariant).toBe("codex") + }) + + it("should preserve existing includedTools and add write_file", () => { + const info: ModelInfo = { ...baseModelInfo, includedTools: ["some_tool"] } + const result = applyRouterToolPreferences("google/gemini-2.5-pro", info) + + expect(result.includedTools).toContain("some_tool") + expect(result.includedTools).toContain("write_file") + }) + + it("should not duplicate write_file in includedTools", () => { + const info: ModelInfo = { ...baseModelInfo, includedTools: ["write_file"] } + const result = applyRouterToolPreferences("google/gemini-2.5-pro", info) + + expect(result.includedTools?.filter((t) => t === "write_file").length).toBe(1) + }) + }) + + describe("xAI/Grok models", () => { + it("should apply grok variant for grok models", () => { + const result = applyRouterToolPreferences("xai/grok-2", baseModelInfo) + + expect(result.editToolVariant).toBe("grok") + }) + + it("should apply grok variant for models containing xai", () => { + const result = applyRouterToolPreferences("xai/grok-beta", baseModelInfo) + + expect(result.editToolVariant).toBe("grok") + }) + + it("should not override existing editToolVariant for grok models", () => { + const info: ModelInfo = { ...baseModelInfo, editToolVariant: "codex" } + const result = applyRouterToolPreferences("xai/grok-2", info) + + expect(result.editToolVariant).toBe("codex") + }) + }) + + describe("Claude/Anthropic models", () => { + it("should apply anthropic variant for claude models", () => { + const result = applyRouterToolPreferences("anthropic/claude-3.5-sonnet", baseModelInfo) + + expect(result.editToolVariant).toBe("anthropic") + }) + + it("should apply anthropic variant for models containing anthropic", () => { + const result = applyRouterToolPreferences("anthropic/claude-3-opus", baseModelInfo) + + expect(result.editToolVariant).toBe("anthropic") + }) + + it("should apply anthropic variant for models containing claude", () => { + const result = applyRouterToolPreferences("openrouter/claude-3-haiku", baseModelInfo) + + expect(result.editToolVariant).toBe("anthropic") + }) + + it("should not override existing editToolVariant for claude models", () => { + const info: ModelInfo = { ...baseModelInfo, editToolVariant: "codex" } + const result = applyRouterToolPreferences("anthropic/claude-3.5-sonnet", info) + + expect(result.editToolVariant).toBe("codex") + }) + }) + + describe("Unknown models", () => { + it("should not modify model info for unknown models", () => { + const result = applyRouterToolPreferences("some-provider/unknown-model", baseModelInfo) + + expect(result).toEqual(baseModelInfo) + }) + + it("should preserve all original properties for unknown models", () => { + const info: ModelInfo = { + ...baseModelInfo, + editToolVariant: "roo", + excludedTools: ["tool1"], + includedTools: ["tool2"], + } + const result = applyRouterToolPreferences("some-provider/unknown-model", info) + + expect(result).toEqual(info) + }) + }) + + describe("Edge cases", () => { + it("should handle empty modelId", () => { + const result = applyRouterToolPreferences("", baseModelInfo) + + expect(result).toEqual(baseModelInfo) + }) + + it("should handle modelId with multiple matching patterns (openai takes precedence via order)", () => { + // This is a contrived case - in reality modelIds wouldn't contain multiple provider names + const result = applyRouterToolPreferences("openai-gemini-hybrid", baseModelInfo) + + // openai matches first, then gemini adds its modifications + expect(result.editToolVariant).toBe("codex") // openai sets this first, gemini doesn't override + expect(result.excludedTools).toContain("write_to_file") + expect(result.includedTools).toContain("write_file") + }) + + it("should preserve other ModelInfo properties", () => { + const info: ModelInfo = { + ...baseModelInfo, + description: "Test model", + supportsNativeTools: true, + } + const result = applyRouterToolPreferences("openai/gpt-4", info) + + expect(result.description).toBe("Test model") + expect(result.supportsNativeTools).toBe(true) + }) + }) +}) diff --git a/src/api/providers/utils/router-tool-preferences.ts b/src/api/providers/utils/router-tool-preferences.ts index 6ee63130c9..369f305828 100644 --- a/src/api/providers/utils/router-tool-preferences.ts +++ b/src/api/providers/utils/router-tool-preferences.ts @@ -47,5 +47,14 @@ export function applyRouterToolPreferences(modelId: string, info: ModelInfo): Mo } } + // For Claude/Anthropic models via routers, use anthropic variant + // This matches the behavior of the native Anthropic provider + if (modelId.includes("claude") || modelId.includes("anthropic")) { + result = { + ...result, + editToolVariant: result.editToolVariant ?? "anthropic", + } + } + return result }