fix(models): sort free models by launch priority

This commit is contained in:
opencode-agent[bot] 2026-05-30 20:33:24 +00:00
commit e97308cd57
9 changed files with 142 additions and 27 deletions

View file

@ -9,6 +9,7 @@ import { popularProviders } from "@/hooks/use-providers"
import { useLanguage } from "@/context/language"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { DialogSelectProvider } from "./dialog-select-provider"
import { sortModels } from "@/utils/model-sort"
export const DialogManageModels: Component = () => {
const local = useLocal()
@ -44,7 +45,7 @@ export const DialogManageModels: Component = () => {
key={(x) => `${x?.provider?.id}:${x?.id}`}
items={local.model.list()}
filterKeys={["provider.name", "name", "id"]}
sortBy={(a, b) => a.name.localeCompare(b.name)}
sortBy={sortModels}
groupBy={(x) => x.provider.id}
groupHeader={(group) => {
const provider = group.items[0].provider

View file

@ -12,9 +12,7 @@ import { List } from "@opencode-ai/ui/list"
import { Tooltip } from "@opencode-ai/ui/tooltip"
import { ModelTooltip } from "./model-tooltip"
import { useLanguage } from "@/context/language"
const isFree = (provider: string, cost: { input: number } | undefined) =>
provider === "opencode" && (!cost || cost.input === 0)
import { isFreeModel, sortModels } from "@/utils/model-sort"
type ModelState = ReturnType<typeof useLocal>["model"]
@ -44,7 +42,7 @@ const ModelList: Component<{
items={models}
current={model.current()}
filterKeys={["provider.name", "name", "id"]}
sortBy={(a, b) => a.name.localeCompare(b.name)}
sortBy={sortModels}
groupBy={(x) => x.provider.name}
sortGroupsBy={(a, b) => {
const aProvider = a.items[0].provider.id
@ -58,7 +56,7 @@ const ModelList: Component<{
class="w-full"
placement="right-start"
gutter={12}
value={<ModelTooltip model={item} latest={item.latest} free={isFree(item.provider.id, item.cost)} />}
value={<ModelTooltip model={item} latest={item.latest} free={isFreeModel(item)} />}
>
{node}
</Tooltip>
@ -73,7 +71,7 @@ const ModelList: Component<{
{(i) => (
<div class="w-full flex items-center gap-x-2 text-13-regular">
<span class="truncate">{i.name}</span>
<Show when={isFree(i.provider.id, i.cost)}>
<Show when={isFreeModel(i)}>
<Tag>{language.t("model.tag.free")}</Tag>
</Show>
<Show when={i.latest}>

View file

@ -9,6 +9,7 @@ import { useLanguage } from "@/context/language"
import { useModels } from "@/context/models"
import { popularProviders } from "@/hooks/use-providers"
import { SettingsList } from "./settings-list"
import { sortModels } from "@/utils/model-sort"
type ModelItem = ReturnType<ReturnType<typeof useModels>["list"]>[number]
@ -39,7 +40,7 @@ export const SettingsModels: Component = () => {
items: (_filter) => models.list(),
key: (x) => `${x.provider.id}:${x.id}`,
filterKeys: ["provider.name", "name", "id"],
sortBy: (a, b) => a.name.localeCompare(b.name),
sortBy: sortModels,
groupBy: (x) => x.provider.id,
sortGroupsBy: (a, b) => {
const aIndex = popularProviders.indexOf(a.category)

View file

@ -0,0 +1,25 @@
import { describe, expect, test } from "bun:test"
import { sortModels } from "./model-sort"
function model(input: { id: string; name: string; release_date: string; cost?: number }) {
return {
id: input.id,
name: input.name,
release_date: input.release_date,
provider: { id: "opencode" },
cost: { input: input.cost ?? 0 },
}
}
describe("sortModels", () => {
test("pins Big Pickle before free models sorted by release date", () => {
expect(
[
model({ id: "older-free", name: "Older Free", release_date: "2026-01-01" }),
model({ id: "paid", name: "Paid", release_date: "2026-05-01", cost: 1 }),
model({ id: "newer-free", name: "Newer Free", release_date: "2026-05-01" }),
model({ id: "big-pickle", name: "Big Pickle", release_date: "2025-10-17" }),
].sort(sortModels).map((item) => item.id),
).toEqual(["big-pickle", "newer-free", "older-free", "paid"])
})
})

View file

@ -0,0 +1,26 @@
type SortableModel = {
id: string
name: string
release_date: string
provider: { id: string }
cost?: { input: number }
}
export function isFreeModel(model: { provider: { id: string }; cost?: { input: number } }) {
return model.provider.id === "opencode" && (!model.cost || model.cost.input === 0)
}
export function sortModels(a: SortableModel, b: SortableModel) {
const aFree = isFreeModel(a)
const bFree = isFreeModel(b)
if (aFree && !bFree) return -1
if (!aFree && bFree) return 1
if (aFree && bFree) {
if (a.id !== b.id) {
if (a.id === "big-pickle") return -1
if (b.id === "big-pickle") return 1
}
return b.release_date.localeCompare(a.release_date) || a.name.localeCompare(b.name)
}
return a.name.localeCompare(b.name)
}