diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index cd29e40822..d9bb0316ef 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -509,23 +509,23 @@ const OPENAI_NONE_EFFORT_RELEASE_DATE = "2025-11-13" // OpenAI rolled out the `xhigh` reasoning_effort tier on this date. Same reasoning. const OPENAI_XHIGH_EFFORT_RELEASE_DATE = "2025-12-04" -// Matches members of the gpt-5 family across the id formats we encounter: -// "gpt-5", "gpt-5-nano", "gpt-5.4", "openai/gpt-5.4-codex". -// Anchored to start-of-string or "/" so it doesn't false-match "gpt-50" or "gpt-5o". -const GPT5_FAMILY_RE = /(?:^|\/)gpt-5(?:[.-]|$)/ +// Matches the first generation GPT-5 ids that still accept `minimal`. +const GPT5_INITIAL_RE = /(?:^|\/)gpt-5(?:-(?:mini|nano))?$/ // Computes the reasoning_effort tiers an OpenAI (or OpenAI-compatible upstream // routed through it, e.g. cf-ai-gateway) model exposes. Returns null for models // with no tunable effort knob (gpt-5-pro). Effort order: weakest to strongest. function openaiReasoningEfforts(apiId: string, releaseDate: string): string[] | null { const id = apiId.toLowerCase() - if (id === "gpt-5-pro" || id === "openai/gpt-5-pro") return null + if (id.endsWith("chat-latest")) return ["medium"] + if (id.endsWith("-pro")) return id.includes("5.1") ? null : ["medium", "high", "xhigh"] if (id.includes("codex")) { - if (id.includes("5.2") || id.includes("5.3")) return [...WIDELY_SUPPORTED_EFFORTS, "xhigh"] - return [...WIDELY_SUPPORTED_EFFORTS] + const efforts = ["minimal", ...WIDELY_SUPPORTED_EFFORTS] + if (!["5-codex", "5.1-codex", "5.1-codex-mini"].some((v) => id.endsWith(v))) efforts.push("xhigh") + return efforts } const efforts = [...WIDELY_SUPPORTED_EFFORTS] - if (GPT5_FAMILY_RE.test(id)) efforts.unshift("minimal") + if (GPT5_INITIAL_RE.test(id)) efforts.unshift("minimal") if (releaseDate >= OPENAI_NONE_EFFORT_RELEASE_DATE) efforts.unshift("none") if (releaseDate >= OPENAI_XHIGH_EFFORT_RELEASE_DATE) efforts.push("xhigh") return efforts diff --git a/packages/opencode/test/provider/transform.test.ts b/packages/opencode/test/provider/transform.test.ts index c7a321d571..3dec2dff34 100644 --- a/packages/opencode/test/provider/transform.test.ts +++ b/packages/opencode/test/provider/transform.test.ts @@ -310,6 +310,65 @@ describe("ProviderTransform.options - gpt-5 textVerbosity", () => { }) }) +describe("ProviderTransform.variants - openai gpt-5 reasoning efforts", () => { + const createModel = (apiId: string, releaseDate = "2025-12-04") => + ({ + id: `openai/${apiId}`, + providerID: "openai", + api: { + id: apiId, + url: "https://api.openai.com", + npm: "@ai-sdk/openai", + }, + name: apiId, + release_date: releaseDate, + capabilities: { + temperature: true, + reasoning: true, + attachment: true, + toolcall: true, + input: { text: true, audio: false, image: true, video: false, pdf: false }, + output: { text: true, audio: false, image: false, video: false, pdf: false }, + interleaved: false, + }, + cost: { input: 0.03, output: 0.06, cache: { read: 0.001, write: 0.002 } }, + limit: { context: 128000, output: 4096 }, + status: "active", + options: {}, + headers: {}, + }) as any + + test.each([ + ["gpt-5", ["none", "minimal", "low", "medium", "high", "xhigh"]], + ["gpt-5-mini", ["none", "minimal", "low", "medium", "high", "xhigh"]], + ["gpt-5-nano", ["none", "minimal", "low", "medium", "high", "xhigh"]], + ["gpt-5.1", ["none", "low", "medium", "high", "xhigh"]], + ["gpt-5.2", ["none", "low", "medium", "high", "xhigh"]], + ["gpt-5.4", ["none", "low", "medium", "high", "xhigh"]], + ["gpt-5.4-fast", ["none", "low", "medium", "high", "xhigh"]], + ["gpt-5.4-mini", ["none", "low", "medium", "high", "xhigh"]], + ["gpt-5.4-mini-fast", ["none", "low", "medium", "high", "xhigh"]], + ["gpt-5.4-nano", ["none", "low", "medium", "high", "xhigh"]], + ["gpt-5.5", ["none", "low", "medium", "high", "xhigh"]], + ["gpt-5.5-fast", ["none", "low", "medium", "high", "xhigh"]], + ["gpt-5.1-chat-latest", ["medium"]], + ["gpt-5.2-chat-latest", ["medium"]], + ["gpt-5.3-chat-latest", ["medium"]], + ["gpt-5.2-pro", ["medium", "high", "xhigh"]], + ["gpt-5.4-pro", ["medium", "high", "xhigh"]], + ["gpt-5.5-pro", ["medium", "high", "xhigh"]], + ["gpt-5-codex", ["minimal", "low", "medium", "high"]], + ["gpt-5.1-codex", ["minimal", "low", "medium", "high"]], + ["gpt-5.1-codex-mini", ["minimal", "low", "medium", "high"]], + ["gpt-5.1-codex-max", ["minimal", "low", "medium", "high", "xhigh"]], + ["gpt-5.2-codex", ["minimal", "low", "medium", "high", "xhigh"]], + ["gpt-5.3-codex", ["minimal", "low", "medium", "high", "xhigh"]], + ["gpt-5.3-codex-spark", ["minimal", "low", "medium", "high", "xhigh"]], + ])("%s exposes the expected effort variants", (apiId, efforts) => { + expect(Object.keys(ProviderTransform.variants(createModel(apiId)))).toEqual(efforts) + }) +}) + describe("ProviderTransform.options - gateway", () => { const sessionID = "test-session-123" @@ -2932,7 +2991,7 @@ describe("ProviderTransform.variants", () => { }) describe("@ai-sdk/openai", () => { - test("gpt-5-pro returns empty object", () => { + test("gpt-5-pro returns pro efforts", () => { const model = createMockModel({ id: "gpt-5-pro", providerID: "openai", @@ -2943,7 +3002,7 @@ describe("ProviderTransform.variants", () => { }, }) const result = ProviderTransform.variants(model) - expect(result).toEqual({}) + expect(Object.keys(result)).toEqual(["medium", "high", "xhigh"]) }) test("standard openai models return custom efforts with reasoningSummary", () => { @@ -2993,10 +3052,10 @@ describe("ProviderTransform.variants", () => { release_date: "2025-12-05", }) const result = ProviderTransform.variants(model) - expect(Object.keys(result)).toEqual(["none", "minimal", "low", "medium", "high", "xhigh"]) + expect(Object.keys(result)).toEqual(["none", "low", "medium", "high", "xhigh"]) }) - test("dotted gpt-5.x ids include 'minimal' (regression: matcher used to miss gpt-5.4)", () => { + test("dotted gpt-5.x ids include 'xhigh' without minimal", () => { const model = createMockModel({ id: "gpt-5.4", providerID: "openai", @@ -3008,7 +3067,7 @@ describe("ProviderTransform.variants", () => { release_date: "2026-03-05", }) const result = ProviderTransform.variants(model) - expect(Object.keys(result)).toEqual(["none", "minimal", "low", "medium", "high", "xhigh"]) + expect(Object.keys(result)).toEqual(["none", "low", "medium", "high", "xhigh"]) }) test("gpt-50 (lookalike) does not get gpt-5 family treatment", () => { @@ -3490,13 +3549,13 @@ describe("ProviderTransform.variants", () => { const result = ProviderTransform.variants(cfModel("openai/gpt-5.4", "2026-03-05")) expect(result.xhigh).toEqual({ reasoningEffort: "xhigh" }) expect(result.high).toEqual({ reasoningEffort: "high" }) - expect(Object.keys(result)).toContain("minimal") + expect(Object.keys(result)).not.toContain("minimal") }) test("openai gpt-5.2-codex includes xhigh", () => { const result = ProviderTransform.variants(cfModel("openai/gpt-5.2-codex", "2025-12-11")) expect(result.xhigh).toEqual({ reasoningEffort: "xhigh" }) - expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh"]) + expect(Object.keys(result)).toEqual(["minimal", "low", "medium", "high", "xhigh"]) }) test("openai gpt-4o (no reasoning) returns empty", () => {