Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/fix-case-insensitive-model-search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"webview-ui": patch
---

Fix case-insensitive model search in ModelPicker

Users can now search for models regardless of casing. For example, searching for "kimi k2.5" will find models like "Kimi-K2.5-Instruct". This fixes model discovery issues when using Azure Cognitive Services or other OpenAI-compatible providers that return models with different casing.

Before: Search was case-sensitive, making it hard to find models
After: Search is case-insensitive for better discoverability
5 changes: 5 additions & 0 deletions .changeset/fix-kimi-model-search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---

Fix Kimi model search and add Kimi models as fallback for OpenAI Compatible provider
34 changes: 27 additions & 7 deletions webview-ui/src/components/settings/ModelPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,21 @@ export const ModelPicker = ({

const [searchValue, setSearchValue] = useState("")

// kilocode_change: Case-insensitive search with dash/space normalization
const normalizeForSearch = (str: string) => str.toLowerCase().replace(/[-_\s]/g, "")

const filteredPreferredIds = useMemo(() => {
if (!searchValue.trim()) return preferredModelIds
const searchNormalized = normalizeForSearch(searchValue)
return preferredModelIds.filter((id) => normalizeForSearch(id).includes(searchNormalized))
}, [preferredModelIds, searchValue])

const filteredRestIds = useMemo(() => {
if (!searchValue.trim()) return restModelIds
const searchNormalized = normalizeForSearch(searchValue)
return restModelIds.filter((id) => normalizeForSearch(id).includes(searchNormalized))
}, [restModelIds, searchValue])
Comment on lines +102 to +114
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The normalizeForSearch function is redefined on every render, which causes the useMemo hooks to re-evaluate unnecessarily. This negates the performance benefits of memoization. Additionally, the filtering logic for preferredModelIds and restModelIds is duplicated.

To improve performance and maintainability, you should:

  1. Wrap normalizeForSearch in useCallback to ensure it's stable across renders.
  2. Combine the two useMemo hooks into a single one to avoid code duplication.
	const normalizeForSearch = useCallback((str: string) => str.toLowerCase().replace(/[-_\s]/g, ""), [])

	const { filteredPreferredIds, filteredRestIds } = useMemo(() => {
		if (!searchValue.trim()) {
			return { filteredPreferredIds: preferredModelIds, filteredRestIds: restModelIds }
		}
		const searchNormalized = normalizeForSearch(searchValue)
		const filterModels = (ids: string[]) =>
			ids.filter((id) => normalizeForSearch(id).includes(searchNormalized))

		return {
			filteredPreferredIds: filterModels(preferredModelIds),
			filteredRestIds: filterModels(restModelIds),
		}
	}, [preferredModelIds, restModelIds, searchValue, normalizeForSearch])

Comment on lines +101 to +114
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. modelpicker search untested 📘 Rule violation ⛯ Reliability

The PR changes ModelPicker search behavior to be case-insensitive and normalize dashes/spaces, but
there are no updated/additional tests asserting the new matching behavior. This risks regressions
where searches may not return expected models across different casing/formatting.
Agent Prompt
## Issue description
`ModelPicker` search behavior was changed to be case-insensitive and to normalize separators, but there is no corresponding test coverage validating the new search filtering outcomes.

## Issue Context
The PR introduces `normalizeForSearch()` and uses it to filter both preferred and rest model IDs, which is a behavior change that should be covered to prevent regressions.

## Fix Focus Areas
- webview-ui/src/components/settings/ModelPicker.tsx[101-114]
- webview-ui/src/components/settings/__tests__/ModelPicker.spec.tsx[74-151]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


const onSelect = useCallback(
(modelId: string) => {
if (!modelId) {
Expand Down Expand Up @@ -179,7 +194,7 @@ export const ModelPicker = ({
</Button>
</PopoverTrigger>
<PopoverContent className="p-0 w-[var(--radix-popover-trigger-width)]">
<Command>
<Command shouldFilter={false}>
<div className="relative">
<CommandInput
ref={searchInputRef}
Expand All @@ -206,10 +221,15 @@ export const ModelPicker = ({
</div>
)}
</CommandEmpty>
{/* kilocode_change start: Section headers for recommended and all models */}
{preferredModelIds.length > 0 && (
<CommandGroup heading={t("settings:modelPicker.recommendedModels")}>
{preferredModelIds.map((model) => (
{/* kilocode_change start: Section headers for recommended and all models with case-insensitive search */}
{filteredPreferredIds.length > 0 && (
<CommandGroup
heading={
searchValue.trim()
? t("settings:modelPicker.matchingModels")
: t("settings:modelPicker.recommendedModels")
}>
Comment on lines +225 to +231
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

3. Missing i18n key 🐞 Bug ✓ Correctness

ModelPicker now references the translation key settings:modelPicker.matchingModels, but it is not
defined in locale files (at least en), so the UI will likely show the raw key string when searching.
Agent Prompt
### Issue description
ModelPicker renders `t("settings:modelPicker.matchingModels")` while searching, but that key is missing from locale JSON files, causing untranslated/raw-key UI text.

### Issue Context
The new label is used when `searchValue.trim()` is truthy.

### Fix Focus Areas
- webview-ui/src/components/settings/ModelPicker.tsx[225-231]
- webview-ui/src/i18n/locales/en/settings.json[1107-1116]
- webview-ui/src/i18n/locales/*/settings.json (add `modelPicker.matchingModels`)

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

{filteredPreferredIds.map((model) => (
<CommandItem
key={model}
value={model}
Expand All @@ -229,9 +249,9 @@ export const ModelPicker = ({
))}
</CommandGroup>
)}
{restModelIds.length > 0 && (
{filteredRestIds.length > 0 && (
<CommandGroup heading={t("settings:modelPicker.allModels")}>
{restModelIds.map((model) => (
{filteredRestIds.map((model) => (
<CommandItem
key={model}
value={model}
Expand Down
25 changes: 22 additions & 3 deletions webview-ui/src/components/settings/providers/OpenAICompatible.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useCallback, useEffect } from "react"
import { useState, useCallback, useEffect, useMemo } from "react"
import { useEvent } from "react-use"
import { Checkbox } from "vscrui"
import { VSCodeButton, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
Expand All @@ -11,6 +11,8 @@ import {
type ExtensionMessage,
azureOpenAiDefaultApiVersion,
openAiModelInfoSaneDefaults,
moonshotModels,
moonshotDefaultModelId,
} from "@roo-code/types"

import { useAppTranslation } from "@src/i18n/TranslationContext"
Expand Down Expand Up @@ -48,6 +50,22 @@ export const OpenAICompatible = ({
return Object.entries(headers)
})

const isKimiEndpoint =
apiConfiguration.openAiBaseUrl?.toLowerCase().includes("kimi") ||
apiConfiguration.openAiBaseUrl?.toLowerCase().includes("moonshot") ||
apiConfiguration.openAiBaseUrl?.toLowerCase().includes("api.moonshot.ai") ||
apiConfiguration.openAiBaseUrl?.toLowerCase().includes("api.moonshot.cn")
Comment on lines +56 to +57
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

These checks for api.moonshot.ai and api.moonshot.cn are redundant, as they are already covered by the includes("moonshot") check on the previous line. You can remove them to simplify the code.


const modelsToUse = useMemo(() => {
const fetchedModels = openAiModels ?? {}
if (isKimiEndpoint) {
return { ...moonshotModels, ...fetchedModels }
}
return fetchedModels
}, [isKimiEndpoint, openAiModels])

const defaultModelId = isKimiEndpoint ? moonshotDefaultModelId : "gpt-4o"

Comment on lines +53 to +68
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Kimi fallback untested 📘 Rule violation ⛯ Reliability

The PR adds Kimi endpoint detection and fallback model/default model selection for
OpenAICompatible, but existing tests do not validate the new defaultModelId/models behavior
passed to ModelPicker. This can break provider configuration silently if endpoint detection or
merging logic changes.
Agent Prompt
## Issue description
`OpenAICompatible` now conditionally merges `moonshotModels` and changes `defaultModelId` when the base URL indicates a Kimi/Moonshot endpoint, but tests do not cover this new behavior.

## Issue Context
The component computes `isKimiEndpoint`, builds `modelsToUse`, and sets `defaultModelId` based on that flag. These changes should be validated in Vitest to prevent regressions.

## Fix Focus Areas
- webview-ui/src/components/settings/providers/OpenAICompatible.tsx[53-68]
- webview-ui/src/components/settings/providers/__tests__/OpenAICompatible.spec.tsx[88-160]গুলো

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

const handleAddCustomHeader = useCallback(() => {
// Only update the local state to show the new row in the UI.
setCustomHeaders((prev) => [...prev, ["", ""]])
Expand Down Expand Up @@ -138,10 +156,11 @@ export const OpenAICompatible = ({
<label className="block font-medium mb-1">{t("settings:providers.apiKey")}</label>
</VSCodeTextField>
<ModelPicker
key={`${apiConfiguration.openAiBaseUrl}-${isKimiEndpoint}`}
apiConfiguration={apiConfiguration}
setApiConfigurationField={setApiConfigurationField}
defaultModelId="gpt-4o"
models={openAiModels}
defaultModelId={defaultModelId}
models={modelsToUse}
modelIdKey="openAiModelId"
serviceName="OpenAI"
serviceUrl="https://platform.openai.com"
Expand Down
Loading