diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 4585be1d2..345c0a7df 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -4,6 +4,7 @@ ### Added +- Added GPT-6 Sol and GPT-6 Luna for OpenAI API keys and OpenAI Codex subscriptions, with full reasoning-effort, prompt-caching, tool-search, long-context pricing, and official cost metadata. - Added Claude Opus 5.5 to the built-in Anthropic model catalog with adaptive thinking, 1M context, and official pricing metadata. - Added Grok 4.7 to the built-in xAI model catalog with long-context pricing metadata. diff --git a/packages/ai/scripts/generate-models.ts b/packages/ai/scripts/generate-models.ts index a611e6b0f..a65e91dfa 100644 --- a/packages/ai/scripts/generate-models.ts +++ b/packages/ai/scripts/generate-models.ts @@ -371,6 +371,8 @@ const OPENAI_TOOL_SEARCH_MODEL_IDS = new Set([ "gpt-5.6-terra", "gpt-5.6-luna", "gpt-6-astra", + "gpt-6-sol", + "gpt-6-luna", ]); const OPENAI_ADDITIONAL_TOOLS_MODEL_IDS = OPENAI_TOOL_SEARCH_MODEL_IDS; const OPENAI_MID_CONVO_SYSTEM_MESSAGE_MODEL_IDS = OPENAI_TOOL_SEARCH_MODEL_IDS; @@ -379,6 +381,8 @@ const OPENAI_CODEX_ADDITIONAL_TOOLS_MODEL_IDS = new Set([ "gpt-5.6-terra", "gpt-5.6-luna", "gpt-6-astra", + "gpt-6-sol", + "gpt-6-luna", ]); const OPENAI_LONG_CONTEXT_INPUT_THRESHOLD = 272000; const OPENAI_SHORT_CONTEXT_CAPPED_MODEL_IDS = new Set([ @@ -388,6 +392,8 @@ const OPENAI_SHORT_CONTEXT_CAPPED_MODEL_IDS = new Set([ "gpt-5.6-terra", "gpt-5.6-luna", "gpt-6-astra", + "gpt-6-sol", + "gpt-6-luna", ]); const OPENAI_LONG_CONTEXT_PRICING_MODEL_IDS = new Set([ "gpt-5.4", @@ -398,6 +404,8 @@ const OPENAI_LONG_CONTEXT_PRICING_MODEL_IDS = new Set([ "gpt-5.6-terra", "gpt-5.6-luna", "gpt-6-astra", + "gpt-6-sol", + "gpt-6-luna", ]); // Keep the generated default no less restrictive than coding-agent's historical @@ -425,12 +433,16 @@ function withOpenAiLongContextPricing(cost: Model["cost"]): Model["cos }; } -// OpenAI reduced GPT-5.6 Terra and Luna prices on 2026-07-30. Keep these -// authoritative values until models.dev and passthrough catalogs catch up. +// Keep current OpenAI prices authoritative until models.dev and passthrough +// catalogs catch up. // https://developers.openai.com/api/docs/pricing -const OPENAI_GPT_56_STANDARD_COSTS: Record = { +const OPENAI_STANDARD_COSTS: Record = { "gpt-5.6-luna": { input: 0.2, output: 1.2, cacheRead: 0.02, cacheWrite: 0.25 }, + "gpt-5.6-sol": { input: 4, output: 20, cacheRead: 0.4, cacheWrite: 5 }, "gpt-5.6-terra": { input: 2, output: 12, cacheRead: 0.2, cacheWrite: 2.5 }, + "gpt-6-astra": { input: 10, output: 50, cacheRead: 1, cacheWrite: 12.5 }, + "gpt-6-luna": { input: 0.1, output: 0.5, cacheRead: 0.01, cacheWrite: 0.125 }, + "gpt-6-sol": { input: 2, output: 10, cacheRead: 0.2, cacheWrite: 2.5 }, }; const OPENAI_RESPONSES_NONE_REASONING_MODELS = new Set([ @@ -444,6 +456,8 @@ const OPENAI_RESPONSES_NONE_REASONING_MODELS = new Set([ "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", + "gpt-6-sol", + "gpt-6-luna", ]); const XAI_BUILTIN_EXCLUDED_MODEL_IDS = new Set([ "grok-3", @@ -557,13 +571,13 @@ function supportsOpenAiXhigh(modelId: string): boolean { modelId.includes("gpt-5.4") || modelId.includes("gpt-5.5") || modelId.includes("gpt-5.6") || - modelId.includes("gpt-6-astra") + modelId.includes("gpt-6") ); } function supportsOpenAiMax(model: Model): boolean { return ( - (model.id.includes("gpt-5.6") || model.id.includes("gpt-6-astra")) && + (model.id.includes("gpt-5.6") || model.id.includes("gpt-6")) && (model.api === "openai-responses" || model.api === "azure-openai-responses" || model.api === "openai-codex-responses" || @@ -1010,13 +1024,13 @@ function applyThinkingLevelMetadata(model: Model): void { mergeThinkingLevelMap(model, { off: null }); } if ( - model.id === "gpt-6-astra" && + (model.id === "gpt-6-astra" || model.id === "gpt-6-sol" || model.id === "gpt-6-luna") && (model.api === "openai-responses" || model.api === "azure-openai-responses" || model.api === "openai-codex-responses") ) { mergeThinkingLevelMap(model, { - off: null, + off: model.id === "gpt-6-astra" ? null : "none", minimal: null, low: "low", medium: "medium", @@ -2724,12 +2738,12 @@ async function generateModels() { candidate.maxTokens = 128000; } if (candidate.provider === "openai" && OPENAI_LONG_CONTEXT_PRICING_MODEL_IDS.has(candidate.id)) { - const standardCost = OPENAI_GPT_56_STANDARD_COSTS[candidate.id]; + const standardCost = OPENAI_STANDARD_COSTS[candidate.id]; candidate.cost = withOpenAiLongContextPricing(standardCost ?? candidate.cost); } // Cloudflare AI Gateway passes OpenAI usage through at OpenAI list prices. if (candidate.provider === "cloudflare-ai-gateway") { - const standardCost = OPENAI_GPT_56_STANDARD_COSTS[candidate.id]; + const standardCost = OPENAI_STANDARD_COSTS[candidate.id]; if (standardCost) candidate.cost = withOpenAiLongContextPricing(standardCost); } // models.dev reports gpt-5-pro output as 272000 (a duplicate of the input sub-limit); @@ -2775,7 +2789,31 @@ async function generateModels() { provider: "openai", reasoning: true, input: ["text", "image"], - cost: withOpenAiLongContextPricing({ input: 10, output: 50, cacheRead: 1, cacheWrite: 12.5 }), + cost: withOpenAiLongContextPricing(OPENAI_STANDARD_COSTS["gpt-6-astra"]), + contextWindow: OPENAI_LONG_CONTEXT_INPUT_THRESHOLD, + maxTokens: 128000, + }, + { + id: "gpt-6-sol", + name: "GPT-6 Sol", + api: "openai-responses", + baseUrl: "https://api.openai.com/v1", + provider: "openai", + reasoning: true, + input: ["text", "image"], + cost: withOpenAiLongContextPricing(OPENAI_STANDARD_COSTS["gpt-6-sol"]), + contextWindow: OPENAI_LONG_CONTEXT_INPUT_THRESHOLD, + maxTokens: 128000, + }, + { + id: "gpt-6-luna", + name: "GPT-6 Luna", + api: "openai-responses", + baseUrl: "https://api.openai.com/v1", + provider: "openai", + reasoning: true, + input: ["text", "image"], + cost: withOpenAiLongContextPricing(OPENAI_STANDARD_COSTS["gpt-6-luna"]), contextWindow: OPENAI_LONG_CONTEXT_INPUT_THRESHOLD, maxTokens: 128000, }, @@ -2787,7 +2825,7 @@ async function generateModels() { provider: "openai", reasoning: true, input: ["text", "image"], - cost: withOpenAiLongContextPricing({ input: 5, output: 30, cacheRead: 0.5, cacheWrite: 6.25 }), + cost: withOpenAiLongContextPricing(OPENAI_STANDARD_COSTS["gpt-5.6-sol"]), contextWindow: OPENAI_LONG_CONTEXT_INPUT_THRESHOLD, maxTokens: 128000, }, @@ -2799,7 +2837,7 @@ async function generateModels() { provider: "openai", reasoning: true, input: ["text", "image"], - cost: withOpenAiLongContextPricing(OPENAI_GPT_56_STANDARD_COSTS["gpt-5.6-terra"]), + cost: withOpenAiLongContextPricing(OPENAI_STANDARD_COSTS["gpt-5.6-terra"]), contextWindow: OPENAI_LONG_CONTEXT_INPUT_THRESHOLD, maxTokens: 128000, }, @@ -2811,7 +2849,7 @@ async function generateModels() { provider: "openai", reasoning: true, input: ["text", "image"], - cost: withOpenAiLongContextPricing(OPENAI_GPT_56_STANDARD_COSTS["gpt-5.6-luna"]), + cost: withOpenAiLongContextPricing(OPENAI_STANDARD_COSTS["gpt-5.6-luna"]), contextWindow: OPENAI_LONG_CONTEXT_INPUT_THRESHOLD, maxTokens: 128000, }, @@ -2969,7 +3007,7 @@ async function generateModels() { // OpenAI Codex (ChatGPT OAuth) models // NOTE: These are not fetched from models.dev; we keep a small, explicit list to avoid aliases. - // Older model limits are based on observed server behavior; GPT-5.6 and GPT-6 Astra use Codex's 272k default catalog limit. + // Older model limits are based on observed server behavior; GPT-5.6 and GPT-6 use Codex's 272k default catalog limit. const CODEX_BASE_URL = "https://chatgpt.com/backend-api"; const CODEX_CONTEXT = 272000; const CODEX_GPT_56_CONTEXT = 272000; @@ -2984,7 +3022,31 @@ async function generateModels() { baseUrl: CODEX_BASE_URL, reasoning: true, input: ["text", "image"], - cost: withOpenAiLongContextPricing({ input: 10, output: 50, cacheRead: 1, cacheWrite: 12.5 }), + cost: withOpenAiLongContextPricing(OPENAI_STANDARD_COSTS["gpt-6-astra"]), + contextWindow: CODEX_CONTEXT, + maxTokens: CODEX_MAX_TOKENS, + }, + { + id: "gpt-6-sol", + name: "GPT-6 Sol", + api: "openai-codex-responses", + provider: "openai-codex", + baseUrl: CODEX_BASE_URL, + reasoning: true, + input: ["text", "image"], + cost: withOpenAiLongContextPricing(OPENAI_STANDARD_COSTS["gpt-6-sol"]), + contextWindow: CODEX_CONTEXT, + maxTokens: CODEX_MAX_TOKENS, + }, + { + id: "gpt-6-luna", + name: "GPT-6 Luna", + api: "openai-codex-responses", + provider: "openai-codex", + baseUrl: CODEX_BASE_URL, + reasoning: true, + input: ["text", "image"], + cost: withOpenAiLongContextPricing(OPENAI_STANDARD_COSTS["gpt-6-luna"]), contextWindow: CODEX_CONTEXT, maxTokens: CODEX_MAX_TOKENS, }, @@ -3020,7 +3082,7 @@ async function generateModels() { baseUrl: CODEX_BASE_URL, reasoning: true, input: ["text", "image"], - cost: withOpenAiLongContextPricing(OPENAI_GPT_56_STANDARD_COSTS["gpt-5.6-luna"]), + cost: withOpenAiLongContextPricing(OPENAI_STANDARD_COSTS["gpt-5.6-luna"]), contextWindow: CODEX_GPT_56_CONTEXT, maxTokens: CODEX_MAX_TOKENS, }, @@ -3032,7 +3094,7 @@ async function generateModels() { baseUrl: CODEX_BASE_URL, reasoning: true, input: ["text", "image"], - cost: withOpenAiLongContextPricing({ input: 5, output: 30, cacheRead: 0.5, cacheWrite: 6.25 }), + cost: withOpenAiLongContextPricing(OPENAI_STANDARD_COSTS["gpt-5.6-sol"]), contextWindow: CODEX_GPT_56_CONTEXT, maxTokens: CODEX_MAX_TOKENS, }, @@ -3044,7 +3106,7 @@ async function generateModels() { baseUrl: CODEX_BASE_URL, reasoning: true, input: ["text", "image"], - cost: withOpenAiLongContextPricing(OPENAI_GPT_56_STANDARD_COSTS["gpt-5.6-terra"]), + cost: withOpenAiLongContextPricing(OPENAI_STANDARD_COSTS["gpt-5.6-terra"]), contextWindow: CODEX_GPT_56_CONTEXT, maxTokens: CODEX_MAX_TOKENS, }, diff --git a/packages/ai/test/cache-retention.test.ts b/packages/ai/test/cache-retention.test.ts index 7329247b1..585e0da3f 100644 --- a/packages/ai/test/cache-retention.test.ts +++ b/packages/ai/test/cache-retention.test.ts @@ -240,7 +240,7 @@ describe("Cache Retention (PI_CACHE_RETENTION)", () => { }); describe("OpenAI Responses Provider", () => { - it.each(["gpt-5.6-luna", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-6-astra"] as const)( + it.each(["gpt-5.6-luna", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-6-astra", "gpt-6-luna", "gpt-6-sol"] as const)( "does not enable cache warming from the documented TTL alone for %s", (modelId) => { expect(getModel("openai", modelId).promptCache).toBeUndefined(); @@ -408,6 +408,8 @@ describe("Cache Retention (PI_CACHE_RETENTION)", () => { it.each([ ["gpt-4o-mini", "24h", undefined], ["gpt-6-astra", undefined, { ttl: "30m" }], + ["gpt-6-sol", undefined, { ttl: "30m" }], + ["gpt-6-luna", undefined, { ttl: "30m" }], ] as const)("should use the supported long cache field for %s", async (modelId, retention, cacheOptions) => { const model = getModel("openai", modelId); let capturedPayload: OpenAIResponsesCachePayload | undefined; diff --git a/packages/ai/test/max-thinking.test.ts b/packages/ai/test/max-thinking.test.ts index f91950901..efbc83efc 100644 --- a/packages/ai/test/max-thinking.test.ts +++ b/packages/ai/test/max-thinking.test.ts @@ -30,7 +30,7 @@ describe("max thinking level", () => { expect(clampThinkingLevel(model, "max")).toBe("high"); }); - it.each(["gpt-5.6-luna", "gpt-5.6-sol", "gpt-5.6-terra"] as const)( + it.each(["gpt-5.6-luna", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-6-luna", "gpt-6-sol"] as const)( "exposes xhigh and max for openai-codex/%s", (modelId) => { const model = getModel("openai-codex", modelId); @@ -67,23 +67,26 @@ describe("max thinking level", () => { expect(clampThinkingLevel(model, "xhigh")).toBe("max"); }); - it.each(["gpt-5.6-sol", "gpt-6-astra"] as const)("sends max to the Codex Responses API for %s", async (modelId) => { - const model = getModel("openai-codex", modelId)!; - const context = normalizeContext({ - systemPrompt: "You are a helpful assistant.", - messages: [{ role: "user", content: "Hello", timestamp: Date.now() }], - }); - let payload: unknown; + it.each(["gpt-5.6-sol", "gpt-6-astra", "gpt-6-sol", "gpt-6-luna"] as const)( + "sends max to the Codex Responses API for %s", + async (modelId) => { + const model = getModel("openai-codex", modelId)!; + const context = normalizeContext({ + systemPrompt: "You are a helpful assistant.", + messages: [{ role: "user", content: "Hello", timestamp: Date.now() }], + }); + let payload: unknown; - await streamSimpleOpenAICodexResponses(model, context, { - apiKey: mockToken(), - reasoning: "max", - onPayload: (request) => { - payload = request; - throw new Error("payload captured"); - }, - }).result(); + await streamSimpleOpenAICodexResponses(model, context, { + apiKey: mockToken(), + reasoning: "max", + onPayload: (request) => { + payload = request; + throw new Error("payload captured"); + }, + }).result(); - expect(payload).toMatchObject({ reasoning: { effort: "max", summary: "auto" } }); - }); + expect(payload).toMatchObject({ reasoning: { effort: "max", summary: "auto" } }); + }, + ); }); diff --git a/packages/ai/test/openai-responses-compat.test.ts b/packages/ai/test/openai-responses-compat.test.ts index a8c53450c..981ce604f 100644 --- a/packages/ai/test/openai-responses-compat.test.ts +++ b/packages/ai/test/openai-responses-compat.test.ts @@ -216,6 +216,8 @@ describe("openai-responses provider defaults", () => { "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", + "gpt-6-sol", + "gpt-6-luna", ] as const)("sends none reasoning effort for OpenAI %s when no reasoning is requested", async (modelId) => { const model = getModel("openai", modelId); let capturedPayload: unknown; diff --git a/packages/ai/test/supports-xhigh.test.ts b/packages/ai/test/supports-xhigh.test.ts index bb762e5dc..72d7d8fa4 100644 --- a/packages/ai/test/supports-xhigh.test.ts +++ b/packages/ai/test/supports-xhigh.test.ts @@ -68,16 +68,21 @@ describe("getSupportedThinkingLevels", () => { expect(getSupportedThinkingLevels(model!)).not.toContain("max"); }); - it.each(["gpt-5.5", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-6-astra"] as const)( - "includes xhigh for openai-codex %s models", - (modelId) => { - const model = getModel("openai-codex", modelId); - expect(model).toBeDefined(); - expect(getSupportedThinkingLevels(model!)).toContain("xhigh"); - }, - ); + it.each([ + "gpt-5.5", + "gpt-5.6-sol", + "gpt-5.6-terra", + "gpt-5.6-luna", + "gpt-6-astra", + "gpt-6-sol", + "gpt-6-luna", + ] as const)("includes xhigh for openai-codex %s models", (modelId) => { + const model = getModel("openai-codex", modelId); + expect(model).toBeDefined(); + expect(getSupportedThinkingLevels(model!)).toContain("xhigh"); + }); - it.each(["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"] as const)( + it.each(["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-6-sol", "gpt-6-luna"] as const)( "includes xhigh and max for OpenAI %s models", (modelId) => { const model = getModel("openai", modelId); @@ -86,6 +91,38 @@ describe("getSupportedThinkingLevels", () => { }, ); + it.each([ + ["gpt-6-sol", { input: 2, output: 10, cacheRead: 0.2, cacheWrite: 2.5 }], + ["gpt-6-luna", { input: 0.1, output: 0.5, cacheRead: 0.01, cacheWrite: 0.125 }], + ] as const)("includes official metadata for OpenAI and Codex %s", (modelId, cost) => { + for (const provider of ["openai", "openai-codex"] as const) { + const model = getModel(provider, modelId); + expect(model).toMatchObject({ + input: ["text", "image"], + cost: { + ...cost, + tiers: [ + { + inputTokensAbove: 272000, + input: cost.input * 2, + output: cost.output * 1.5, + cacheRead: cost.cacheRead * 2, + cacheWrite: cost.cacheWrite * 2, + }, + ], + }, + contextWindow: 272000, + maxTokens: 128000, + compat: { + supportsAdditionalTools: true, + supportsMidConvoSystemMessages: true, + supportsOpenAIGrammarTools: true, + supportsToolSearch: true, + }, + }); + } + }); + it("includes only medium/high/xhigh for OpenAI GPT-5.5 Pro", () => { const model = getModel("openai", "gpt-5.5-pro"); expect(model).toBeDefined(); diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index ad5f8bb1a..21c948fbc 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -4,6 +4,7 @@ ### Added +- Added inherited GPT-6 Sol and GPT-6 Luna support for OpenAI API keys and OpenAI Codex subscriptions. - Added inherited Claude Opus 5.5 support for Anthropic with adaptive thinking and a 1M context window. ### Changed