diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index c3adece3cb9..49dc3cd4c8e 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -222,10 +222,7 @@ const layer = Layer.effect( const all = Array.from(byProvider.values()) .flatMap((models) => Array.from(models.values())) .sort((left, right) => right.time.released - left.time.released) - cached = freeze( - { data, all, available: preferBigPickle(all.filter((model) => model.enabled)), byProvider }, - true, - ) + cached = freeze({ data, all, available: preferFree(all.filter((model) => model.enabled)), byProvider }, true) return cached } }) @@ -290,14 +287,21 @@ const layer = Layer.effect( export const node = makeLocationNode({ service: Service, layer, deps: [Provider.node, Bus.node, Location.node] }) // Signed out, only free OpenCode models are enabled. Clients default to the first available model, so keep -// big-pickle ahead of each new free launch instead of letting release order pick it. -function preferBigPickle(models: Info[]) { +// these free models ahead of each new free launch instead of letting release order pick the default. +const PREFERRED_FREE = [ + "big-pickle", + "deepseek-v4.1-flash-free", + "glm-5.3-flash-free", + "muse-spark-1.3-contributor-free", +] + +function preferFree(models: Info[]) { const freeOnly = models.every( (model) => model.providerID === Provider.ID.opencode && !model.cost.some((cost) => cost.input > 0), ) - const pick = freeOnly ? models.find((model) => model.id === "big-pickle") : undefined - if (!pick) return models - return [pick, ...models.filter((model) => model !== pick)] + if (!freeOnly) return models + const picks = PREFERRED_FREE.flatMap((id) => models.find((model) => model.id === id) ?? []) + return [...picks, ...models.filter((model) => !picks.includes(model))] } export function supportsText(model: Pick) { diff --git a/packages/core/test/catalog.test.ts b/packages/core/test/catalog.test.ts index d723073637e..8eed25ba917 100644 --- a/packages/core/test/catalog.test.ts +++ b/packages/core/test/catalog.test.ts @@ -576,16 +576,18 @@ describe("Provider and Model", () => { const models = yield* Model.Service yield* providers.transform((editor) => { editor.update(Provider.ID.opencode, () => {}) - editor.models.update(Provider.ID.opencode, Model.ID.make("big-pickle"), (model) => { - model.time.released = 1000 - }) - editor.models.update(Provider.ID.opencode, Model.ID.make("new-free"), (model) => { - model.time.released = 2000 - }) + ;["muse-spark-1.3-contributor-free", "new-free", "deepseek-v4.1-flash-free", "big-pickle"].forEach( + (id, index) => + editor.models.update(Provider.ID.opencode, Model.ID.make(id), (model) => { + model.time.released = 4000 - index * 1000 + }), + ) }) expect((yield* models.available()).map((model) => model.id)).toEqual([ Model.ID.make("big-pickle"), + Model.ID.make("deepseek-v4.1-flash-free"), + Model.ID.make("muse-spark-1.3-contributor-free"), Model.ID.make("new-free"), ]) expect((yield* models.default())?.id).toBe(Model.ID.make("big-pickle"))