diff --git a/bun.lock b/bun.lock index 3e9cbb84de95..79951a2b05c8 100644 --- a/bun.lock +++ b/bun.lock @@ -303,7 +303,7 @@ "@ai-sdk/google-vertex": "4.0.128", "@ai-sdk/groq": "3.0.31", "@ai-sdk/mistral": "3.0.27", - "@ai-sdk/openai": "3.0.53", + "@ai-sdk/openai": "3.0.84", "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/perplexity": "3.0.26", "@ai-sdk/provider": "3.0.8", @@ -578,7 +578,7 @@ "@ai-sdk/google-vertex": "4.0.128", "@ai-sdk/groq": "3.0.31", "@ai-sdk/mistral": "3.0.27", - "@ai-sdk/openai": "3.0.53", + "@ai-sdk/openai": "3.0.84", "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/perplexity": "3.0.26", "@ai-sdk/provider": "3.0.8", @@ -6020,7 +6020,7 @@ "@openauthjs/openauth/jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="], - "@opencode-ai/core/@ai-sdk/openai": ["@ai-sdk/openai@3.0.53", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Wld+Rbc05KaUn08uBt06eEuwcgalcIFtIl32Yp+GxuZXUQwOb6YeAuq+C6da4ch6BurFoqEaLemJVwjBb7x+PQ=="], + "@opencode-ai/core/@ai-sdk/openai": ["@ai-sdk/openai@3.0.84", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.38" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cmgbeJL0bbY0yTJH4/AdmP5E7MjWRL9G8UdhIi0JlV/So03o82ORJofW8OzwCZPTORVQblFbpZXYGDcUd9NdUQ=="], "@opencode-ai/core/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], @@ -6388,7 +6388,7 @@ "opencode/@ai-sdk/cerebras": ["@ai-sdk/cerebras@2.0.60", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.54", "@ai-sdk/provider": "3.0.12", "@ai-sdk/provider-utils": "4.0.33" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Rnok3cThg6awBwaDSyiZpgRpbV7pqxGYrA89LODCo5cuEHeP2h0AM0lLHP7zIkclAdXfOm4wldKi/S2T/DGCOw=="], - "opencode/@ai-sdk/openai": ["@ai-sdk/openai@3.0.53", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Wld+Rbc05KaUn08uBt06eEuwcgalcIFtIl32Yp+GxuZXUQwOb6YeAuq+C6da4ch6BurFoqEaLemJVwjBb7x+PQ=="], + "opencode/@ai-sdk/openai": ["@ai-sdk/openai@3.0.84", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.38" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cmgbeJL0bbY0yTJH4/AdmP5E7MjWRL9G8UdhIi0JlV/So03o82ORJofW8OzwCZPTORVQblFbpZXYGDcUd9NdUQ=="], "opencode/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], @@ -6896,6 +6896,10 @@ "@octokit/rest/@octokit/core/before-after-hook": ["before-after-hook@4.0.0", "", {}, "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ=="], + "@opencode-ai/core/@ai-sdk/openai/@ai-sdk/provider": ["@ai-sdk/provider@3.0.14", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-5X1k57JBJ4H7H1QjX7CnJYAB1I19r/trVZTMcSms7/kLNZ8RaU4Nt2agcwZzv82Hfx6Q7/TOLU7agAKeFfc8cA=="], + + "@opencode-ai/core/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.38", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-/HHGmtKllqjg1OLc023v9w9kK3laW7Z6TzfZukYQWCsGBbzB9p60zTvvpXFVcs44NZBVXL3viOa1HRKUbeee8g=="], + "@opencode-ai/desktop/@actions/artifact/@actions/http-client": ["@actions/http-client@2.2.3", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" } }, "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA=="], "@opencode-ai/web/@shikijs/transformers/@shikijs/core": ["@shikijs/core@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g=="], @@ -7118,6 +7122,10 @@ "opencode/@ai-sdk/cerebras/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.33", "", { "dependencies": { "@ai-sdk/provider": "3.0.12", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-nJ0bAfegMAIJtrzMJtbzer1cS3nb7c7DsyU1S4nrPm7ZU0Mn6SBBZv5IGZZGTbpWTJwqKTSPeZJTXalbAxt1BA=="], + "opencode/@ai-sdk/openai/@ai-sdk/provider": ["@ai-sdk/provider@3.0.14", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-5X1k57JBJ4H7H1QjX7CnJYAB1I19r/trVZTMcSms7/kLNZ8RaU4Nt2agcwZzv82Hfx6Q7/TOLU7agAKeFfc8cA=="], + + "opencode/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.38", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-/HHGmtKllqjg1OLc023v9w9kK3laW7Z6TzfZukYQWCsGBbzB9p60zTvvpXFVcs44NZBVXL3viOa1HRKUbeee8g=="], + "p-locate/p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], "pkg-dir/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], @@ -7366,6 +7374,8 @@ "@octokit/rest/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], + "@opencode-ai/core/@ai-sdk/openai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@opencode-ai/desktop/@actions/artifact/@actions/http-client/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], "@sentry/bundler-plugin-core/glob/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="], @@ -7452,6 +7462,8 @@ "opencode/@ai-sdk/cerebras/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "opencode/@ai-sdk/openai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "pkg-dir/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], "pkg-up/find-up/locate-path/p-locate": ["p-locate@3.0.0", "", { "dependencies": { "p-limit": "^2.0.0" } }, "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ=="], diff --git a/packages/core/package.json b/packages/core/package.json index 1587a7eddf49..af8cf084c0ea 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -73,7 +73,7 @@ "@ai-sdk/google-vertex": "4.0.128", "@ai-sdk/groq": "3.0.31", "@ai-sdk/mistral": "3.0.27", - "@ai-sdk/openai": "3.0.53", + "@ai-sdk/openai": "3.0.84", "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/perplexity": "3.0.26", "@ai-sdk/provider": "3.0.8", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 442460f18bef..6e9db87eb6b2 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -67,7 +67,7 @@ "@ai-sdk/google-vertex": "4.0.128", "@ai-sdk/groq": "3.0.31", "@ai-sdk/mistral": "3.0.27", - "@ai-sdk/openai": "3.0.53", + "@ai-sdk/openai": "3.0.84", "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/perplexity": "3.0.26", "@ai-sdk/provider": "3.0.8", diff --git a/packages/opencode/src/plugin/openai/codex.ts b/packages/opencode/src/plugin/openai/codex.ts index d1aa060eb63a..10186635be49 100644 --- a/packages/opencode/src/plugin/openai/codex.ts +++ b/packages/opencode/src/plugin/openai/codex.ts @@ -286,6 +286,7 @@ export async function CodexAuthPlugin(input: PluginInput, options: CodexAuthPlug return Object.fromEntries( Object.entries(provider.models) .filter(([, model]) => { + if (model.options.reasoningMode === "pro") return false if (ALLOWED_MODELS.has(model.api.id)) return true if (DISALLOWED_MODELS.has(model.api.id)) return false if (model.api.id === "gpt-5.6") return false diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 69e29d3930e3..2b1b53c231bb 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -1267,14 +1267,7 @@ export function fromModelsDevProvider(provider: ModelsDev.Provider): Info { id: ModelV2.ID.make(id), name: `${model.name} ${mode[0].toUpperCase()}${mode.slice(1)}`, cost: opts.cost ? mergeDeep(base.cost, cost(opts.cost)) : base.cost, - options: opts.provider?.body - ? Object.fromEntries( - Object.entries(opts.provider.body).map(([k, v]) => [ - k.replace(/_([a-z])/g, (_, c) => c.toUpperCase()), - v, - ]), - ) - : base.options, + options: modeOptions(base, opts.provider?.body), headers: opts.provider?.headers ?? base.headers, } } @@ -1289,6 +1282,17 @@ export function fromModelsDevProvider(provider: ModelsDev.Provider): Info { } } +function modeOptions(model: Model, body: Record | undefined) { + if (!body) return model.options + const options = Object.fromEntries( + Object.entries(body).map(([key, value]) => [key.replace(/_([a-z])/g, (_, char) => char.toUpperCase()), value]), + ) + const reasoning = body.reasoning + if (model.api.npm !== "@ai-sdk/openai" || !isRecord(reasoning) || typeof reasoning.mode !== "string") return options + const { reasoning: _, ...rest } = options + return { ...rest, reasoningMode: reasoning.mode } +} + function modelSuggestions(provider: Info | undefined, modelID: ModelV2.ID, enableExperimentalModels: boolean) { const available = provider ? Object.keys(provider.models).filter((id) => { diff --git a/packages/opencode/test/plugin/codex.test.ts b/packages/opencode/test/plugin/codex.test.ts index 0d789bb42b5a..1e1c190ccf78 100644 --- a/packages/opencode/test/plugin/codex.test.ts +++ b/packages/opencode/test/plugin/codex.test.ts @@ -149,16 +149,31 @@ describe("plugin.codex", () => { await enabled.dispose?.() }) - test("uses Codex context limits for OAuth GPT models", async () => { + test("filters unsupported modes and uses Codex context limits for OAuth GPT models", async () => { const hooks = await CodexAuthPlugin({} as never) const limit = { context: 1_050_000, input: 922_000, output: 128_000 } const provider = { - models: Object.fromEntries( - ["gpt-5.4", "gpt-5.5", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"].map((id) => [ - id, - { id, api: { id }, limit, cost: {} }, - ]), - ), + models: { + ...Object.fromEntries( + ["gpt-5.4", "gpt-5.5", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-5.7-pro"].map( + (id) => [id, { id, api: { id }, limit, cost: {}, options: {} }], + ), + ), + "gpt-5.4-pro": { + id: "gpt-5.4-pro", + api: { id: "gpt-5.4" }, + limit, + cost: {}, + options: { reasoningMode: "pro" }, + }, + "gpt-5.6-sol-high": { + id: "gpt-5.6-sol-high", + api: { id: "gpt-5.6-sol" }, + limit, + cost: {}, + options: { reasoningEffort: "high" }, + }, + }, } const models = await hooks.provider!.models!(provider as never, { auth: { type: "oauth" } } as never) @@ -168,6 +183,9 @@ describe("plugin.codex", () => { expect(models["gpt-5.6-sol"]?.limit).toEqual({ context: 500_000, input: 372_000, output: 128_000 }) expect(models["gpt-5.6-terra"]?.limit).toEqual({ context: 500_000, input: 372_000, output: 128_000 }) expect(models["gpt-5.6-luna"]?.limit).toEqual({ context: 500_000, input: 372_000, output: 128_000 }) + expect(models["gpt-5.4-pro"]).toBeUndefined() + expect(models["gpt-5.7-pro"]).toBeDefined() + expect(models["gpt-5.6-sol-high"]).toBeDefined() expect(await hooks.provider!.models!(provider as never, { auth: { type: "api" } } as never)).toBe( provider.models as never, ) diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts index b78bdae8bc63..e656890b01b0 100644 --- a/packages/opencode/test/provider/provider.test.ts +++ b/packages/opencode/test/provider/provider.test.ts @@ -1376,16 +1376,17 @@ it.instance( }, ) -test("mode cost preserves over-200k pricing from base model", () => { +test("mode options and cost are derived from the base model", () => { const provider = { id: "openai", name: "OpenAI", env: [], + npm: "@ai-sdk/openai", api: "https://api.openai.com/v1", models: { - "gpt-5.4": { - id: "gpt-5.4", - name: "GPT-5.4", + "gpt-5.6-sol": { + id: "gpt-5.6-sol", + name: "GPT-5.6 Sol", family: "gpt", release_date: "2026-03-05", attachment: true, @@ -1421,18 +1422,29 @@ test("mode cost preserves over-200k pricing from base model", () => { }, }, }, + pro: { + provider: { + body: { + reasoning: { mode: "pro" }, + service_tier: "priority", + }, + }, + }, }, }, }, }, } as unknown as ModelsDev.Provider - const model = Provider.fromModelsDevProvider(provider).models["gpt-5.4-fast"] + const model = Provider.fromModelsDevProvider(provider).models["gpt-5.6-sol-fast"] expect(model.cost.input).toEqual(5) expect(model.cost.output).toEqual(30) expect(model.cost.cache.read).toEqual(0.5) expect(model.cost.cache.write).toEqual(0) expect(model.options["serviceTier"]).toEqual("priority") + 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, diff --git a/packages/opencode/test/session/llm.test.ts b/packages/opencode/test/session/llm.test.ts index 5574d29356da..61aac13ac395 100644 --- a/packages/opencode/test/session/llm.test.ts +++ b/packages/opencode/test/session/llm.test.ts @@ -1092,7 +1092,7 @@ describe("session.llm.stream", () => { const agent = { name: "test", mode: "primary", - options: {}, + options: { reasoningMode: "pro" }, permission: [{ permission: "*", pattern: "*", action: "allow" }], temperature: 0.2, } satisfies Agent.Info @@ -1123,6 +1123,7 @@ describe("session.llm.stream", () => { expect(body.model).toBe(resolved.api.id) expect(body.stream).toBe(true) expect((body.reasoning as { effort?: string } | undefined)?.effort).toBe("high") + expect((body.reasoning as { mode?: string } | undefined)?.mode).toBe("pro") const maxTokens = body.max_output_tokens as number | undefined expect(maxTokens).toBe(undefined) // match codex cli behavior