From 188341fbcff51ef4a9951bfe156fff1b0899dd3f Mon Sep 17 00:00:00 2001 From: fede-kamel Date: Tue, 22 Sep 2026 01:45:28 -0400 Subject: [PATCH 1/2] fix(tui): rank model search results by relevance #33558 sorted the model picker newest-first, and wrapped the search results in the same sort. That discards fuzzysort's relevance order, so searching "luna" lists inclusionai/ling-3.0-flash (which matches the letters l-u-n-a scattered through "inclusionai") above openai/gpt-5.6-luna. Search results now keep fuzzysort's order. The unfiltered picker still sorts free-first, then newest-first. --- packages/tui/src/component/dialog-model.tsx | 11 ++++--- .../test/cli/cmd/tui/model-options.test.ts | 30 ++++++++++++++++++- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/packages/tui/src/component/dialog-model.tsx b/packages/tui/src/component/dialog-model.tsx index 38db569aaa24..1146fcb5d0b2 100644 --- a/packages/tui/src/component/dialog-model.tsx +++ b/packages/tui/src/component/dialog-model.tsx @@ -118,10 +118,7 @@ export function DialogModel(props: { providerID?: string }) { if (needle) { return [ - ...sortModelOptions( - fuzzysort.go(needle, providerOptions, { keys: ["title", "category"] }).map((x) => x.obj), - false, - ), + ...searchModelOptions(needle, providerOptions), ...fuzzysort.go(needle, popularProviders, { keys: ["title"] }).map((x) => x.obj), ] } @@ -183,6 +180,12 @@ export function DialogModel(props: { providerID?: string }) { ) } +// Search results keep fuzzysort's relevance order: re-sorting them by release date +// would rank a scattered-letter match above an exact one. +export function searchModelOptions(needle: string, options: T[]) { + return fuzzysort.go(needle, options, { keys: ["title", "category"] }).map((x) => x.obj) +} + export function sortModelOptions( options: T[], newestFirst: boolean, diff --git a/packages/tui/test/cli/cmd/tui/model-options.test.ts b/packages/tui/test/cli/cmd/tui/model-options.test.ts index 97bae7532fe9..4ddb80cc4d63 100644 --- a/packages/tui/test/cli/cmd/tui/model-options.test.ts +++ b/packages/tui/test/cli/cmd/tui/model-options.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test" -import { sortModelOptions } from "../../../../src/component/dialog-model" +import { searchModelOptions, sortModelOptions } from "../../../../src/component/dialog-model" describe("sortModelOptions", () => { test("orders provider-scoped model choices by newest release first", () => { @@ -30,3 +30,31 @@ describe("sortModelOptions", () => { expect(sorted.map((model) => model.title)).toEqual(["Free new", "Free old", "GLM 5.2", "GLM 5.1", "GLM 5"]) }) }) + +describe("searchModelOptions", () => { + const category = "OpenRouter" + // Titles from a real OpenRouter catalogue, in the order the picker lists them. + const options = [ + { title: "inclusionai/ling-3.0-flash", category, releaseDate: "2026-09-01" }, + { title: "inclusionai/ling-3.0-flash-vl", category, releaseDate: "2026-09-01" }, + { title: "openai/gpt-5.6-luna", category, releaseDate: "2026-08-01" }, + { title: "openai/gpt-5.6-luna-pro", category, releaseDate: "2026-08-01" }, + { title: "poolside/laguna-s-2.1", category, releaseDate: "2026-07-01" }, + { title: "~openai/gpt-luna-latest", category, releaseDate: "2026-06-01" }, + ] + + test("ranks names containing the query above scattered-letter matches", () => { + const titles = searchModelOptions("luna", options).map((option) => option.title) + const exact = titles.filter((title) => title.includes("luna")) + + // Every name that literally contains "luna" comes before any that only match + // its letters in order, like incLUsioNAi or LagUNA. + expect(titles.slice(0, exact.length)).toEqual(exact) + expect(exact).toHaveLength(3) + }) + + test("does not re-sort search results by release date", () => { + const titles = searchModelOptions("luna", options).map((option) => option.title) + expect(titles[0]).not.toBe("inclusionai/ling-3.0-flash") + }) +}) From f21f2545fcf8cc2a04888dccf28d8fda6e840eb3 Mon Sep 17 00:00:00 2001 From: fede-kamel Date: Tue, 22 Sep 2026 10:13:43 -0400 Subject: [PATCH 2/2] test(tui): guard that model search stays fuzzy The second search test only re-checked the first. It now asserts that scattered-letter matches are kept, below the exact ones, so a future change can't fix the ordering by making search exact-only. --- packages/tui/test/cli/cmd/tui/model-options.test.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/tui/test/cli/cmd/tui/model-options.test.ts b/packages/tui/test/cli/cmd/tui/model-options.test.ts index 4ddb80cc4d63..fc7dd146e1a5 100644 --- a/packages/tui/test/cli/cmd/tui/model-options.test.ts +++ b/packages/tui/test/cli/cmd/tui/model-options.test.ts @@ -33,7 +33,7 @@ describe("sortModelOptions", () => { describe("searchModelOptions", () => { const category = "OpenRouter" - // Titles from a real OpenRouter catalogue, in the order the picker lists them. + // Titles from a real OpenRouter catalogue, newest first as the picker sorts them. const options = [ { title: "inclusionai/ling-3.0-flash", category, releaseDate: "2026-09-01" }, { title: "inclusionai/ling-3.0-flash-vl", category, releaseDate: "2026-09-01" }, @@ -53,8 +53,13 @@ describe("searchModelOptions", () => { expect(exact).toHaveLength(3) }) - test("does not re-sort search results by release date", () => { + test("keeps scattered-letter matches, below the exact ones", () => { const titles = searchModelOptions("luna", options).map((option) => option.title) - expect(titles[0]).not.toBe("inclusionai/ling-3.0-flash") + + // Search stays fuzzy: every option matches l-u-n-a in order, so none are dropped. + expect(titles).toHaveLength(options.length) + expect(titles.slice(3)).toEqual( + expect.arrayContaining(["inclusionai/ling-3.0-flash", "inclusionai/ling-3.0-flash-vl", "poolside/laguna-s-2.1"]), + ) }) })