diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 349073197d7c..593d63dccecf 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -1083,6 +1083,7 @@ export namespace Provider { const configProvider = config.provider?.[providerID] for (const [modelID, model] of Object.entries(provider.models)) { + const configModel = configProvider?.models?.[modelID] model.api.id = model.api.id ?? model.id ?? modelID if ( modelID === "gpt-5-chat-latest" || @@ -1099,8 +1100,31 @@ export namespace Provider { model.variants = mapValues(ProviderTransform.variants(model), (v) => v) + if (configModel?.cost) { + model.cost = { + ...model.cost, + input: configModel.cost.input ?? model.cost.input, + output: configModel.cost.output ?? model.cost.output, + cache: { + read: configModel.cost.cache_read ?? model.cost.cache.read, + write: configModel.cost.cache_write ?? model.cost.cache.write, + }, + experimentalOver200K: configModel.cost.context_over_200k + ? { + input: configModel.cost.context_over_200k.input, + output: configModel.cost.context_over_200k.output, + cache: { + read: configModel.cost.context_over_200k.cache_read ?? model.cost.experimentalOver200K?.cache.read ?? 0, + write: + configModel.cost.context_over_200k.cache_write ?? model.cost.experimentalOver200K?.cache.write ?? 0, + }, + } + : model.cost.experimentalOver200K, + } + } + // Filter out disabled variants from config - const configVariants = configProvider?.models?.[modelID]?.variants + const configVariants = configModel?.variants if (configVariants && model.variants) { const merged = mergeDeep(model.variants, configVariants) model.variants = mapValues( diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts index b14d27522406..f7840af0fa6a 100644 --- a/packages/opencode/test/provider/provider.test.ts +++ b/packages/opencode/test/provider/provider.test.ts @@ -1213,6 +1213,65 @@ test("model cost overrides existing cost values", async () => { }) }) +test("OPENCODE_CONFIG_CONTENT overrides cost for configured model", async () => { + const overrideCost = { + input: 9.876543, + output: 21.234567, + cache_read: 3.210987, + cache_write: 4.109876, + } + + const original = process.env["OPENCODE_CONFIG_CONTENT"] + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + }), + ) + }, + }) + + try { + process.env["OPENCODE_CONFIG_CONTENT"] = JSON.stringify({ + $schema: "https://opencode.ai/config.json", + provider: { + "fictional-provider": { + npm: "@ai-sdk/openai-compatible", + env: [], + models: { + "fictional-model": { + cost: overrideCost, + }, + }, + options: { + apiKey: "test-api-key", + }, + }, + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const providers = await Provider.list() + const model = providers["fictional-provider"].models["fictional-model"] + expect(model.cost.input).toBe(overrideCost.input) + expect(model.cost.output).toBe(overrideCost.output) + expect(model.cost.cache.read).toBe(overrideCost.cache_read) + expect(model.cost.cache.write).toBe(overrideCost.cache_write) + }, + }) + } finally { + if (original !== undefined) { + process.env["OPENCODE_CONFIG_CONTENT"] = original + } else { + delete process.env["OPENCODE_CONFIG_CONTENT"] + } + } +}) + test("completely new provider not in database can be configured", async () => { await using tmp = await tmpdir({ init: async (dir) => {