diff --git a/packages/core/src/plugin/models-dev.ts b/packages/core/src/plugin/models-dev.ts index f499fbafbd1..5816199aa3b 100644 --- a/packages/core/src/plugin/models-dev.ts +++ b/packages/core/src/plugin/models-dev.ts @@ -38,12 +38,33 @@ function cost(input: ModelsDev.Model["cost"]) { ] } -function variants(model: ModelsDev.Model) { - return Object.entries(model.experimental?.modes ?? {}).map(([id, item]) => ({ - id: ModelV2.VariantID.make(id), - headers: { ...(item.provider?.headers ?? {}) }, - body: { ...(item.provider?.body ?? {}) }, - })) +function variants(model: ModelsDev.Model, packageName: string | undefined): ModelV2Info["variants"] { + const result = new Map() + if (packageName === "@ai-sdk/openai" || packageName === "@ai-sdk/openai-compatible") { + const option = model.reasoning_options?.find((option) => option.type === "effort") + for (const value of option?.values ?? []) { + const id = value === null ? "none" : value + if (typeof id !== "string") continue + const variantID = ModelV2.VariantID.make(id) + result.set(variantID, { + id: variantID, + headers: {}, + body: + packageName === "@ai-sdk/openai" + ? { include: ["reasoning.encrypted_content"], reasoning: { effort: id, summary: "auto" } } + : { reasoning_effort: id }, + }) + } + } + for (const [id, item] of Object.entries(model.experimental?.modes ?? {})) { + const variantID = ModelV2.VariantID.make(id) + result.set(variantID, { + id: variantID, + headers: { ...(item.provider?.headers ?? {}) }, + body: { ...(item.provider?.body ?? {}) }, + }) + } + return [...result.values()] } function mergeVariants(model: ModelV2Info, next: ModelV2Info["variants"]) { @@ -121,7 +142,7 @@ export const ModelsDevPlugin = define({ input: [...(model.modalities?.input ?? [])], output: [...(model.modalities?.output ?? [])], } - mergeVariants(draft, variants(model)) + mergeVariants(draft, variants(model, model.provider?.npm ?? item.npm)) draft.time.released = released(model.release_date) draft.cost = cost(model.cost) draft.status = model.status ?? "active" diff --git a/packages/core/test/plugin/fixtures/models-dev.json b/packages/core/test/plugin/fixtures/models-dev.json index 41cb2381763..37cf512bfbd 100644 --- a/packages/core/test/plugin/fixtures/models-dev.json +++ b/packages/core/test/plugin/fixtures/models-dev.json @@ -9,26 +9,25 @@ "id": "local", "name": "Local", "env": [], + "models": {} + }, + "opencode": { + "id": "opencode", + "name": "OpenCode", + "env": [], + "npm": "@ai-sdk/openai-compatible", "models": { - "model": { - "id": "model", - "name": "Local Model", - "release_date": "2026-01-01", - "attachment": false, + "gpt-5.5": { + "id": "gpt-5.5", + "name": "GPT-5.5", + "release_date": "2026-04-23", + "attachment": true, "reasoning": true, - "temperature": true, + "reasoning_options": [{ "type": "effort", "values": [null, "none", "low", "medium", "high", "xhigh"] }], + "temperature": false, "tool_call": true, - "limit": { "context": 1000, "output": 100 }, - "experimental": { - "modes": { - "high": { - "provider": { "body": { "reasoning_effort": "high" } } - }, - "max": { - "provider": { "headers": { "x-variant": "max" }, "body": { "reasoning_effort": "max" } } - } - } - } + "limit": { "context": 400000, "output": 128000 }, + "provider": { "npm": "@ai-sdk/openai", "api": "https://console.opencode.ai/inference/openai/v1" } } } } diff --git a/packages/core/test/plugin/models-dev.test.ts b/packages/core/test/plugin/models-dev.test.ts index 9d0f28d76be..eb6298cb2f1 100644 --- a/packages/core/test/plugin/models-dev.test.ts +++ b/packages/core/test/plugin/models-dev.test.ts @@ -76,7 +76,7 @@ describe("ModelsDevPlugin", () => { ), ) - it.effect("loads models.dev variants without replacing existing variants", () => + it.effect("derives OpenAI reasoning variants from models.dev reasoning options", () => Effect.acquireUseRelease( Effect.sync(() => { const previous = { @@ -89,27 +89,61 @@ describe("ModelsDevPlugin", () => { }), () => Effect.gen(function* () { - const service = yield* Catalog.Service + const catalog = yield* Catalog.Service const integrations = yield* Integration.Service - yield* service.transform((catalog) => { - catalog.model.update(ProviderV2.ID.make("local"), ModelV2.ID.make("model"), (model) => { + yield* catalog.transform((catalog) => { + catalog.model.update(ProviderV2.ID.opencode, ModelV2.ID.make("gpt-5.5"), (model) => { model.variants = [ - { id: ModelV2.VariantID.make("high"), headers: { custom: "true" }, body: {} }, - { id: ModelV2.VariantID.make("custom"), headers: {}, body: { custom: true } }, + { + id: ModelV2.VariantID.make("high"), + headers: { custom: "true" }, + body: { custom: true }, + }, ] }) }) yield* ModelsDevPlugin.effect( host({ - catalog: catalogHost(service), + catalog: catalogHost(catalog), integration: integrationHost(integrations), }), ) - expect((yield* service.model.get(ProviderV2.ID.make("local"), ModelV2.ID.make("model")))?.variants).toEqual([ - expect.objectContaining({ id: "high", headers: { custom: "true" } }), - expect.objectContaining({ id: "max", headers: { "x-variant": "max" }, body: { reasoning_effort: "max" } }), - expect.objectContaining({ id: "custom", body: { custom: true } }), + expect((yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("gpt-5.5")))?.variants).toEqual([ + { + id: ModelV2.VariantID.make("none"), + headers: {}, + body: { + include: ["reasoning.encrypted_content"], + reasoning: { effort: "none", summary: "auto" }, + }, + }, + expect.objectContaining({ + id: "low", + body: { + include: ["reasoning.encrypted_content"], + reasoning: { effort: "low", summary: "auto" }, + }, + }), + expect.objectContaining({ + id: "medium", + body: { + include: ["reasoning.encrypted_content"], + reasoning: { effort: "medium", summary: "auto" }, + }, + }), + expect.objectContaining({ + id: "high", + headers: { custom: "true" }, + body: { custom: true }, + }), + expect.objectContaining({ + id: "xhigh", + body: { + include: ["reasoning.encrypted_content"], + reasoning: { effort: "xhigh", summary: "auto" }, + }, + }), ]) }).pipe(Effect.provide(ModelsDev.defaultLayer)), (previous) => @@ -119,4 +153,5 @@ describe("ModelsDevPlugin", () => { }), ), ) + })