Skip to content

Commit 92f241b

Browse files
rekram1-nodebvolpato
authored andcommitted
fix(core): refine small model defaults (anomalyco#33926)
1 parent 74808c9 commit 92f241b

3 files changed

Lines changed: 125 additions & 24 deletions

File tree

packages/core/src/catalog.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,11 @@ export const layer = Layer.effect(
235235
if (!record) return
236236
const provider = record.provider
237237

238+
// TODO: Remove these provider-specific assumptions once model syncing reliably reports available deployments.
239+
if (providerID === ProviderV2.ID.azure || providerID === ProviderV2.ID.make("azure-cognitive-services")) {
240+
return
241+
}
242+
238243
if (providerID === ProviderV2.ID.opencode) {
239244
const gpt5Nano = record.models.get(ModelV2.ID.make("gpt-5-nano"))
240245
if (gpt5Nano?.enabled && gpt5Nano.status === "active") return projectModel(gpt5Nano, provider)

packages/opencode/src/provider/provider.ts

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1868,44 +1868,43 @@ export const layer = Layer.effect(
18681868
}
18691869
}
18701870

1871-
const defaultPriority = [
1872-
"claude-haiku-4-5",
1873-
"claude-haiku-4.5",
1874-
"3-5-haiku",
1875-
"3.5-haiku",
1876-
"gemini-3-flash",
1877-
"gemini-2.5-flash",
1878-
"gpt-5-nano",
1879-
]
1871+
// TODO: Remove these provider-specific assumptions once model syncing reliably reports available deployments.
1872+
if (providerID === ProviderV2.ID.azure || providerID === ProviderV2.ID.make("azure-cognitive-services")) {
1873+
return undefined
1874+
}
1875+
18801876
const priority = providerID.startsWith("opencode")
1881-
? ["gpt-5-nano"]
1877+
? ["gpt-nano"]
18821878
: providerID.startsWith("github-copilot")
1883-
? ["gpt-5-mini", "claude-haiku-4.5", ...defaultPriority]
1884-
: defaultPriority
1885-
for (const item of priority) {
1879+
? ["gpt-mini", ...smallModelFamilyPriority]
1880+
: smallModelFamilyPriority
1881+
const models = sortBy(
1882+
Object.values(provider.models),
1883+
[(model) => model.release_date, "desc"],
1884+
[(model) => model.id, "desc"],
1885+
)
1886+
for (const family of priority) {
1887+
const candidates = models.filter((model) => model.family === family)
18861888
if (providerID === ProviderV2.ID.amazonBedrock) {
18871889
const crossRegionPrefixes = ["global.", "us.", "eu."]
1888-
const candidates = Object.keys(provider.models).filter((m) => m.includes(item))
18891890

1890-
const globalMatch = candidates.find((m) => m.startsWith("global."))
1891-
if (globalMatch) return provider.models[globalMatch]
1891+
const globalMatch = candidates.find((model) => model.id.startsWith("global."))
1892+
if (globalMatch) return globalMatch
18921893

18931894
const region = provider.options?.region
18941895
if (region) {
18951896
const regionPrefix = region.split("-")[0]
18961897
if (regionPrefix === "us" || regionPrefix === "eu") {
1897-
const regionalMatch = candidates.find((m) => m.startsWith(`${regionPrefix}.`))
1898-
if (regionalMatch) return provider.models[regionalMatch]
1898+
const regionalMatch = candidates.find((model) => model.id.startsWith(`${regionPrefix}.`))
1899+
if (regionalMatch) return regionalMatch
18991900
}
19001901
}
19011902

1902-
const unprefixed = candidates.find((m) => !crossRegionPrefixes.some((p) => m.startsWith(p)))
1903-
if (unprefixed) return provider.models[unprefixed]
1904-
} else {
1905-
for (const model of Object.keys(provider.models)) {
1906-
if (model.includes(item)) return provider.models[model]
1907-
}
1903+
const unprefixed = candidates.find((model) => !crossRegionPrefixes.some((p) => model.id.startsWith(p)))
1904+
if (unprefixed) return unprefixed
1905+
continue
19081906
}
1907+
if (candidates[0]) return candidates[0]
19091908
}
19101909

19111910
return undefined
@@ -1962,6 +1961,7 @@ export const defaultLayer = Layer.suspend(() =>
19621961
)
19631962

19641963
const priority = ["gpt-5", "claude-sonnet-4", "big-pickle", "gemini-3-pro"]
1964+
const smallModelFamilyPriority = ["gemini-flash", "gpt-nano", "claude-haiku"]
19651965
export function sort<T extends { id: string }>(models: T[]) {
19661966
return sortBy(
19671967
models,

packages/opencode/test/provider/provider.test.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,102 @@ it.instance("getSmallModel returns appropriate small model", () =>
652652
}),
653653
)
654654

655+
it.instance("getSmallModel prefers Gemini for Google Vertex", () =>
656+
Effect.gen(function* () {
657+
yield* set("GOOGLE_VERTEX_PROJECT", "test-project")
658+
const model = yield* Provider.use.getSmallModel(ProviderV2.ID.googleVertex)
659+
expect(model).toBeDefined()
660+
expect(model?.id).toContain("gemini")
661+
}),
662+
)
663+
664+
it.instance(
665+
"getSmallModel selects the latest model in the preferred family",
666+
Effect.gen(function* () {
667+
const model = yield* Provider.use.getSmallModel(ProviderV2.ID.make("test-provider"))
668+
expect(model?.id).toBe(ModelV2.ID.make("new-flash"))
669+
}),
670+
{
671+
config: {
672+
provider: {
673+
"test-provider": {
674+
name: "Test Provider",
675+
npm: "@ai-sdk/openai-compatible",
676+
models: {
677+
"old-flash": { family: "gemini-flash", release_date: "2025-01-01" },
678+
"new-flash": { family: "gemini-flash", release_date: "2026-01-01" },
679+
"newer-haiku": { family: "claude-haiku", release_date: "2026-06-01" },
680+
},
681+
options: { apiKey: "test-key" },
682+
},
683+
},
684+
},
685+
},
686+
)
687+
688+
it.instance(
689+
"getSmallModel matches exact model families",
690+
Effect.gen(function* () {
691+
const model = yield* Provider.use.getSmallModel(ProviderV2.ID.make("test-provider"))
692+
expect(model?.id).toBe(ModelV2.ID.make("claude-haiku"))
693+
}),
694+
{
695+
config: {
696+
provider: {
697+
"test-provider": {
698+
name: "Test Provider",
699+
npm: "@ai-sdk/openai-compatible",
700+
models: {
701+
"glm-flash": { family: "glm-flash", release_date: "2026-06-01" },
702+
"claude-haiku": { family: "claude-haiku", release_date: "2026-01-01" },
703+
},
704+
options: { apiKey: "test-key" },
705+
},
706+
},
707+
},
708+
},
709+
)
710+
711+
it.instance(
712+
"getSmallModel ignores model IDs without family metadata",
713+
Effect.gen(function* () {
714+
const model = yield* Provider.use.getSmallModel(ProviderV2.ID.make("test-provider"))
715+
expect(model).toBeUndefined()
716+
}),
717+
{
718+
config: {
719+
provider: {
720+
"test-provider": {
721+
name: "Test Provider",
722+
npm: "@ai-sdk/openai-compatible",
723+
models: {
724+
"gpt-5-nano": { release_date: "2026-01-01" },
725+
},
726+
options: { apiKey: "test-key" },
727+
},
728+
},
729+
},
730+
},
731+
)
732+
733+
it.instance("getSmallModel skips inferred models for Azure", () =>
734+
Effect.gen(function* () {
735+
yield* set("AZURE_RESOURCE_NAME", "test-resource")
736+
yield* set("AZURE_API_KEY", "test-key")
737+
const model = yield* Provider.use.getSmallModel(ProviderV2.ID.azure)
738+
expect(model).toBeUndefined()
739+
}),
740+
)
741+
742+
it.instance("getSmallModel skips inferred models for Azure Cognitive Services", () =>
743+
Effect.gen(function* () {
744+
yield* set("AZURE_COGNITIVE_SERVICES_RESOURCE_NAME", "test-resource")
745+
yield* set("AZURE_COGNITIVE_SERVICES_API_KEY", "test-key")
746+
const model = yield* Provider.use.getSmallModel(ProviderV2.ID.make("azure-cognitive-services"))
747+
expect(model).toBeUndefined()
748+
}),
749+
)
750+
655751
it.instance(
656752
"getSmallModel respects config small_model override",
657753
Effect.gen(function* () {

0 commit comments

Comments
 (0)