diff --git a/.changeset/calm-forks-pretend.md b/.changeset/calm-forks-pretend.md new file mode 100644 index 00000000000..b1b91ab8068 --- /dev/null +++ b/.changeset/calm-forks-pretend.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Normalize legacy Claude Code model IDs so dated aliases resolve to canonical model metadata and preserve capabilities (including image support). diff --git a/src/api/providers/__tests__/claude-code.spec.ts b/src/api/providers/__tests__/claude-code.spec.ts index 5b5bdca65ae..00f8242204c 100644 --- a/src/api/providers/__tests__/claude-code.spec.ts +++ b/src/api/providers/__tests__/claude-code.spec.ts @@ -55,6 +55,18 @@ describe("ClaudeCodeHandler", () => { expect(model.id).toBe("claude-sonnet-4-5") // default model }) + test("should normalize legacy dated model ids to canonical claude-code models", () => { + const options: ApiHandlerOptions = { + apiModelId: "claude-opus-4-5-20251101", + } + const handlerWithLegacyModel = new ClaudeCodeHandler(options) + const model = handlerWithLegacyModel.getModel() + + expect(model.id).toBe("claude-opus-4-5") + expect(model.info.supportsImages).toBe(true) + expect(model.info.supportsPromptCache).toBe(true) + }) + test("should return model maxTokens from model definition", () => { const options: ApiHandlerOptions = { apiModelId: "claude-opus-4-5", diff --git a/src/api/providers/claude-code.ts b/src/api/providers/claude-code.ts index f2bccc329c7..86f32679a85 100644 --- a/src/api/providers/claude-code.ts +++ b/src/api/providers/claude-code.ts @@ -7,6 +7,7 @@ import { claudeCodeReasoningConfig, type ClaudeCodeReasoningLevel, type ModelInfo, + normalizeClaudeCodeModelId, } from "@roo-code/types" import { type ApiHandler, ApiHandlerCreateMessageMetadata, type SingleCompletionHandler } from ".." import { ApiStreamUsageChunk, type ApiStream } from "../transform/stream" @@ -303,9 +304,9 @@ export class ClaudeCodeHandler implements ApiHandler, SingleCompletionHandler { } getModel(): { id: string; info: ModelInfo } { - const modelId = this.options.apiModelId - if (modelId && Object.hasOwn(claudeCodeModels, modelId)) { - const id = modelId as ClaudeCodeModelId + const modelId = this.options.apiModelId?.trim() + if (modelId) { + const id = normalizeClaudeCodeModelId(modelId) as ClaudeCodeModelId return { id, info: { ...claudeCodeModels[id] } } } diff --git a/webview-ui/src/components/ui/hooks/__tests__/useSelectedModel.spec.ts b/webview-ui/src/components/ui/hooks/__tests__/useSelectedModel.spec.ts index cac60e2b110..1ef7e9241c9 100644 --- a/webview-ui/src/components/ui/hooks/__tests__/useSelectedModel.spec.ts +++ b/webview-ui/src/components/ui/hooks/__tests__/useSelectedModel.spec.ts @@ -540,6 +540,39 @@ describe("useSelectedModel", () => { expect(result.current.info).toBeDefined() expect(result.current.info?.supportsImages).toBe(true) // Claude Code now supports images }) + + it("should normalize dated claude-code model ids and keep image support enabled", () => { + mockUseRouterModels.mockReturnValue({ + data: { + openrouter: {}, + requesty: {}, + glama: {}, + unbound: {}, + litellm: {}, + "io-intelligence": {}, + }, + isLoading: false, + isError: false, + } as any) + + mockUseOpenRouterModelProviders.mockReturnValue({ + data: {}, + isLoading: false, + isError: false, + } as any) + + const apiConfiguration: ProviderSettings = { + apiProvider: "claude-code", + apiModelId: "claude-opus-4-5-20251101", + } + + const wrapper = createWrapper() + const { result } = renderHook(() => useSelectedModel(apiConfiguration), { wrapper }) + + expect(result.current.provider).toBe("claude-code") + expect(result.current.id).toBe("claude-opus-4-5") + expect(result.current.info?.supportsImages).toBe(true) + }) }) // kilocode_change start