From ded80951d81797b15eac600abc29cc03c9f6bcff Mon Sep 17 00:00:00 2001 From: Zeph Gillen Date: Sun, 12 Jul 2026 11:47:14 +0200 Subject: [PATCH] feat(openai): add GPT-5.6 Sol/Terra/Luna models MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the GPT-5.6 family (GA 2026-07-09) to the native OpenAI provider, and by extension OpenRouter. - packages/types/src/providers/openai.ts: three openAiNativeModels entries (gpt-5.6-sol/terra/luna) templated on gpt-5.5 — 1.05M context, 128K output, the new `max` reasoning-effort level, verbosity, and pricing $5/$30, $2.50/$15, $1/$6 (in/out) with 90%-off cache reads. Promote gpt-5.6-sol to openAiNativeDefaultModelId. - packages/types/src/providers/index.ts: getProviderDefaultModelId( "openai-native") now returns openAiNativeDefaultModelId instead of a stale hardcoded "gpt-4o", so the loading-time fallback also honors Sol. OpenRouter needs no code change: parseOpenRouterModel already mirrors the static effort array for any openai/* id, so openai/gpt-5.6-* surfaces `max` automatically and caching auto-detects from API pricing. Bedrock deferred: GPT-5.6 is not GA on Bedrock and AWS has not published model IDs. Long-context surcharge, flex/priority service tiers, and the new 1.25x cache-write price are omitted pending confirmation against OpenAI's official pricing page. Tests: default-model assertion updated to gpt-5.6-sol; new getModel() and `"effort":"max"` request-body passthrough tests (openai-native.spec.ts); new openai/gpt-5.6-sol effort-mirror case (openrouter fetcher spec). check-types passes and the pre-existing 22-failure reasoning/model-params baseline is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/types/src/providers/index.ts | 3 +- packages/types/src/providers/openai.ts | 69 ++++++++++++++++++- .../providers/__tests__/openai-native.spec.ts | 60 +++++++++++++++- .../fetchers/__tests__/openrouter.spec.ts | 21 ++++++ 4 files changed, 150 insertions(+), 3 deletions(-) diff --git a/packages/types/src/providers/index.ts b/packages/types/src/providers/index.ts index bcb4a76d5d..c226efdcf6 100644 --- a/packages/types/src/providers/index.ts +++ b/packages/types/src/providers/index.ts @@ -40,6 +40,7 @@ import { litellmDefaultModelId } from "./lite-llm.js" import { mistralDefaultModelId } from "./mistral.js" import { moonshotDefaultModelId } from "./moonshot.js" import { openAiCodexDefaultModelId } from "./openai-codex.js" +import { openAiNativeDefaultModelId } from "./openai.js" import { openRouterDefaultModelId } from "./openrouter.js" import { poeDefaultModelId } from "./poe.js" import { qwenCodeDefaultModelId } from "./qwen-code.js" @@ -96,7 +97,7 @@ export function getProviderDefaultModelId( case "zai": return options?.isChina ? mainlandZAiDefaultModelId : internationalZAiDefaultModelId case "openai-native": - return "gpt-4o" // Based on openai-native patterns + return openAiNativeDefaultModelId case "openai-codex": return openAiCodexDefaultModelId case "mistral": diff --git a/packages/types/src/providers/openai.ts b/packages/types/src/providers/openai.ts index 875e1759a3..600a94cd2e 100644 --- a/packages/types/src/providers/openai.ts +++ b/packages/types/src/providers/openai.ts @@ -3,9 +3,76 @@ import type { ModelInfo } from "../model.js" // https://openai.com/api/pricing/ export type OpenAiNativeModelId = keyof typeof openAiNativeModels -export const openAiNativeDefaultModelId: OpenAiNativeModelId = "gpt-5.1-codex-max" +export const openAiNativeDefaultModelId: OpenAiNativeModelId = "gpt-5.6-sol" export const openAiNativeModels = { + "gpt-5.6-sol": { + maxTokens: 128000, + contextWindow: 1_050_000, + includedTools: ["apply_patch"], + excludedTools: ["apply_diff", "write_to_file"], + supportsImages: true, + supportsPromptCache: true, + supportsReasoningEffort: ["none", "low", "medium", "high", "xhigh", "max"], + reasoningEffort: "high", + inputPrice: 5.0, + outputPrice: 30.0, + cacheReadsPrice: 0.5, + longContextPricing: { + thresholdTokens: 272_000, + inputPriceMultiplier: 2, + outputPriceMultiplier: 1.5, + appliesToServiceTiers: ["default", "flex"], + }, + supportsVerbosity: true, + supportsTemperature: false, + description: + "GPT-5.6 Sol: OpenAI's flagship model for frontier reasoning, complex coding, and multi-step agentic tasks", + }, + "gpt-5.6-terra": { + maxTokens: 128000, + contextWindow: 1_050_000, + includedTools: ["apply_patch"], + excludedTools: ["apply_diff", "write_to_file"], + supportsImages: true, + supportsPromptCache: true, + supportsReasoningEffort: ["none", "low", "medium", "high", "xhigh", "max"], + reasoningEffort: "medium", + inputPrice: 2.5, + outputPrice: 15.0, + cacheReadsPrice: 0.25, + longContextPricing: { + thresholdTokens: 272_000, + inputPriceMultiplier: 2, + outputPriceMultiplier: 1.5, + appliesToServiceTiers: ["default", "flex"], + }, + supportsVerbosity: true, + supportsTemperature: false, + description: "GPT-5.6 Terra: Balanced model for everyday coding, reasoning, and agentic tasks", + }, + "gpt-5.6-luna": { + maxTokens: 128000, + contextWindow: 1_050_000, + includedTools: ["apply_patch"], + excludedTools: ["apply_diff", "write_to_file"], + supportsImages: true, + supportsPromptCache: true, + supportsReasoningEffort: ["none", "low", "medium", "high", "xhigh", "max"], + reasoningEffort: "low", + inputPrice: 1.0, + outputPrice: 6.0, + cacheReadsPrice: 0.1, + longContextPricing: { + thresholdTokens: 272_000, + inputPriceMultiplier: 2, + outputPriceMultiplier: 1.5, + appliesToServiceTiers: ["default", "flex"], + }, + supportsVerbosity: true, + supportsTemperature: false, + description: "GPT-5.6 Luna: Fast, cost-efficient model optimized for speed and everyday use", + }, "gpt-5.1-codex-max": { maxTokens: 128000, contextWindow: 400000, diff --git a/src/api/providers/__tests__/openai-native.spec.ts b/src/api/providers/__tests__/openai-native.spec.ts index 2a3ec0afb2..397ca993cf 100644 --- a/src/api/providers/__tests__/openai-native.spec.ts +++ b/src/api/providers/__tests__/openai-native.spec.ts @@ -284,6 +284,24 @@ describe("OpenAiNativeHandler", () => { expect(modelInfo.info.reasoningEffort).toBe("medium") }) + it("should return GPT-5.6 Sol model info when selected", () => { + const solHandler = new OpenAiNativeHandler({ + ...mockOptions, + apiModelId: "gpt-5.6-sol", + }) + + const modelInfo = solHandler.getModel() + expect(modelInfo.id).toBe("gpt-5.6-sol") + expect(modelInfo.info.maxTokens).toBe(128000) + expect(modelInfo.info.contextWindow).toBe(1_050_000) + expect(modelInfo.info.supportsVerbosity).toBe(true) + expect(modelInfo.info.supportsReasoningEffort).toEqual(["none", "low", "medium", "high", "xhigh", "max"]) + expect(modelInfo.info.reasoningEffort).toBe("high") + expect(modelInfo.info.inputPrice).toBe(5.0) + expect(modelInfo.info.outputPrice).toBe(30.0) + expect(modelInfo.info.cacheReadsPrice).toBe(0.5) + }) + it("should return GPT-5.4 model info when selected", () => { const gpt54Handler = new OpenAiNativeHandler({ ...mockOptions, @@ -356,7 +374,7 @@ describe("OpenAiNativeHandler", () => { openAiNativeApiKey: "test-api-key", }) const modelInfo = handlerWithoutModel.getModel() - expect(modelInfo.id).toBe("gpt-5.1-codex-max") // Default model + expect(modelInfo.id).toBe("gpt-5.6-sol") // Default model expect(modelInfo.info).toBeDefined() }) }) @@ -805,6 +823,46 @@ describe("OpenAiNativeHandler", () => { ) }) + it("should support max reasoning effort for GPT-5.6 Sol", async () => { + // Mock fetch for Responses API + const mockFetch = vitest.fn().mockResolvedValue({ + ok: true, + body: new ReadableStream({ + start(controller) { + controller.enqueue( + new TextEncoder().encode( + 'data: {"type":"response.output_item.added","item":{"type":"text","text":"Max effort"}}\n\n', + ), + ) + controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n")) + controller.close() + }, + }), + }) + global.fetch = mockFetch as any + + // Mock SDK to fail + mockResponsesCreate.mockRejectedValue(new Error("SDK not available")) + + handler = new OpenAiNativeHandler({ + ...mockOptions, + apiModelId: "gpt-5.6-sol", + reasoningEffort: "max", + }) + + const stream = handler.createMessage(systemPrompt, messages) + for await (const _chunk of stream) { + // drain + } + + expect(mockFetch).toHaveBeenCalledWith( + "https://api.openai.com/v1/responses", + expect.objectContaining({ + body: expect.stringContaining('"effort":"max"'), + }), + ) + }) + it("should omit reasoning when selection is 'disable'", async () => { // Mock fetch for Responses API const mockFetch = vitest.fn().mockResolvedValue({ diff --git a/src/api/providers/fetchers/__tests__/openrouter.spec.ts b/src/api/providers/fetchers/__tests__/openrouter.spec.ts index 268f83b92e..6f2349120c 100644 --- a/src/api/providers/fetchers/__tests__/openrouter.spec.ts +++ b/src/api/providers/fetchers/__tests__/openrouter.spec.ts @@ -471,6 +471,27 @@ describe("OpenRouter API", () => { expect(result.supportsReasoningEffort).toEqual(["none", "low", "medium", "high", "xhigh"]) }) + it("mirrors reasoning-effort capability array (incl. max) for openai/gpt-5.6-* ids", () => { + const mockModel = { + name: "GPT-5.6 Sol", + description: "Test model", + context_length: 1_050_000, + max_completion_tokens: 128000, + pricing: { prompt: "0.000005", completion: "0.00003", input_cache_read: "0.0000005" }, + } + + const result = parseOpenRouterModel({ + id: "openai/gpt-5.6-sol", + model: mockModel, + inputModality: ["text", "image"], + outputModality: ["text"], + maxTokens: 128000, + supportedParameters: ["reasoning", "max_tokens", "tools"], + }) + + expect(result.supportsReasoningEffort).toEqual(["none", "low", "medium", "high", "xhigh", "max"]) + }) + it("leaves supportsReasoningEffort as boolean for openai ids not in static defs", () => { const mockModel = { name: "GPT-5.5 Future",