Skip to content

tui: model picker search ranks scattered-letter matches above exact ones #50531

Open

Description

@fede-kamel

Description

Searching in the TUI model picker returns matches in release-date order instead of relevance order, so an exact match can sit at the bottom of the list below models that only share a few scattered letters with the query.

Searching luna against an OpenRouter catalogue:

inclusionai/ling-3.0-flash        ← top result, selected
inclusionai/ling-3.0-flash-fin
inclusionai/ling-3.0-flash-fin:free
...
openai/gpt-5.6-luna
openai/gpt-5.6-luna-pro
...
poolside/laguna-s-2.1
~openai/gpt-luna-latest            ← exact match, last

inclusionai/ling-3.0-flash matches because fuzzysort finds l-u-n-a in order inside "incLUsioNAi". That fuzzy match is fine on its own — the problem is that it outranks names that literally contain luna, and pressing Enter picks it.

Cause

A regression from #33558 ("sort model picker by release date"). In packages/tui/src/component/dialog-model.tsx the search branch became:

...sortModelOptions(
  fuzzysort.go(needle, providerOptions, { keys: ["title", "category"] }).map((x) => x.obj),
  false,
),

fuzzysort.go returns results best-first; sortModelOptions(…, false) then re-sorts them free-first, newest-first, discarding the relevance order. Before #33558 the search results were passed through unsorted. The release-date ordering makes sense for the unfiltered picker, but not for search results.

model-options.test.ts covers sortModelOptions itself, not the search path, which is why nothing caught it.

Steps to reproduce

  1. Configure a provider with a large catalogue (OpenRouter shows it well).
  2. Open /models and type luna.
  3. The first, pre-selected result is inclusionai/ling-3.0-flash, not a luna model.

Operating System

Linux x86_64 (WSL2)

Terminal

Windows Terminal

Activity

  1. opencodejr commented on Oct 28, 2026

    @opencodejr
    Contributor

    Thanks for the detailed report. I reproduced this on @opencode/[email protected], and it still happens on the latest dev (fa1260d6).

    I set up a simulated provider with three paid models ("OpenAI: GPT Luna Latest", "OpenAI: GPT-5.6 Luna", "Poolside: Laguna S 2.1") and two free ones ("InclusionAI: Ling-3.0-Flash (free)", "InclusionAI: Ling-3.0-Flash-Fin (free)"). Then I opened /models and typed luna. The two InclusionAI models, which only match through the scattered letters in "incLUsioNAi", come first, and Ling-3.0-Flash is pre-selected. The models that actually contain "Luna" come after them.

    On v2 the release-date sort you traced to #33558 has since been removed (#57151). However, the search results still go through sortModelOptions(…, false), which now puts free models first. That still overrides fuzzysort's relevance order. The recording and screenshots below show it, and the script below reproduces it.

    Related: #50532 (your earlier fix, closed because it targeted v1), #33558 and #47183 (where search results started being re-sorted), #57151 (removed the release-date part).

    recording.mp4

    Screenshot

    Screenshot

    Reproduction script (drive.ts)
    // Repro for anomalyco/opencode#50531: model picker search lets a free scattered-letter
    // fuzzy match outrank (and be pre-selected over) models whose names contain the query exactly.
    import { Effect } from "effect"
    import { OpenCodeDriver } from "opencode-drive"
    
    const paid = { input: 1, output: 2 }
    const free = { input: 0, output: 0 }
    const model = (name: string, cost: { input: number; output: number }, releaseDate: string) => ({
      name,
      cost,
      releaseDate,
      capabilities: { tools: true, input: ["text"], output: ["text"] },
      limit: { context: 128_000, output: 16_000 },
    })
    
    export default OpenCodeDriver.use(
      {
        ...(process.env.OC_DEV ? { opencode: { dev: process.env.OC_DEV } } : {}),
        tui: { recording: true, keypressOverlay: true },
        config: {
          providers: {
            simulation: {
              models: {
                "gpt-luna-latest": model("OpenAI: GPT Luna Latest", paid, "2026-01-01"),
                "gpt-5.6-luna": model("OpenAI: GPT-5.6 Luna", paid, "2026-02-01"),
                "laguna-s-2.1": model("Poolside: Laguna S 2.1", paid, "2026-03-01"),
                "ling-3.0-flash-fin:free": model("InclusionAI: Ling-3.0-Flash-Fin (free)", free, "2025-01-01"),
                "ling-3.0-flash:free": model("InclusionAI: Ling-3.0-Flash (free)", free, "2025-01-01"),
              },
            },
          },
        },
      },
      ({ ui }) =>
        Effect.gen(function* () {
          yield* ui.screenshot("home")
          yield* ui.type("/models")
          yield* Effect.sleep("500 millis")
          yield* ui.enter()
          yield* ui.waitFor("GPT Luna Latest")
          yield* ui.screenshot("picker-open")
          yield* ui.type("luna")
          yield* Effect.sleep("1 second")
          yield* ui.screenshot("search-luna")
          const frame = yield* ui.capture()
          yield* Effect.log(JSON.stringify(frame).slice(0, 200))
          yield* Effect.sleep("1 second")
        }),
    )

    Run it with opencode-drive: opencode-drive run ./drive.ts. It drives opencode from your PATH; set OC_DEV to the path of an OpenCode checkout to run that from source instead.

  2. added a commit that references this issue on Nov 6, 2026
    38c4f1a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions