Skip to content
Closed
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 @@ -99,6 +99,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])

const onSelect = useCallback(
(modelId: string) => {
if (!modelId) {
Expand Down Expand Up @@ -180,7 +195,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 @@ -207,10 +222,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")
}>
{filteredPreferredIds.map((model) => (
<CommandItem
key={model}
value={model}
Expand All @@ -230,9 +250,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")

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

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

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