mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-27 17:58:07 +00:00
fix(models): match bare Kimi Coding k3 when searching kimi
Kimi Coding discovers the flagship as wire id `k3`. Picker search used only that id, so typing "kimi" hid it next to every other kimi-* model. Add picker-only search aliases without changing the wire id.
This commit is contained in:
parent
2be2464d25
commit
c63e0cd3e3
9 changed files with 151 additions and 6 deletions
|
|
@ -4,6 +4,7 @@ import { useState } from 'react'
|
|||
import { useI18n } from '@/i18n'
|
||||
import { modelOptionsQueryKey, requestModelOptions } from '@/lib/model-options'
|
||||
import { currentPickerSelection } from '@/lib/model-status-label'
|
||||
import { modelSearchText } from '@/lib/model-search-text'
|
||||
import { normalize } from '@/lib/text'
|
||||
import type { ModelOptionProvider, ModelPricing } from '@/types/hermes'
|
||||
|
||||
|
|
@ -172,7 +173,7 @@ function ModelResults({
|
|||
|
||||
const matches = (provider: ModelOptionProvider, model: string) =>
|
||||
!q ||
|
||||
model.toLowerCase().includes(q) ||
|
||||
modelSearchText(model).toLowerCase().includes(q) ||
|
||||
provider.name.toLowerCase().includes(q) ||
|
||||
provider.slug.toLowerCase().includes(q)
|
||||
|
||||
|
|
|
|||
28
apps/desktop/src/lib/model-search-text.ts
Normal file
28
apps/desktop/src/lib/model-search-text.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* Extra tokens used only for model-picker search ranking.
|
||||
*
|
||||
* Wire IDs stay unchanged — some providers report short or brand-less ids
|
||||
* (Kimi Coding's flagship is literally `k3`) that users still search for by
|
||||
* the familiar `kimi-…` naming of sibling models.
|
||||
*
|
||||
* Keep in sync with ui-tui/src/lib/model-search-text.ts,
|
||||
* web/src/lib/model-search-text.ts, and hermes_cli/model_search.py.
|
||||
*/
|
||||
const MODEL_SEARCH_ALIASES: Record<string, readonly string[]> = {
|
||||
k3: ['kimi-k3', 'kimi']
|
||||
}
|
||||
|
||||
/** Haystack for fuzzy/substring model search; never changes the wire id. */
|
||||
export function modelSearchText(model: string): string {
|
||||
const id = model.trim()
|
||||
if (!id) {
|
||||
return model
|
||||
}
|
||||
|
||||
const aliases = MODEL_SEARCH_ALIASES[id.toLowerCase()]
|
||||
if (!aliases?.length) {
|
||||
return id
|
||||
}
|
||||
|
||||
return `${id} ${aliases.join(' ')}`
|
||||
}
|
||||
|
|
@ -7341,6 +7341,22 @@ def _prompt_model_selection(
|
|||
desc_lines.append(f" ── {unavailable_footer} ──")
|
||||
description = "\n".join(desc_lines) if desc_lines else None
|
||||
|
||||
# Search haystacks keep pricing labels visible while adding aliases
|
||||
# for brand-less wire ids (e.g. Kimi Coding `k3` ↔ query "kimi").
|
||||
from hermes_cli.model_search import model_search_text
|
||||
|
||||
model_search_labels = []
|
||||
for mid in ordered:
|
||||
label = _label(mid)
|
||||
haystack = model_search_text(mid)
|
||||
# model_search_text always starts with the wire id; only append when
|
||||
# aliases add tokens beyond the bare id already in the label.
|
||||
model_search_labels.append(
|
||||
label if haystack == mid else f"{label} {haystack}"
|
||||
)
|
||||
model_search_labels.append("Enter custom model name")
|
||||
model_search_labels.append("Skip (keep current)")
|
||||
|
||||
idx = curses_radiolist(
|
||||
"Select default model:",
|
||||
choices,
|
||||
|
|
@ -7348,6 +7364,7 @@ def _prompt_model_selection(
|
|||
cancel_returns=-1,
|
||||
description=description,
|
||||
searchable=True,
|
||||
search_labels=model_search_labels,
|
||||
)
|
||||
if idx < 0:
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -723,6 +723,7 @@ def curses_radiolist(
|
|||
cancel_returns: int | None = None,
|
||||
description: str | None = None,
|
||||
searchable: bool = False,
|
||||
search_labels: List[str] | None = None,
|
||||
) -> int:
|
||||
"""Curses single-select radio list. Returns the selected index.
|
||||
|
||||
|
|
@ -741,6 +742,8 @@ def curses_radiolist(
|
|||
searchable: When true, ``/`` opens a type-to-filter prompt. The
|
||||
returned value is always the original item index, not a filtered
|
||||
row position.
|
||||
search_labels: Optional haystacks for type-to-filter (length must
|
||||
match ``items``). Defaults to the display labels when omitted.
|
||||
"""
|
||||
if cancel_returns is None:
|
||||
cancel_returns = selected
|
||||
|
|
@ -812,7 +815,11 @@ def curses_radiolist(
|
|||
fallback=lambda: _radio_numbered_fallback(title, items, selected, cancel_returns),
|
||||
cancel_value=cancel_returns,
|
||||
searchable=searchable,
|
||||
search_labels=plain_labels if searchable else None,
|
||||
search_labels=(
|
||||
list(search_labels)
|
||||
if searchable and search_labels is not None
|
||||
else (plain_labels if searchable else None)
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
30
hermes_cli/model_search.py
Normal file
30
hermes_cli/model_search.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
"""Picker-only search aliases for model ids.
|
||||
|
||||
Wire IDs stay unchanged. Some providers report short or brand-less ids
|
||||
(Kimi Coding's flagship is literally ``k3``) that users still search for by
|
||||
the familiar ``kimi-…`` naming of sibling models.
|
||||
|
||||
Keep in sync with ``ui-tui/src/lib/model-search-text.ts`` and
|
||||
``web/src/lib/model-search-text.ts``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
# Lowercased wire id → extra tokens appended to the search haystack only.
|
||||
_MODEL_SEARCH_ALIASES: dict[str, tuple[str, ...]] = {
|
||||
"k3": ("kimi-k3", "kimi"),
|
||||
}
|
||||
|
||||
|
||||
def model_search_text(model: str) -> str:
|
||||
"""Return the haystack used for fuzzy/substring model search.
|
||||
|
||||
Never changes the wire id passed to the provider.
|
||||
"""
|
||||
mid = (model or "").strip()
|
||||
if not mid:
|
||||
return model or ""
|
||||
aliases = _MODEL_SEARCH_ALIASES.get(mid.lower())
|
||||
if not aliases:
|
||||
return mid
|
||||
return f"{mid} {' '.join(aliases)}"
|
||||
|
|
@ -6,6 +6,7 @@ import { TUI_SESSION_MODEL_FLAG } from '../domain/slash.js'
|
|||
import type { GatewayClient } from '../gatewayClient.js'
|
||||
import type { ModelOptionProvider, ModelOptionsResponse } from '../gatewayTypes.js'
|
||||
import { fuzzyRank } from '../lib/fuzzy.js'
|
||||
import { modelSearchText } from '../lib/model-search-text.js'
|
||||
import { asRpcResult, rpcErrorMessage } from '../lib/rpc.js'
|
||||
import type { Theme } from '../theme.js'
|
||||
|
||||
|
|
@ -136,7 +137,9 @@ export function ModelPicker({
|
|||
return allModels
|
||||
}
|
||||
|
||||
return fuzzyRank(allModels, filter, m => m).map(r => r.item)
|
||||
// modelSearchText adds aliases for brand-less wire ids (e.g. Kimi
|
||||
// Coding `k3` still matches a "kimi" query).
|
||||
return fuzzyRank(allModels, filter, modelSearchText).map(r => r.item)
|
||||
}, [allModels, filter, stage])
|
||||
|
||||
const models = filteredModels
|
||||
|
|
|
|||
28
ui-tui/src/lib/model-search-text.ts
Normal file
28
ui-tui/src/lib/model-search-text.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* Extra tokens used only for model-picker search ranking.
|
||||
*
|
||||
* Wire IDs stay unchanged — some providers report short or brand-less ids
|
||||
* (Kimi Coding's flagship is literally `k3`) that users still search for by
|
||||
* the familiar `kimi-…` naming of sibling models.
|
||||
*
|
||||
* Keep in sync with web/src/lib/model-search-text.ts and
|
||||
* hermes_cli/model_search.py.
|
||||
*/
|
||||
const MODEL_SEARCH_ALIASES: Record<string, readonly string[]> = {
|
||||
k3: ['kimi-k3', 'kimi'],
|
||||
}
|
||||
|
||||
/** Haystack for fuzzy/substring model search; never changes the wire id. */
|
||||
export function modelSearchText(model: string): string {
|
||||
const id = model.trim()
|
||||
if (!id) {
|
||||
return model
|
||||
}
|
||||
|
||||
const aliases = MODEL_SEARCH_ALIASES[id.toLowerCase()]
|
||||
if (!aliases?.length) {
|
||||
return id
|
||||
}
|
||||
|
||||
return `${id} ${aliases.join(' ')}`
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ import { createPortal } from "react-dom";
|
|||
import { cn, themedBody } from "@/lib/utils";
|
||||
import { fuzzyRank } from "@/lib/fuzzy";
|
||||
import { queryMatchesProviderOnly } from "@/lib/model-picker-filter";
|
||||
import { modelSearchText } from "@/lib/model-search-text";
|
||||
|
||||
/**
|
||||
* Two-stage model picker modal.
|
||||
|
|
@ -246,16 +247,18 @@ export function ModelPickerDialog(props: Props) {
|
|||
);
|
||||
|
||||
// Fuzzy-ranked models carrying the matched character positions so the model
|
||||
// list can highlight why each entry matched.
|
||||
// list can highlight why each entry matched. modelSearchText adds aliases
|
||||
// for brand-less wire ids (e.g. Kimi Coding `k3` ↔ search "kimi").
|
||||
const filteredModels = useMemo(
|
||||
() =>
|
||||
fuzzyRank(
|
||||
models,
|
||||
queryMatchesSelectedProviderOnly ? "" : trimmedQuery,
|
||||
(m) => m,
|
||||
modelSearchText,
|
||||
).map((r) => ({
|
||||
model: r.item,
|
||||
positions: r.positions,
|
||||
// Positions may land in alias suffixes — keep only in-id highlights.
|
||||
positions: r.positions.filter((i) => i >= 0 && i < r.item.length),
|
||||
})),
|
||||
[models, trimmedQuery, queryMatchesSelectedProviderOnly],
|
||||
);
|
||||
|
|
|
|||
28
web/src/lib/model-search-text.ts
Normal file
28
web/src/lib/model-search-text.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* Extra tokens used only for model-picker search ranking.
|
||||
*
|
||||
* Wire IDs stay unchanged — some providers report short or brand-less ids
|
||||
* (Kimi Coding's flagship is literally `k3`) that users still search for by
|
||||
* the familiar `kimi-…` naming of sibling models.
|
||||
*
|
||||
* Keep in sync with ui-tui/src/lib/model-search-text.ts and
|
||||
* hermes_cli/model_search.py. Behavioural tests live in the TUI package.
|
||||
*/
|
||||
const MODEL_SEARCH_ALIASES: Record<string, readonly string[]> = {
|
||||
k3: ["kimi-k3", "kimi"],
|
||||
};
|
||||
|
||||
/** Haystack for fuzzy/substring model search; never changes the wire id. */
|
||||
export function modelSearchText(model: string): string {
|
||||
const id = model.trim();
|
||||
if (!id) {
|
||||
return model;
|
||||
}
|
||||
|
||||
const aliases = MODEL_SEARCH_ALIASES[id.toLowerCase()];
|
||||
if (!aliases?.length) {
|
||||
return id;
|
||||
}
|
||||
|
||||
return `${id} ${aliases.join(" ")}`;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue