From 9818c9e8d04aa9ff0a0257bef9ee44ded0590673 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Wed, 13 May 2026 20:44:34 -0400 Subject: [PATCH] fix(provider): make small model fallback optional (#27405) --- packages/opencode/src/provider/provider.ts | 12 ++++++----- .../opencode/test/provider/provider.test.ts | 21 +++++++++++++++++++ 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 46c1c56d62..d2c6eafd40 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -1707,7 +1707,9 @@ const layer = Layer.effect( if (cfg.small_model) { const parsed = parseModel(cfg.small_model) - return yield* getModel(parsed.providerID, parsed.modelID).pipe(Effect.orDie) + return yield* getModel(parsed.providerID, parsed.modelID).pipe( + Effect.catchTag("ProviderModelNotFoundError", () => Effect.succeed(undefined)), + ) } const s = yield* InstanceState.get(state) @@ -1735,22 +1737,22 @@ const layer = Layer.effect( const candidates = Object.keys(provider.models).filter((m) => m.includes(item)) const globalMatch = candidates.find((m) => m.startsWith("global.")) - if (globalMatch) return yield* getModel(providerID, ModelID.make(globalMatch)).pipe(Effect.orDie) + if (globalMatch) return provider.models[globalMatch] const region = provider.options?.region if (region) { const regionPrefix = region.split("-")[0] if (regionPrefix === "us" || regionPrefix === "eu") { const regionalMatch = candidates.find((m) => m.startsWith(`${regionPrefix}.`)) - if (regionalMatch) return yield* getModel(providerID, ModelID.make(regionalMatch)).pipe(Effect.orDie) + if (regionalMatch) return provider.models[regionalMatch] } } const unprefixed = candidates.find((m) => !crossRegionPrefixes.some((p) => m.startsWith(p))) - if (unprefixed) return yield* getModel(providerID, ModelID.make(unprefixed)).pipe(Effect.orDie) + if (unprefixed) return provider.models[unprefixed] } else { for (const model of Object.keys(provider.models)) { - if (model.includes(item)) return yield* getModel(providerID, ModelID.make(model)).pipe(Effect.orDie) + if (model.includes(item)) return provider.models[model] } } } diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts index aafc19255b..da708a59d6 100644 --- a/packages/opencode/test/provider/provider.test.ts +++ b/packages/opencode/test/provider/provider.test.ts @@ -1025,6 +1025,27 @@ test("getSmallModel respects config small_model override", async () => { }) }) +test("getSmallModel ignores invalid config small_model", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + small_model: "anthropic/not-a-real-model", + }), + ) + }, + }) + await WithInstance.provide({ + directory: tmp.path, + fn: async () => { + set("ANTHROPIC_API_KEY", "test-api-key") + expect(await getSmallModel(ProviderID.anthropic)).toBeUndefined() + }, + }) +}) + test("provider.sort prioritizes preferred models", () => { const models = [ { id: "random-model", name: "Random" },