From 96135d70a4cb3fbcbe98d90f40c8762a50d30af3 Mon Sep 17 00:00:00 2001 From: Aiden Date: Sun, 23 Aug 2026 02:34:33 +0000 Subject: [PATCH 1/6] refactor(opencode): remove legacy 200k pricing --- packages/core/src/models-dev.ts | 8 ---- packages/core/src/plugin/models-dev.ts | 16 ------- packages/core/src/plugin/provider/opencode.ts | 17 ++----- packages/core/src/v1/config/migrate.ts | 10 ---- packages/core/src/v1/config/provider.ts | 8 ---- packages/core/test/plugin/models-dev.test.ts | 7 --- packages/opencode/src/provider/provider.ts | 18 +------ packages/opencode/src/session/session.ts | 5 +- .../opencode/test/provider/provider.test.ts | 47 +++++++++++++++---- .../opencode/test/session/compaction.test.ts | 16 ++----- packages/sdk/js/src/v2/gen/types.gen.ts | 14 ------ packages/sdk/openapi.json | 45 ------------------ 12 files changed, 45 insertions(+), 166 deletions(-) diff --git a/packages/core/src/models-dev.ts b/packages/core/src/models-dev.ts index e577935f067a..11ae67e27849 100644 --- a/packages/core/src/models-dev.ts +++ b/packages/core/src/models-dev.ts @@ -39,14 +39,6 @@ const Cost = Schema.Struct({ cache_read: Schema.optional(Schema.Finite), cache_write: Schema.optional(Schema.Finite), tiers: Schema.optional(Schema.Array(CostTier)), - context_over_200k: Schema.optional( - Schema.Struct({ - input: Schema.Finite, - output: Schema.Finite, - cache_read: Schema.optional(Schema.Finite), - cache_write: Schema.optional(Schema.Finite), - }), - ), }) const ReasoningOption = Schema.Union([ diff --git a/packages/core/src/plugin/models-dev.ts b/packages/core/src/plugin/models-dev.ts index 075a6ed093c6..78880d060475 100644 --- a/packages/core/src/plugin/models-dev.ts +++ b/packages/core/src/plugin/models-dev.ts @@ -30,22 +30,6 @@ function cost(input: ModelsDev.Model["cost"]): ModelV2Info["cost"] { write: item.cache_write ?? 0, }, })) ?? []), - ...(input?.context_over_200k - ? [ - { - tier: { - type: "context" as const, - size: 200_000, - }, - input: input.context_over_200k.input, - output: input.context_over_200k.output, - cache: { - read: input.context_over_200k.cache_read ?? 0, - write: input.context_over_200k.cache_write ?? 0, - }, - }, - ] - : []), ] } diff --git a/packages/core/src/plugin/provider/opencode.ts b/packages/core/src/plugin/provider/opencode.ts index 7bd78cae415d..edac5053be09 100644 --- a/packages/core/src/plugin/provider/opencode.ts +++ b/packages/core/src/plugin/provider/opencode.ts @@ -225,22 +225,11 @@ function withoutCredentials(body: Readonly> | undefined) } function remoteCost(input: NonNullable<(typeof ConfigProviderV1.Model.Type)["cost"]>) { - const base = { - input: input.input, - output: input.output, - cache: { read: input.cache_read ?? 0, write: input.cache_write ?? 0 }, - } - if (!input.context_over_200k) return [base] return [ - base, { - tier: { type: "context" as const, size: 200_000 }, - input: input.context_over_200k.input, - output: input.context_over_200k.output, - cache: { - read: input.context_over_200k.cache_read ?? 0, - write: input.context_over_200k.cache_write ?? 0, - }, + input: input.input, + output: input.output, + cache: { read: input.cache_read ?? 0, write: input.cache_write ?? 0 }, }, ] } diff --git a/packages/core/src/v1/config/migrate.ts b/packages/core/src/v1/config/migrate.ts index ebe1b905ff87..4fc89f8a3650 100644 --- a/packages/core/src/v1/config/migrate.ts +++ b/packages/core/src/v1/config/migrate.ts @@ -199,16 +199,6 @@ function migrateModel(info: typeof ConfigProviderV1.Model.Type, packageName?: st output: info.cost.output, cache: { read: info.cost.cache_read, write: info.cost.cache_write }, }, - ...(info.cost.context_over_200k - ? [ - { - tier: { type: "context" as const, size: 200_000 }, - input: info.cost.context_over_200k.input, - output: info.cost.context_over_200k.output, - cache: { read: info.cost.context_over_200k.cache_read, write: info.cost.context_over_200k.cache_write }, - }, - ] - : []), ] const capabilities = info.tool_call !== undefined || info.modalities?.input !== undefined || info.modalities?.output !== undefined diff --git a/packages/core/src/v1/config/provider.ts b/packages/core/src/v1/config/provider.ts index f860a2b4ac7e..18fa0138a07c 100644 --- a/packages/core/src/v1/config/provider.ts +++ b/packages/core/src/v1/config/provider.ts @@ -34,14 +34,6 @@ export const Model = Schema.Struct({ output: Schema.Finite, cache_read: Schema.optional(Schema.Finite), cache_write: Schema.optional(Schema.Finite), - context_over_200k: Schema.optional( - Schema.Struct({ - input: Schema.Finite, - output: Schema.Finite, - cache_read: Schema.optional(Schema.Finite), - cache_write: Schema.optional(Schema.Finite), - }), - ), }), ), limit: Schema.optional( diff --git a/packages/core/test/plugin/models-dev.test.ts b/packages/core/test/plugin/models-dev.test.ts index 6c0f7e070296..05e20189922f 100644 --- a/packages/core/test/plugin/models-dev.test.ts +++ b/packages/core/test/plugin/models-dev.test.ts @@ -61,7 +61,6 @@ describe("ModelsDevPlugin", () => { cache_read: 0.25, }, ], - context_over_200k: { input: 5, output: 22.5, cache_read: 0.5 }, }, limit: { context: 1_050_000, input: 922_000, output: 128_000 }, experimental: { @@ -114,12 +113,6 @@ describe("ModelsDevPlugin", () => { output: 18, cache: { read: 0.25, write: 0 }, }, - { - tier: { type: "context", size: 200_000 }, - input: 5, - output: 22.5, - cache: { read: 0.5, write: 0 }, - }, ]) }), ) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index dba9cbece552..2f8dedb9cc3f 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -1035,13 +1035,6 @@ const ProviderCost = Schema.Struct({ output: Schema.Finite, cache: ProviderCacheCost, tiers: optional(Schema.Array(ProviderCostTier)), - experimentalOver200K: optional( - Schema.Struct({ - input: Schema.Finite, - output: Schema.Finite, - cache: ProviderCacheCost, - }), - ), }) const ProviderLimit = Schema.Struct({ @@ -1213,16 +1206,6 @@ function cost(c: ModelsDev.Model["cost"]): Model["cost"] { tier: item.tier, })) } - if (c?.context_over_200k) { - result.experimentalOver200K = { - cache: { - read: c.context_over_200k.cache_read ?? 0, - write: c.context_over_200k.cache_write ?? 0, - }, - input: c.context_over_200k.input, - output: c.context_over_200k.output, - } - } return result } @@ -1528,6 +1511,7 @@ const layer = Layer.effect( read: model?.cost?.cache_read ?? existingModel?.cost?.cache.read ?? 0, write: model?.cost?.cache_write ?? existingModel?.cost?.cache.write ?? 0, }, + tiers: existingModel?.cost.tiers, }, options: mergeDeep(existingModel?.options ?? {}, model.options ?? {}), limit: { diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index a2a91cd47b5e..2782fc6086de 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -380,10 +380,7 @@ export const getUsage = (input: { model: Provider.Model; usage: Usage; metadata? const costInfo = input.model.cost?.tiers ?.filter((item) => item.tier.type === "context" && contextTokens > item.tier.size) - .sort((a, b) => b.tier.size - a.tier.size)[0] ?? - (input.model.cost?.experimentalOver200K && contextTokens > 200_000 - ? input.model.cost.experimentalOver200K - : input.model.cost) + .sort((a, b) => b.tier.size - a.tier.size)[0] ?? input.model.cost const totalNanoAiu = input.metadata?.["copilot"]?.["totalNanoAiu"] return { cost: diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts index 32d4e3a39b10..e4e7a120f4bb 100644 --- a/packages/opencode/test/provider/provider.test.ts +++ b/packages/opencode/test/provider/provider.test.ts @@ -566,6 +566,27 @@ it.instance( }, ) +it.instance( + "model config preserves catalog cost tiers", + Effect.gen(function* () { + yield* set("REQUESTY_API_KEY", "test-api-key") + const providers = yield* list + expect(providers[ProviderV2.ID.make("requesty")].models["google/gemini-2.5-pro"].cost.tiers).toEqual([ + { + input: 2.5, + output: 15, + cache: { read: 0.25, write: 0 }, + tier: { type: "context", size: 200_000 }, + }, + ]) + }), + { + config: { + provider: { requesty: { models: { "google/gemini-2.5-pro": {} } } }, + }, + }, +) + it.instance( "model config preserves explicitly empty models.dev variants", Effect.gen(function* () { @@ -1401,11 +1422,14 @@ test("mode options and cost are derived from the base model", () => { input: 2.5, output: 15, cache_read: 0.25, - context_over_200k: { - input: 5, - output: 22.5, - cache_read: 0.5, - }, + tiers: [ + { + input: 5, + output: 22.5, + cache_read: 0.5, + tier: { type: "context", size: 272_000 }, + }, + ], }, limit: { context: 1_050_000, @@ -1449,11 +1473,14 @@ test("mode options and cost are derived from the base model", () => { const pro = Provider.fromModelsDevProvider(provider).models["gpt-5.6-sol-pro"] expect(pro.api.id).toEqual("gpt-5.6-sol") expect(pro.options).toEqual({ reasoningMode: "pro", serviceTier: "priority" }) - expect(model.cost.experimentalOver200K).toEqual({ - input: 5, - output: 22.5, - cache: { read: 0.5, write: 0 }, - }) + expect(model.cost.tiers).toEqual([ + { + input: 5, + output: 22.5, + cache: { read: 0.5, write: 0 }, + tier: { type: "context", size: 272_000 }, + }, + ]) }) test("models.dev normalization fills required response fields", () => { diff --git a/packages/opencode/test/session/compaction.test.ts b/packages/opencode/test/session/compaction.test.ts index c76dd98b8614..e56812b2d361 100644 --- a/packages/opencode/test/session/compaction.test.ts +++ b/packages/opencode/test/session/compaction.test.ts @@ -1830,7 +1830,7 @@ describe("SessionNs.getUsage", () => { expect(result.cost).toBe(0.04473525) }) - test("uses matching context cost tier before over-200k fallback", () => { + test("uses the matching context cost tier", () => { const model = createModel({ context: 1_000_000, output: 32_000, @@ -1852,11 +1852,6 @@ describe("SessionNs.getUsage", () => { tier: { type: "context", size: 500_000 }, }, ], - experimentalOver200K: { - input: 100, - output: 100, - cache: { read: 100, write: 100 }, - }, }, }) const result = SessionNs.getUsage({ @@ -1873,7 +1868,7 @@ describe("SessionNs.getUsage", () => { expect(result.cost).toBe(2.75 + 0.6 + 0.05) }) - test("falls back to over-200k pricing when no cost tier matches", () => { + test("uses base pricing when no context cost tier matches", () => { const model = createModel({ context: 1_000_000, output: 32_000, @@ -1889,11 +1884,6 @@ describe("SessionNs.getUsage", () => { tier: { type: "context", size: 500_000 }, }, ], - experimentalOver200K: { - input: 3, - output: 4, - cache: { read: 0.3, write: 1.5 }, - }, }, }) const result = SessionNs.getUsage({ @@ -1901,7 +1891,7 @@ describe("SessionNs.getUsage", () => { usage: usage({ inputTokens: 300_000, outputTokens: 100_000, totalTokens: 400_000 }), }) - expect(result.cost).toBe(0.9 + 0.4) + expect(result.cost).toBe(0.3 + 0.2) }) test.each(["@ai-sdk/anthropic", "@ai-sdk/amazon-bedrock", "@ai-sdk/google-vertex/anthropic"])( diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 90c91e9158cc..6de744a1fc2c 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1781,12 +1781,6 @@ export type ProviderConfig = { output: number cache_read?: number cache_write?: number - context_over_200k?: { - input: number - output: number - cache_read?: number - cache_write?: number - } } limit?: { context: number @@ -2083,14 +2077,6 @@ export type Model = { size: number } }> - experimentalOver200K?: { - input: number - output: number - cache: { - read: number - write: number - } - } } limit: { context: number diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index afe14bb604cc..6139079ae7bb 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -20863,25 +20863,6 @@ }, "cache_write": { "type": "number" - }, - "context_over_200k": { - "type": "object", - "properties": { - "input": { - "type": "number" - }, - "output": { - "type": "number" - }, - "cache_read": { - "type": "number" - }, - "cache_write": { - "type": "number" - } - }, - "required": ["input", "output"], - "additionalProperties": false } }, "required": ["input", "output"], @@ -21738,32 +21719,6 @@ "required": ["input", "output", "cache", "tier"], "additionalProperties": false } - }, - "experimentalOver200K": { - "type": "object", - "properties": { - "input": { - "type": "number" - }, - "output": { - "type": "number" - }, - "cache": { - "type": "object", - "properties": { - "read": { - "type": "number" - }, - "write": { - "type": "number" - } - }, - "required": ["read", "write"], - "additionalProperties": false - } - }, - "required": ["input", "output", "cache"], - "additionalProperties": false } }, "required": ["input", "output", "cache"], From 3a9d73e1e8b06643bb2470b03ca36e9140bc0154 Mon Sep 17 00:00:00 2001 From: Aiden Date: Sun, 23 Aug 2026 16:27:31 +0000 Subject: [PATCH 2/6] fix(opencode): preserve legacy pricing inputs --- packages/core/src/models-dev.ts | 8 ++ packages/core/src/plugin/models-dev.ts | 24 +++++- packages/core/src/plugin/provider/opencode.ts | 11 +++ packages/core/src/v1/config/migrate.ts | 11 +++ packages/core/src/v1/config/provider.ts | 8 ++ packages/core/test/plugin/models-dev.test.ts | 27 +++++++ packages/opencode/src/provider/provider.ts | 35 +++++++-- .../opencode/test/provider/provider.test.ts | 73 +++++++++++++++++++ packages/sdk/js/src/v2/gen/types.gen.ts | 6 ++ packages/sdk/openapi.json | 19 +++++ 10 files changed, 213 insertions(+), 9 deletions(-) diff --git a/packages/core/src/models-dev.ts b/packages/core/src/models-dev.ts index 11ae67e27849..e577935f067a 100644 --- a/packages/core/src/models-dev.ts +++ b/packages/core/src/models-dev.ts @@ -39,6 +39,14 @@ const Cost = Schema.Struct({ cache_read: Schema.optional(Schema.Finite), cache_write: Schema.optional(Schema.Finite), tiers: Schema.optional(Schema.Array(CostTier)), + context_over_200k: Schema.optional( + Schema.Struct({ + input: Schema.Finite, + output: Schema.Finite, + cache_read: Schema.optional(Schema.Finite), + cache_write: Schema.optional(Schema.Finite), + }), + ), }) const ReasoningOption = Schema.Union([ diff --git a/packages/core/src/plugin/models-dev.ts b/packages/core/src/plugin/models-dev.ts index 78880d060475..bfb01c261e9a 100644 --- a/packages/core/src/plugin/models-dev.ts +++ b/packages/core/src/plugin/models-dev.ts @@ -19,9 +19,8 @@ function cost(input: ModelsDev.Model["cost"]): ModelV2Info["cost"] { write: input?.cache_write ?? 0, }, } - return [ - base, - ...(input?.tiers?.map((item) => ({ + const tiers = + input?.tiers?.map((item) => ({ tier: item.tier, input: item.input, output: item.output, @@ -29,7 +28,24 @@ function cost(input: ModelsDev.Model["cost"]): ModelV2Info["cost"] { read: item.cache_read ?? 0, write: item.cache_write ?? 0, }, - })) ?? []), + })) ?? [] + const legacy = input?.context_over_200k + return [ + base, + ...tiers, + ...(legacy && tiers.length === 0 + ? [ + { + tier: { type: "context" as const, size: 200_000 }, + input: legacy.input, + output: legacy.output, + cache: { + read: legacy.cache_read ?? 0, + write: legacy.cache_write ?? 0, + }, + }, + ] + : []), ] } diff --git a/packages/core/src/plugin/provider/opencode.ts b/packages/core/src/plugin/provider/opencode.ts index edac5053be09..781454d584cc 100644 --- a/packages/core/src/plugin/provider/opencode.ts +++ b/packages/core/src/plugin/provider/opencode.ts @@ -225,12 +225,23 @@ function withoutCredentials(body: Readonly> | undefined) } function remoteCost(input: NonNullable<(typeof ConfigProviderV1.Model.Type)["cost"]>) { + const legacy = input.context_over_200k return [ { input: input.input, output: input.output, cache: { read: input.cache_read ?? 0, write: input.cache_write ?? 0 }, }, + ...(legacy + ? [ + { + tier: { type: "context" as const, size: 200_000 }, + input: legacy.input, + output: legacy.output, + cache: { read: legacy.cache_read ?? 0, write: legacy.cache_write ?? 0 }, + }, + ] + : []), ] } diff --git a/packages/core/src/v1/config/migrate.ts b/packages/core/src/v1/config/migrate.ts index 4fc89f8a3650..c3283b282cc1 100644 --- a/packages/core/src/v1/config/migrate.ts +++ b/packages/core/src/v1/config/migrate.ts @@ -193,12 +193,23 @@ function migrateModel(info: typeof ConfigProviderV1.Model.Type, packageName?: st const packageID = info.provider?.npm ?? packageName const lowerer = ConfigProviderOptionsV1.get(packageID) const request = info.options && lowerer.request(info.options) + const legacy = info.cost?.context_over_200k const costs = info.cost && [ { input: info.cost.input, output: info.cost.output, cache: { read: info.cost.cache_read, write: info.cost.cache_write }, }, + ...(legacy + ? [ + { + tier: { type: "context" as const, size: 200_000 }, + input: legacy.input, + output: legacy.output, + cache: { read: legacy.cache_read, write: legacy.cache_write }, + }, + ] + : []), ] const capabilities = info.tool_call !== undefined || info.modalities?.input !== undefined || info.modalities?.output !== undefined diff --git a/packages/core/src/v1/config/provider.ts b/packages/core/src/v1/config/provider.ts index 18fa0138a07c..f860a2b4ac7e 100644 --- a/packages/core/src/v1/config/provider.ts +++ b/packages/core/src/v1/config/provider.ts @@ -34,6 +34,14 @@ export const Model = Schema.Struct({ output: Schema.Finite, cache_read: Schema.optional(Schema.Finite), cache_write: Schema.optional(Schema.Finite), + context_over_200k: Schema.optional( + Schema.Struct({ + input: Schema.Finite, + output: Schema.Finite, + cache_read: Schema.optional(Schema.Finite), + cache_write: Schema.optional(Schema.Finite), + }), + ), }), ), limit: Schema.optional( diff --git a/packages/core/test/plugin/models-dev.test.ts b/packages/core/test/plugin/models-dev.test.ts index 05e20189922f..423be40cbd6d 100644 --- a/packages/core/test/plugin/models-dev.test.ts +++ b/packages/core/test/plugin/models-dev.test.ts @@ -61,6 +61,7 @@ describe("ModelsDevPlugin", () => { cache_read: 0.25, }, ], + context_over_200k: { input: 5, output: 22.5, cache_read: 0.5 }, }, limit: { context: 1_050_000, input: 922_000, output: 128_000 }, experimental: { @@ -75,6 +76,22 @@ describe("ModelsDevPlugin", () => { }, }, }, + legacy: { + id: "legacy", + name: "Legacy", + family: "legacy", + release_date: "2026-01-01", + attachment: false, + reasoning: false, + temperature: true, + tool_call: true, + cost: { + input: 1, + output: 2, + context_over_200k: { input: 3, output: 4, cache_read: 0.3 }, + }, + limit: { context: 1_000_000, output: 32_000 }, + }, }, }, } satisfies Record), @@ -91,6 +108,7 @@ describe("ModelsDevPlugin", () => { const providerID = ProviderV2.ID.make("acme") const base = yield* catalog.model.get(providerID, ModelV2.ID.make("gpt-5.4")) const fast = yield* catalog.model.get(providerID, ModelV2.ID.make("gpt-5.4-fast")) + const legacy = yield* catalog.model.get(providerID, ModelV2.ID.make("legacy")) expect(base?.variants).toEqual([]) expect(base?.request.body).toEqual({}) @@ -114,6 +132,15 @@ describe("ModelsDevPlugin", () => { cache: { read: 0.25, write: 0 }, }, ]) + expect(legacy?.cost).toEqual([ + { input: 1, output: 2, cache: { read: 0, write: 0 } }, + { + tier: { type: "context", size: 200_000 }, + input: 3, + output: 4, + cache: { read: 0.3, write: 0 }, + }, + ]) }), ) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 2f8dedb9cc3f..548a81b7ce66 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -1195,8 +1195,8 @@ function cost(c: ModelsDev.Model["cost"]): Model["cost"] { write: c?.cache_write ?? 0, }, } - if (c?.tiers) { - result.tiers = c.tiers.map((item) => ({ + const tiers = + c?.tiers?.map((item) => ({ input: item.input, output: item.output, cache: { @@ -1204,8 +1204,19 @@ function cost(c: ModelsDev.Model["cost"]): Model["cost"] { write: item.cache_write ?? 0, }, tier: item.tier, - })) - } + })) ?? [] + const legacy = c?.context_over_200k + if (legacy && tiers.length === 0) + tiers.push({ + input: legacy.input, + output: legacy.output, + cache: { + read: legacy.cache_read ?? 0, + write: legacy.cache_write ?? 0, + }, + tier: { type: "context", size: 200_000 }, + }) + if (tiers.length > 0) result.tiers = tiers return result } @@ -1465,6 +1476,20 @@ const layer = Layer.effect( if (model.id && model.id !== modelID) return modelID return existingModel?.name ?? modelID }) + const legacy = model.cost?.context_over_200k + const tiers = legacy + ? [ + ...(existingModel?.cost.tiers?.filter( + (item) => item.tier.type !== "context" || item.tier.size !== 200_000, + ) ?? []), + { + input: legacy.input, + output: legacy.output, + cache: { read: legacy.cache_read ?? 0, write: legacy.cache_write ?? 0 }, + tier: { type: "context" as const, size: 200_000 }, + }, + ] + : existingModel?.cost.tiers const parsedModel: Model = { id: ModelV2.ID.make(modelID), api: { @@ -1511,7 +1536,7 @@ const layer = Layer.effect( read: model?.cost?.cache_read ?? existingModel?.cost?.cache.read ?? 0, write: model?.cost?.cache_write ?? existingModel?.cost?.cache.write ?? 0, }, - tiers: existingModel?.cost.tiers, + tiers, }, options: mergeDeep(existingModel?.options ?? {}, model.options ?? {}), limit: { diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts index e4e7a120f4bb..2aa4ab73333b 100644 --- a/packages/opencode/test/provider/provider.test.ts +++ b/packages/opencode/test/provider/provider.test.ts @@ -587,6 +587,39 @@ it.instance( }, ) +it.instance( + "legacy model config pricing is normalized into a context tier", + Effect.gen(function* () { + yield* set("REQUESTY_API_KEY", "test-api-key") + const providers = yield* list + expect(providers[ProviderV2.ID.make("requesty")].models["google/gemini-2.5-pro"].cost.tiers).toEqual([ + { + input: 4, + output: 16, + cache: { read: 0.4, write: 0 }, + tier: { type: "context", size: 200_000 }, + }, + ]) + }), + { + config: { + provider: { + requesty: { + models: { + "google/gemini-2.5-pro": { + cost: { + input: 1.25, + output: 10, + context_over_200k: { input: 4, output: 16, cache_read: 0.4 }, + }, + }, + }, + }, + }, + }, + }, +) + it.instance( "model config preserves explicitly empty models.dev variants", Effect.gen(function* () { @@ -1430,6 +1463,11 @@ test("mode options and cost are derived from the base model", () => { tier: { type: "context", size: 272_000 }, }, ], + context_over_200k: { + input: 4, + output: 20, + cache_read: 0.4, + }, }, limit: { context: 1_050_000, @@ -1483,6 +1521,41 @@ test("mode options and cost are derived from the base model", () => { ]) }) +test("legacy models.dev pricing is normalized into a context tier", () => { + const provider = { + id: "legacy", + name: "Legacy", + env: [], + npm: "@ai-sdk/openai-compatible", + models: { + legacy: { + id: "legacy", + name: "Legacy", + release_date: "2026-01-01", + attachment: false, + reasoning: false, + temperature: true, + tool_call: true, + cost: { + input: 1, + output: 2, + context_over_200k: { input: 3, output: 4, cache_read: 0.3 }, + }, + limit: { context: 1_000_000, output: 32_000 }, + }, + }, + } as unknown as ModelsDev.Provider + + expect(Provider.fromModelsDevProvider(provider).models.legacy.cost.tiers).toEqual([ + { + input: 3, + output: 4, + cache: { read: 0.3, write: 0 }, + tier: { type: "context", size: 200_000 }, + }, + ]) +}) + test("models.dev normalization fills required response fields", () => { const provider = { id: "gateway", diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 6de744a1fc2c..6d3b347bdd8f 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1781,6 +1781,12 @@ export type ProviderConfig = { output: number cache_read?: number cache_write?: number + context_over_200k?: { + input: number + output: number + cache_read?: number + cache_write?: number + } } limit?: { context: number diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 6139079ae7bb..928b86e91176 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -20863,6 +20863,25 @@ }, "cache_write": { "type": "number" + }, + "context_over_200k": { + "type": "object", + "properties": { + "input": { + "type": "number" + }, + "output": { + "type": "number" + }, + "cache_read": { + "type": "number" + }, + "cache_write": { + "type": "number" + } + }, + "required": ["input", "output"], + "additionalProperties": false } }, "required": ["input", "output"], From 0344b3b2baff1aef991f21e485060b3a15c7279f Mon Sep 17 00:00:00 2001 From: Aiden Date: Sun, 23 Aug 2026 16:39:49 +0000 Subject: [PATCH 3/6] fix(opencode): prefer explicit pricing tiers --- packages/opencode/src/provider/provider.ts | 2 +- .../opencode/test/provider/provider.test.ts | 23 +++++++++++++++---- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 548a81b7ce66..0bd5a7f60284 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -1476,7 +1476,7 @@ const layer = Layer.effect( if (model.id && model.id !== modelID) return modelID return existingModel?.name ?? modelID }) - const legacy = model.cost?.context_over_200k + const legacy = existingModel?.cost.tiers?.length ? undefined : model.cost?.context_over_200k const tiers = legacy ? [ ...(existingModel?.cost.tiers?.filter( diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts index 2aa4ab73333b..d4cdf211dea1 100644 --- a/packages/opencode/test/provider/provider.test.ts +++ b/packages/opencode/test/provider/provider.test.ts @@ -582,7 +582,19 @@ it.instance( }), { config: { - provider: { requesty: { models: { "google/gemini-2.5-pro": {} } } }, + provider: { + requesty: { + models: { + "google/gemini-2.5-pro": { + cost: { + input: 1.25, + output: 10, + context_over_200k: { input: 4, output: 16, cache_read: 0.4 }, + }, + }, + }, + }, + }, }, }, ) @@ -590,9 +602,8 @@ it.instance( it.instance( "legacy model config pricing is normalized into a context tier", Effect.gen(function* () { - yield* set("REQUESTY_API_KEY", "test-api-key") const providers = yield* list - expect(providers[ProviderV2.ID.make("requesty")].models["google/gemini-2.5-pro"].cost.tiers).toEqual([ + expect(providers[ProviderV2.ID.make("legacy")].models.legacy.cost.tiers).toEqual([ { input: 4, output: 16, @@ -604,9 +615,11 @@ it.instance( { config: { provider: { - requesty: { + legacy: { + npm: "@ai-sdk/openai-compatible", + options: { apiKey: "test-api-key" }, models: { - "google/gemini-2.5-pro": { + legacy: { cost: { input: 1.25, output: 10, From c8a13619cd715fbc1cc45c3b2638de570dd8abdd Mon Sep 17 00:00:00 2001 From: Aiden Date: Sun, 23 Aug 2026 16:53:55 +0000 Subject: [PATCH 4/6] fix(opencode): close tier precedence gaps --- packages/core/src/config/plugin/provider.ts | 20 +++++- packages/core/src/config/provider.ts | 5 ++ packages/core/src/plugin/models-dev.ts | 4 +- packages/core/src/plugin/provider/opencode.ts | 12 ++-- packages/core/src/v1/config/migrate.ts | 21 +++--- packages/core/test/config/provider.test.ts | 65 +++++++++++++++++++ packages/core/test/plugin/models-dev.test.ts | 7 +- packages/sdk/js/src/gen/types.gen.ts | 8 ++- 8 files changed, 121 insertions(+), 21 deletions(-) diff --git a/packages/core/src/config/plugin/provider.ts b/packages/core/src/config/plugin/provider.ts index 6f6e0528da73..bbe5718298fa 100644 --- a/packages/core/src/config/plugin/provider.ts +++ b/packages/core/src/config/plugin/provider.ts @@ -91,7 +91,8 @@ export const Plugin = define({ } } if (config.cost !== undefined) { - model.cost = (Array.isArray(config.cost) ? config.cost : [config.cost]).map((cost) => ({ + const input = Array.isArray(config.cost) ? config.cost : [config.cost] + const next = input.map((cost) => ({ tier: cost.tier && { ...cost.tier }, input: cost.input, output: cost.output, @@ -100,6 +101,23 @@ export const Plugin = define({ write: cost.cache?.write ?? 0, }, })) + const explicit = next.filter((cost) => cost.tier?.type === "context") + const existing = model.cost.filter((cost) => cost.tier?.type === "context") + const legacy = input.find((cost) => cost.tier === undefined)?.context_over_200k + model.cost = [ + ...next.filter((cost) => cost.tier === undefined), + ...(explicit.length > 0 ? explicit : existing), + ...(legacy && explicit.length === 0 && existing.length === 0 + ? [ + { + tier: { type: "context" as const, size: 200_000 }, + input: legacy.input, + output: legacy.output, + cache: { read: legacy.cache?.read ?? 0, write: legacy.cache?.write ?? 0 }, + }, + ] + : []), + ] } if (config.disabled !== undefined) model.enabled = !config.disabled if (config.limit !== undefined) model.limit = { ...model.limit, ...config.limit } diff --git a/packages/core/src/config/provider.ts b/packages/core/src/config/provider.ts index 1b547570783a..00a4a2322f1e 100644 --- a/packages/core/src/config/provider.ts +++ b/packages/core/src/config/provider.ts @@ -22,6 +22,11 @@ class Cost extends Schema.Class("ConfigV2.Model.Cost")({ input: Schema.Finite, output: Schema.Finite, cache: Cache.pipe(Schema.optional), + context_over_200k: Schema.Struct({ + input: Schema.Finite, + output: Schema.Finite, + cache: Cache.pipe(Schema.optional), + }).pipe(Schema.optional), }) {} class Limit extends Schema.Class("ConfigV2.Model.Limit")({ diff --git a/packages/core/src/plugin/models-dev.ts b/packages/core/src/plugin/models-dev.ts index bfb01c261e9a..d9ea25072968 100644 --- a/packages/core/src/plugin/models-dev.ts +++ b/packages/core/src/plugin/models-dev.ts @@ -51,8 +51,10 @@ function cost(input: ModelsDev.Model["cost"]): ModelV2Info["cost"] { function mergeCost(base: ModelV2Info["cost"], override: ModelsDev.Model["cost"] | undefined) { if (!override) return base - const next = cost(override) const [baseDefault, ...baseTiers] = base + const next = cost( + baseTiers.length > 0 && !override.tiers?.length ? { ...override, context_over_200k: undefined } : override, + ) const [nextDefault, ...nextTiers] = next const tierKey = (item: ModelV2Info["cost"][number]) => `${item.tier?.type ?? "base"}:${item.tier?.size ?? 0}` const merge = (left: ModelV2Info["cost"][number], right: ModelV2Info["cost"][number]) => ({ diff --git a/packages/core/src/plugin/provider/opencode.ts b/packages/core/src/plugin/provider/opencode.ts index 781454d584cc..5daf29021f5b 100644 --- a/packages/core/src/plugin/provider/opencode.ts +++ b/packages/core/src/plugin/provider/opencode.ts @@ -2,7 +2,7 @@ import { Duration, Effect, Schema, Semaphore, Stream } from "effect" import type { Scope } from "effect" import type { IntegrationOAuthMethodRegistration } from "@opencode-ai/plugin/v2/effect/integration" import { define } from "@opencode-ai/plugin/v2/effect/plugin" -import type { CredentialValue } from "@opencode-ai/sdk/v2/types" +import type { CredentialValue, ModelV2Info } from "@opencode-ai/sdk/v2/types" import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http" import { EventV2 } from "../../event" import { Credential } from "../../credential" @@ -162,7 +162,10 @@ export const OpencodePlugin = define cost.tier?.type === "context"), + ) } model.status = config.status ?? "active" model.enabled = config.status !== "deprecated" @@ -224,7 +227,7 @@ function withoutCredentials(body: Readonly> | undefined) return Object.fromEntries(Object.entries(body ?? {}).filter(([key]) => key !== "apiKey" && key !== "headers")) } -function remoteCost(input: NonNullable<(typeof ConfigProviderV1.Model.Type)["cost"]>) { +function remoteCost(input: NonNullable<(typeof ConfigProviderV1.Model.Type)["cost"]>, tiers: ModelV2Info["cost"] = []) { const legacy = input.context_over_200k return [ { @@ -232,7 +235,8 @@ function remoteCost(input: NonNullable<(typeof ConfigProviderV1.Model.Type)["cos output: input.output, cache: { read: input.cache_read ?? 0, write: input.cache_write ?? 0 }, }, - ...(legacy + ...tiers, + ...(legacy && tiers.length === 0 ? [ { tier: { type: "context" as const, size: 200_000 }, diff --git a/packages/core/src/v1/config/migrate.ts b/packages/core/src/v1/config/migrate.ts index c3283b282cc1..fddde8a24a73 100644 --- a/packages/core/src/v1/config/migrate.ts +++ b/packages/core/src/v1/config/migrate.ts @@ -194,23 +194,20 @@ function migrateModel(info: typeof ConfigProviderV1.Model.Type, packageName?: st const lowerer = ConfigProviderOptionsV1.get(packageID) const request = info.options && lowerer.request(info.options) const legacy = info.cost?.context_over_200k - const costs = info.cost && [ - { - input: info.cost.input, - output: info.cost.output, - cache: { read: info.cost.cache_read, write: info.cost.cache_write }, - }, + const costs = info.cost && { + input: info.cost.input, + output: info.cost.output, + cache: { read: info.cost.cache_read, write: info.cost.cache_write }, ...(legacy - ? [ - { - tier: { type: "context" as const, size: 200_000 }, + ? { + context_over_200k: { input: legacy.input, output: legacy.output, cache: { read: legacy.cache_read, write: legacy.cache_write }, }, - ] - : []), - ] + } + : {}), + } const capabilities = info.tool_call !== undefined || info.modalities?.input !== undefined || info.modalities?.output !== undefined ? { tools: info.tool_call ?? false, input: info.modalities?.input ?? [], output: info.modalities?.output ?? [] } diff --git a/packages/core/test/config/provider.test.ts b/packages/core/test/config/provider.test.ts index 605bcd63cf97..ec5060948a82 100644 --- a/packages/core/test/config/provider.test.ts +++ b/packages/core/test/config/provider.test.ts @@ -55,6 +55,71 @@ function request(headers: Record, variant?: string) { const decode = Schema.decodeUnknownSync(Config.Info) describe("ConfigProviderPlugin.Plugin", () => { + it.effect("prefers catalog tiers over legacy config pricing", () => + Effect.gen(function* () { + const catalog = yield* Catalog.Service + const providerID = ProviderV2.ID.opencode + const modelID = ModelV2.ID.make("alpha-gpt-next") + const config = Config.Service.of({ + entries: () => + Effect.succeed([ + new Config.Document({ + type: "document", + info: decode({ + providers: { + opencode: { + models: { + "alpha-gpt-next": { + cost: [ + { input: 1, output: 2 }, + { + tier: { type: "context", size: 272_000 }, + input: 3, + output: 4, + cache: { read: 0.3 }, + }, + ], + }, + }, + }, + }, + }), + }), + new Config.Document({ + type: "document", + info: decode({ + providers: { + opencode: { + models: { + "alpha-gpt-next": { + cost: { + input: 5, + output: 6, + context_over_200k: { input: 100, output: 100 }, + }, + }, + }, + }, + }, + }), + }), + ]), + }) + + yield* addPlugin(config) + + expect(required(yield* catalog.model.get(providerID, modelID)).cost).toEqual([ + { input: 5, output: 6, cache: { read: 0, write: 0 }, tier: undefined }, + { + tier: { type: "context", size: 272_000 }, + input: 3, + output: 4, + cache: { read: 0.3, write: 0 }, + }, + ]) + }), + ) + it.effect("keeps configured model variant bodies unchanged", () => Effect.gen(function* () { const catalog = yield* Catalog.Service diff --git a/packages/core/test/plugin/models-dev.test.ts b/packages/core/test/plugin/models-dev.test.ts index 423be40cbd6d..6cef52b0ae70 100644 --- a/packages/core/test/plugin/models-dev.test.ts +++ b/packages/core/test/plugin/models-dev.test.ts @@ -67,7 +67,12 @@ describe("ModelsDevPlugin", () => { experimental: { modes: { fast: { - cost: { input: 5, output: 30, cache_read: 0.5 }, + cost: { + input: 5, + output: 30, + cache_read: 0.5, + context_over_200k: { input: 100, output: 100 }, + }, provider: { headers: { "x-mode": "fast" }, body: { service_tier: "priority" }, diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts index 5e4fd8906155..1ed3f23119f5 100644 --- a/packages/sdk/js/src/gen/types.gen.ts +++ b/packages/sdk/js/src/gen/types.gen.ts @@ -1489,14 +1489,18 @@ export type Model = { read: number write: number } - experimentalOver200K?: { + tiers?: Array<{ input: number output: number cache: { read: number write: number } - } + tier: { + type: "context" + size: number + } + }> } limit: { context: number From 7eea69360b2d6253539c60e9670b3226afe7192c Mon Sep 17 00:00:00 2001 From: Aiden Date: Sun, 23 Aug 2026 17:04:01 +0000 Subject: [PATCH 5/6] refactor(opencode): limit pricing changes to legacy runtime --- packages/core/src/config/plugin/provider.ts | 20 +----- packages/core/src/config/provider.ts | 5 -- packages/core/src/plugin/models-dev.ts | 30 ++++----- packages/core/src/plugin/provider/opencode.ts | 38 +++++------ packages/core/src/v1/config/migrate.ts | 30 +++++---- packages/core/test/config/provider.test.ts | 65 ------------------- packages/core/test/plugin/models-dev.test.ts | 33 ++-------- 7 files changed, 52 insertions(+), 169 deletions(-) diff --git a/packages/core/src/config/plugin/provider.ts b/packages/core/src/config/plugin/provider.ts index bbe5718298fa..6f6e0528da73 100644 --- a/packages/core/src/config/plugin/provider.ts +++ b/packages/core/src/config/plugin/provider.ts @@ -91,8 +91,7 @@ export const Plugin = define({ } } if (config.cost !== undefined) { - const input = Array.isArray(config.cost) ? config.cost : [config.cost] - const next = input.map((cost) => ({ + model.cost = (Array.isArray(config.cost) ? config.cost : [config.cost]).map((cost) => ({ tier: cost.tier && { ...cost.tier }, input: cost.input, output: cost.output, @@ -101,23 +100,6 @@ export const Plugin = define({ write: cost.cache?.write ?? 0, }, })) - const explicit = next.filter((cost) => cost.tier?.type === "context") - const existing = model.cost.filter((cost) => cost.tier?.type === "context") - const legacy = input.find((cost) => cost.tier === undefined)?.context_over_200k - model.cost = [ - ...next.filter((cost) => cost.tier === undefined), - ...(explicit.length > 0 ? explicit : existing), - ...(legacy && explicit.length === 0 && existing.length === 0 - ? [ - { - tier: { type: "context" as const, size: 200_000 }, - input: legacy.input, - output: legacy.output, - cache: { read: legacy.cache?.read ?? 0, write: legacy.cache?.write ?? 0 }, - }, - ] - : []), - ] } if (config.disabled !== undefined) model.enabled = !config.disabled if (config.limit !== undefined) model.limit = { ...model.limit, ...config.limit } diff --git a/packages/core/src/config/provider.ts b/packages/core/src/config/provider.ts index 00a4a2322f1e..1b547570783a 100644 --- a/packages/core/src/config/provider.ts +++ b/packages/core/src/config/provider.ts @@ -22,11 +22,6 @@ class Cost extends Schema.Class("ConfigV2.Model.Cost")({ input: Schema.Finite, output: Schema.Finite, cache: Cache.pipe(Schema.optional), - context_over_200k: Schema.Struct({ - input: Schema.Finite, - output: Schema.Finite, - cache: Cache.pipe(Schema.optional), - }).pipe(Schema.optional), }) {} class Limit extends Schema.Class("ConfigV2.Model.Limit")({ diff --git a/packages/core/src/plugin/models-dev.ts b/packages/core/src/plugin/models-dev.ts index d9ea25072968..075a6ed093c6 100644 --- a/packages/core/src/plugin/models-dev.ts +++ b/packages/core/src/plugin/models-dev.ts @@ -19,8 +19,9 @@ function cost(input: ModelsDev.Model["cost"]): ModelV2Info["cost"] { write: input?.cache_write ?? 0, }, } - const tiers = - input?.tiers?.map((item) => ({ + return [ + base, + ...(input?.tiers?.map((item) => ({ tier: item.tier, input: item.input, output: item.output, @@ -28,20 +29,19 @@ function cost(input: ModelsDev.Model["cost"]): ModelV2Info["cost"] { read: item.cache_read ?? 0, write: item.cache_write ?? 0, }, - })) ?? [] - const legacy = input?.context_over_200k - return [ - base, - ...tiers, - ...(legacy && tiers.length === 0 + })) ?? []), + ...(input?.context_over_200k ? [ { - tier: { type: "context" as const, size: 200_000 }, - input: legacy.input, - output: legacy.output, + tier: { + type: "context" as const, + size: 200_000, + }, + input: input.context_over_200k.input, + output: input.context_over_200k.output, cache: { - read: legacy.cache_read ?? 0, - write: legacy.cache_write ?? 0, + read: input.context_over_200k.cache_read ?? 0, + write: input.context_over_200k.cache_write ?? 0, }, }, ] @@ -51,10 +51,8 @@ function cost(input: ModelsDev.Model["cost"]): ModelV2Info["cost"] { function mergeCost(base: ModelV2Info["cost"], override: ModelsDev.Model["cost"] | undefined) { if (!override) return base + const next = cost(override) const [baseDefault, ...baseTiers] = base - const next = cost( - baseTiers.length > 0 && !override.tiers?.length ? { ...override, context_over_200k: undefined } : override, - ) const [nextDefault, ...nextTiers] = next const tierKey = (item: ModelV2Info["cost"][number]) => `${item.tier?.type ?? "base"}:${item.tier?.size ?? 0}` const merge = (left: ModelV2Info["cost"][number], right: ModelV2Info["cost"][number]) => ({ diff --git a/packages/core/src/plugin/provider/opencode.ts b/packages/core/src/plugin/provider/opencode.ts index 5daf29021f5b..7bd78cae415d 100644 --- a/packages/core/src/plugin/provider/opencode.ts +++ b/packages/core/src/plugin/provider/opencode.ts @@ -2,7 +2,7 @@ import { Duration, Effect, Schema, Semaphore, Stream } from "effect" import type { Scope } from "effect" import type { IntegrationOAuthMethodRegistration } from "@opencode-ai/plugin/v2/effect/integration" import { define } from "@opencode-ai/plugin/v2/effect/plugin" -import type { CredentialValue, ModelV2Info } from "@opencode-ai/sdk/v2/types" +import type { CredentialValue } from "@opencode-ai/sdk/v2/types" import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http" import { EventV2 } from "../../event" import { Credential } from "../../credential" @@ -162,10 +162,7 @@ export const OpencodePlugin = define cost.tier?.type === "context"), - ) + model.cost = remoteCost(config.cost) } model.status = config.status ?? "active" model.enabled = config.status !== "deprecated" @@ -227,25 +224,24 @@ function withoutCredentials(body: Readonly> | undefined) return Object.fromEntries(Object.entries(body ?? {}).filter(([key]) => key !== "apiKey" && key !== "headers")) } -function remoteCost(input: NonNullable<(typeof ConfigProviderV1.Model.Type)["cost"]>, tiers: ModelV2Info["cost"] = []) { - const legacy = input.context_over_200k +function remoteCost(input: NonNullable<(typeof ConfigProviderV1.Model.Type)["cost"]>) { + const base = { + input: input.input, + output: input.output, + cache: { read: input.cache_read ?? 0, write: input.cache_write ?? 0 }, + } + if (!input.context_over_200k) return [base] return [ + base, { - input: input.input, - output: input.output, - cache: { read: input.cache_read ?? 0, write: input.cache_write ?? 0 }, + tier: { type: "context" as const, size: 200_000 }, + input: input.context_over_200k.input, + output: input.context_over_200k.output, + cache: { + read: input.context_over_200k.cache_read ?? 0, + write: input.context_over_200k.cache_write ?? 0, + }, }, - ...tiers, - ...(legacy && tiers.length === 0 - ? [ - { - tier: { type: "context" as const, size: 200_000 }, - input: legacy.input, - output: legacy.output, - cache: { read: legacy.cache_read ?? 0, write: legacy.cache_write ?? 0 }, - }, - ] - : []), ] } diff --git a/packages/core/src/v1/config/migrate.ts b/packages/core/src/v1/config/migrate.ts index fddde8a24a73..ebe1b905ff87 100644 --- a/packages/core/src/v1/config/migrate.ts +++ b/packages/core/src/v1/config/migrate.ts @@ -193,21 +193,23 @@ function migrateModel(info: typeof ConfigProviderV1.Model.Type, packageName?: st const packageID = info.provider?.npm ?? packageName const lowerer = ConfigProviderOptionsV1.get(packageID) const request = info.options && lowerer.request(info.options) - const legacy = info.cost?.context_over_200k - const costs = info.cost && { - input: info.cost.input, - output: info.cost.output, - cache: { read: info.cost.cache_read, write: info.cost.cache_write }, - ...(legacy - ? { - context_over_200k: { - input: legacy.input, - output: legacy.output, - cache: { read: legacy.cache_read, write: legacy.cache_write }, + const costs = info.cost && [ + { + input: info.cost.input, + output: info.cost.output, + cache: { read: info.cost.cache_read, write: info.cost.cache_write }, + }, + ...(info.cost.context_over_200k + ? [ + { + tier: { type: "context" as const, size: 200_000 }, + input: info.cost.context_over_200k.input, + output: info.cost.context_over_200k.output, + cache: { read: info.cost.context_over_200k.cache_read, write: info.cost.context_over_200k.cache_write }, }, - } - : {}), - } + ] + : []), + ] const capabilities = info.tool_call !== undefined || info.modalities?.input !== undefined || info.modalities?.output !== undefined ? { tools: info.tool_call ?? false, input: info.modalities?.input ?? [], output: info.modalities?.output ?? [] } diff --git a/packages/core/test/config/provider.test.ts b/packages/core/test/config/provider.test.ts index ec5060948a82..605bcd63cf97 100644 --- a/packages/core/test/config/provider.test.ts +++ b/packages/core/test/config/provider.test.ts @@ -55,71 +55,6 @@ function request(headers: Record, variant?: string) { const decode = Schema.decodeUnknownSync(Config.Info) describe("ConfigProviderPlugin.Plugin", () => { - it.effect("prefers catalog tiers over legacy config pricing", () => - Effect.gen(function* () { - const catalog = yield* Catalog.Service - const providerID = ProviderV2.ID.opencode - const modelID = ModelV2.ID.make("alpha-gpt-next") - const config = Config.Service.of({ - entries: () => - Effect.succeed([ - new Config.Document({ - type: "document", - info: decode({ - providers: { - opencode: { - models: { - "alpha-gpt-next": { - cost: [ - { input: 1, output: 2 }, - { - tier: { type: "context", size: 272_000 }, - input: 3, - output: 4, - cache: { read: 0.3 }, - }, - ], - }, - }, - }, - }, - }), - }), - new Config.Document({ - type: "document", - info: decode({ - providers: { - opencode: { - models: { - "alpha-gpt-next": { - cost: { - input: 5, - output: 6, - context_over_200k: { input: 100, output: 100 }, - }, - }, - }, - }, - }, - }), - }), - ]), - }) - - yield* addPlugin(config) - - expect(required(yield* catalog.model.get(providerID, modelID)).cost).toEqual([ - { input: 5, output: 6, cache: { read: 0, write: 0 }, tier: undefined }, - { - tier: { type: "context", size: 272_000 }, - input: 3, - output: 4, - cache: { read: 0.3, write: 0 }, - }, - ]) - }), - ) - it.effect("keeps configured model variant bodies unchanged", () => Effect.gen(function* () { const catalog = yield* Catalog.Service diff --git a/packages/core/test/plugin/models-dev.test.ts b/packages/core/test/plugin/models-dev.test.ts index 6cef52b0ae70..6c0f7e070296 100644 --- a/packages/core/test/plugin/models-dev.test.ts +++ b/packages/core/test/plugin/models-dev.test.ts @@ -67,12 +67,7 @@ describe("ModelsDevPlugin", () => { experimental: { modes: { fast: { - cost: { - input: 5, - output: 30, - cache_read: 0.5, - context_over_200k: { input: 100, output: 100 }, - }, + cost: { input: 5, output: 30, cache_read: 0.5 }, provider: { headers: { "x-mode": "fast" }, body: { service_tier: "priority" }, @@ -81,22 +76,6 @@ describe("ModelsDevPlugin", () => { }, }, }, - legacy: { - id: "legacy", - name: "Legacy", - family: "legacy", - release_date: "2026-01-01", - attachment: false, - reasoning: false, - temperature: true, - tool_call: true, - cost: { - input: 1, - output: 2, - context_over_200k: { input: 3, output: 4, cache_read: 0.3 }, - }, - limit: { context: 1_000_000, output: 32_000 }, - }, }, }, } satisfies Record), @@ -113,7 +92,6 @@ describe("ModelsDevPlugin", () => { const providerID = ProviderV2.ID.make("acme") const base = yield* catalog.model.get(providerID, ModelV2.ID.make("gpt-5.4")) const fast = yield* catalog.model.get(providerID, ModelV2.ID.make("gpt-5.4-fast")) - const legacy = yield* catalog.model.get(providerID, ModelV2.ID.make("legacy")) expect(base?.variants).toEqual([]) expect(base?.request.body).toEqual({}) @@ -136,14 +114,11 @@ describe("ModelsDevPlugin", () => { output: 18, cache: { read: 0.25, write: 0 }, }, - ]) - expect(legacy?.cost).toEqual([ - { input: 1, output: 2, cache: { read: 0, write: 0 } }, { tier: { type: "context", size: 200_000 }, - input: 3, - output: 4, - cache: { read: 0.3, write: 0 }, + input: 5, + output: 22.5, + cache: { read: 0.5, write: 0 }, }, ]) }), From 45e0a3af36a06b9d18b804e61161179123bf452c Mon Sep 17 00:00:00 2001 From: Aiden Date: Sun, 23 Aug 2026 17:08:15 +0000 Subject: [PATCH 6/6] fix(opencode): preserve external pricing contracts --- packages/opencode/src/provider/provider.ts | 24 ++++++++++++++++- .../opencode/test/provider/provider.test.ts | 10 ++++++- packages/sdk/js/src/gen/types.gen.ts | 8 ++---- packages/sdk/js/src/v2/gen/types.gen.ts | 8 ++++++ packages/sdk/openapi.json | 26 +++++++++++++++++++ 5 files changed, 68 insertions(+), 8 deletions(-) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 0bd5a7f60284..2f6be0e5f723 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -1035,6 +1035,13 @@ const ProviderCost = Schema.Struct({ output: Schema.Finite, cache: ProviderCacheCost, tiers: optional(Schema.Array(ProviderCostTier)), + experimentalOver200K: optional( + Schema.Struct({ + input: Schema.Finite, + output: Schema.Finite, + cache: ProviderCacheCost, + }), + ), }) const ProviderLimit = Schema.Struct({ @@ -1087,6 +1094,21 @@ export const ConfigProvidersResult = Schema.Struct({ export type ConfigProvidersResult = Types.DeepMutable> export function toPublicInfo(provider: Info): Info { + const result = cloneInfo(provider) + for (const model of Object.values(result.models)) { + if (model.cost.experimentalOver200K) continue + if (model.cost.tiers?.length !== 1) continue + const [tier] = model.cost.tiers + model.cost.experimentalOver200K = { + input: tier.input, + output: tier.output, + cache: { ...tier.cache }, + } + } + return result +} + +function cloneInfo(provider: Info): Info { return JSON.parse( JSON.stringify( { @@ -1372,7 +1394,7 @@ const layer = Layer.effect( const cfg = yield* config.get() const modelsDev = yield* modelsDevSvc.get() const catalog = mapValues(modelsDev, fromModelsDevProvider) - const database = mapValues(catalog, toPublicInfo) + const database = mapValues(catalog, cloneInfo) const providers: Record = {} as Record const languages = new Map() diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts index d4cdf211dea1..8e4eb9beef83 100644 --- a/packages/opencode/test/provider/provider.test.ts +++ b/packages/opencode/test/provider/provider.test.ts @@ -571,7 +571,9 @@ it.instance( Effect.gen(function* () { yield* set("REQUESTY_API_KEY", "test-api-key") const providers = yield* list - expect(providers[ProviderV2.ID.make("requesty")].models["google/gemini-2.5-pro"].cost.tiers).toEqual([ + const provider = providers[ProviderV2.ID.make("requesty")] + const model = provider.models["google/gemini-2.5-pro"] + expect(model.cost.tiers).toEqual([ { input: 2.5, output: 15, @@ -579,6 +581,12 @@ it.instance( tier: { type: "context", size: 200_000 }, }, ]) + expect(model.cost.experimentalOver200K).toBeUndefined() + expect(Provider.toPublicInfo(provider).models["google/gemini-2.5-pro"].cost.experimentalOver200K).toEqual({ + input: 2.5, + output: 15, + cache: { read: 0.25, write: 0 }, + }) }), { config: { diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts index 1ed3f23119f5..5e4fd8906155 100644 --- a/packages/sdk/js/src/gen/types.gen.ts +++ b/packages/sdk/js/src/gen/types.gen.ts @@ -1489,18 +1489,14 @@ export type Model = { read: number write: number } - tiers?: Array<{ + experimentalOver200K?: { input: number output: number cache: { read: number write: number } - tier: { - type: "context" - size: number - } - }> + } } limit: { context: number diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 6d3b347bdd8f..90c91e9158cc 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -2083,6 +2083,14 @@ export type Model = { size: number } }> + experimentalOver200K?: { + input: number + output: number + cache: { + read: number + write: number + } + } } limit: { context: number diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 928b86e91176..afe14bb604cc 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -21738,6 +21738,32 @@ "required": ["input", "output", "cache", "tier"], "additionalProperties": false } + }, + "experimentalOver200K": { + "type": "object", + "properties": { + "input": { + "type": "number" + }, + "output": { + "type": "number" + }, + "cache": { + "type": "object", + "properties": { + "read": { + "type": "number" + }, + "write": { + "type": "number" + } + }, + "required": ["read", "write"], + "additionalProperties": false + } + }, + "required": ["input", "output", "cache"], + "additionalProperties": false } }, "required": ["input", "output", "cache"],