From 81b1785dcbecea14cc284be4034ed42a5a2de70c Mon Sep 17 00:00:00 2001 From: gubin-dev Date: Wed, 30 Sep 2026 02:30:29 +0300 Subject: [PATCH 1/3] feat(openai): support GPT-6.1 Sol in native and Codex providers --- .../types/src/__tests__/openai-models.test.ts | 64 +++++++++- packages/types/src/providers/openai-codex.ts | 19 +++ packages/types/src/providers/openai.ts | 45 +++++++ .../__tests__/openai-sol-6.1.spec.ts | 116 ++++++++++++++++++ src/api/providers/openai-codex.ts | 2 +- 5 files changed, 244 insertions(+), 2 deletions(-) create mode 100644 src/api/providers/__tests__/openai-sol-6.1.spec.ts diff --git a/packages/types/src/__tests__/openai-models.test.ts b/packages/types/src/__tests__/openai-models.test.ts index bc2b41427c..026dc0e015 100644 --- a/packages/types/src/__tests__/openai-models.test.ts +++ b/packages/types/src/__tests__/openai-models.test.ts @@ -1,7 +1,69 @@ -import { openAiCodexModels } from "../providers/openai-codex.js" +import { openAiCodexDefaultModelId, openAiCodexModels } from "../providers/openai-codex.js" import { openAiNativeDefaultModelId, openAiNativeModels } from "../providers/openai.js" describe("OpenAI native models", () => { + it("describes GPT-6.1 Sol pricing and required reasoning without changing defaults", () => { + expect(openAiNativeDefaultModelId).toBe("gpt-5.6-sol") + expect(openAiCodexDefaultModelId).toBe("gpt-5.6-sol") + expect(openAiNativeModels["gpt-6.1-sol"]).toMatchObject({ + maxTokens: 128_000, + contextWindow: 1_050_000, + includedTools: ["apply_patch"], + excludedTools: ["apply_diff", "write_to_file"], + supportsImages: true, + supportsPromptCache: true, + supportsReasoningEffort: ["low", "medium", "high", "xhigh", "max"], + requiredReasoningEffort: true, + reasoningEffort: "medium", + supportsTemperature: false, + supportsVerbosity: true, + inputPrice: 2, + outputPrice: 10, + cacheWritesPrice: 2.5, + cacheReadsPrice: 0.1, + longContextPricing: { + thresholdTokens: 272_000, + inputPriceMultiplier: 2, + outputPriceMultiplier: 1.5, + cacheWritesPriceMultiplier: 2, + cacheReadsPriceMultiplier: 2, + appliesToServiceTiers: ["default", "flex", "priority"], + }, + }) + expect(openAiNativeModels["gpt-6.1-sol"].tiers).toEqual([ + { + name: "flex", + contextWindow: 1_050_000, + inputPrice: 1, + outputPrice: 5, + cacheWritesPrice: 1.25, + cacheReadsPrice: 0.05, + }, + { + name: "priority", + contextWindow: 1_050_000, + inputPrice: 4, + outputPrice: 20, + cacheWritesPrice: 5, + cacheReadsPrice: 0.2, + }, + ]) + expect(openAiCodexModels["gpt-6.1-sol"]).toMatchObject({ + maxTokens: 128_000, + contextWindow: 272_000, + includedTools: ["apply_patch"], + excludedTools: ["apply_diff", "write_to_file"], + supportsImages: true, + supportsPromptCache: true, + supportsReasoningEffort: ["low", "medium", "high", "xhigh", "max"], + requiredReasoningEffort: true, + reasoningEffort: "low", + supportsTemperature: false, + inputPrice: 0, + outputPrice: 0, + }) + }) + it("describes GPT-6 Astra without changing the provider default", () => { expect(openAiNativeDefaultModelId).toBe("gpt-5.6-sol") expect(openAiNativeModels["gpt-6-astra"]).toMatchObject({ diff --git a/packages/types/src/providers/openai-codex.ts b/packages/types/src/providers/openai-codex.ts index dab48ef218..ae0eae22b6 100644 --- a/packages/types/src/providers/openai-codex.ts +++ b/packages/types/src/providers/openai-codex.ts @@ -24,6 +24,25 @@ export const openAiCodexDefaultModelId: OpenAiCodexModelId = "gpt-5.6-sol" * Costs are 0 as they are covered by the subscription. */ export const openAiCodexModels = { + // https://github.com/openai/codex/blob/main/codex-rs/models-manager/models.json + "gpt-6.1-sol": { + maxTokens: 128000, + // Codex's default context window; the optional 872K maximum is not enabled here. + contextWindow: 272000, + includedTools: ["apply_patch"], + excludedTools: ["apply_diff", "write_to_file"], + supportsImages: true, + supportsPromptCache: true, + supportsReasoningEffort: ["low", "medium", "high", "xhigh", "max"], + requiredReasoningEffort: true, + reasoningEffort: "low", + inputPrice: 0, + outputPrice: 0, + supportsVerbosity: true, + supportsTemperature: false, + description: + "GPT-6.1 Sol: Near-Astra performance for complex coding and professional work via ChatGPT subscription", + }, "gpt-6-astra": { maxTokens: 128000, contextWindow: 872000, diff --git a/packages/types/src/providers/openai.ts b/packages/types/src/providers/openai.ts index 4f56bb740a..b5db12bb70 100644 --- a/packages/types/src/providers/openai.ts +++ b/packages/types/src/providers/openai.ts @@ -17,6 +17,51 @@ export const OPENAI_API_PROTOCOL = "openai" export const openAiNativeDefaultModelId: OpenAiNativeModelId = "gpt-5.6-sol" export const openAiNativeModels = { + // https://developers.openai.com/api/docs/models/gpt-6.1-sol + "gpt-6.1-sol": { + maxTokens: 128000, + contextWindow: 1_050_000, + includedTools: ["apply_patch"], + excludedTools: ["apply_diff", "write_to_file"], + supportsImages: true, + supportsPromptCache: true, + supportsReasoningEffort: ["low", "medium", "high", "xhigh", "max"], + requiredReasoningEffort: true, + reasoningEffort: "medium", + inputPrice: 2.0, + outputPrice: 10.0, + cacheWritesPrice: 2.5, + cacheReadsPrice: 0.1, + longContextPricing: { + thresholdTokens: 272_000, + inputPriceMultiplier: 2, + outputPriceMultiplier: 1.5, + cacheWritesPriceMultiplier: 2, + cacheReadsPriceMultiplier: 2, + appliesToServiceTiers: ["default", "flex", "priority"], + }, + supportsVerbosity: true, + supportsTemperature: false, + tiers: [ + { + name: "flex", + contextWindow: 1_050_000, + inputPrice: 1.0, + outputPrice: 5.0, + cacheWritesPrice: 1.25, + cacheReadsPrice: 0.05, + }, + { + name: "priority", + contextWindow: 1_050_000, + inputPrice: 4.0, + outputPrice: 20.0, + cacheWritesPrice: 5.0, + cacheReadsPrice: 0.2, + }, + ], + description: "GPT-6.1 Sol: Near-Astra performance for complex coding and professional work at a lower cost", + }, "gpt-6-astra": { maxTokens: 128000, contextWindow: 1_050_000, diff --git a/src/api/providers/__tests__/openai-sol-6.1.spec.ts b/src/api/providers/__tests__/openai-sol-6.1.spec.ts new file mode 100644 index 0000000000..ea9e8e59e8 --- /dev/null +++ b/src/api/providers/__tests__/openai-sol-6.1.spec.ts @@ -0,0 +1,116 @@ +vitest.mock("@roo-code/telemetry", () => ({ + TelemetryService: { instance: { captureException: vitest.fn() } }, +})) + +import type { ApiHandlerOptions } from "../../../shared/api" +import { openAiCodexOAuthManager } from "../../../integrations/openai-codex/oauth" +import { asyncStreamFrom, collectStream } from "../../../test-utils/stream" +import { OpenAiNativeHandler } from "../openai-native" +import { OpenAiCodexHandler } from "../openai-codex" + +const reasoningCases: { options: ApiHandlerOptions; native: string; codex: string }[] = [ + { options: {}, native: "medium", codex: "low" }, + { options: { reasoningEffort: "none" }, native: "medium", codex: "low" }, + { options: { reasoningEffort: "minimal" }, native: "medium", codex: "low" }, + { options: { reasoningEffort: "disable" }, native: "medium", codex: "low" }, + { options: { enableReasoningEffort: false }, native: "medium", codex: "low" }, + ...(["low", "medium", "high", "xhigh", "max"] as const).map((effort) => ({ + options: { reasoningEffort: effort }, + native: effort, + codex: effort, + })), +] + +describe("GPT-6.1 Sol requests", () => { + afterEach(() => vitest.restoreAllMocks()) + + it.each(reasoningCases)("uses supported reasoning for $options", ({ options, native, codex }) => { + const settings = { apiModelId: "gpt-6.1-sol", openAiNativeApiKey: "test-key", ...options } + const nativeHandler = new OpenAiNativeHandler(settings) + const codexHandler = new OpenAiCodexHandler(settings) + expect(nativeHandler.getModel().id).toBe("gpt-6.1-sol") + expect(codexHandler.getModel().id).toBe("gpt-6.1-sol") + expect(nativeHandler["getReasoningEffort"](nativeHandler.getModel())).toBe(native) + expect(codexHandler["getReasoningEffort"](codexHandler.getModel())).toBe(codex) + }) + + it("builds native Responses tool requests with verified pricing and no temperature", () => { + const handler = new OpenAiNativeHandler({ + apiModelId: "gpt-6.1-sol", + openAiNativeApiKey: "test-key", + openAiNativeServiceTier: "priority", + modelTemperature: 0.7, + }) + const model = handler.getModel() + const body = handler["buildRequestBody"](model, [], "System", "low", handler["getReasoningEffort"](model), { + taskId: "sol-task", + tools: [ + { type: "function", function: { name: "read_file", parameters: { type: "object", properties: {} } } }, + ], + }) + expect(body).toMatchObject({ + model: "gpt-6.1-sol", + max_output_tokens: 128_000, + reasoning: { effort: "medium" }, + service_tier: "priority", + tools: [{ type: "function", name: "read_file", strict: true }], + }) + expect(body).not.toHaveProperty("temperature") + expect(handler["applyServiceTierPricing"](model.info, "priority")).toMatchObject({ + inputPrice: 4, + outputPrice: 20, + cacheReadsPrice: 0.2, + cacheWritesPrice: 5, + }) + }) + + it("routes Codex tool requests through Responses Lite with Fast mode and required reasoning", async () => { + const handler = new OpenAiCodexHandler({ + apiModelId: "gpt-6.1-sol", + reasoningEffort: "none", + openAiCodexServiceTier: "priority", + }) + vitest.spyOn(openAiCodexOAuthManager, "getAccessToken").mockResolvedValue("test-token") + vitest.spyOn(openAiCodexOAuthManager, "getAccountId").mockResolvedValue("test-account") + const create = vitest.fn().mockResolvedValue(asyncStreamFrom([])) + Reflect.set(handler, "client", { responses: { create } }) + await collectStream( + handler.createMessage("System", [{ role: "user", content: "Read the file" }], { + taskId: "sol-task", + tools: [ + { + type: "function", + function: { name: "read_file", parameters: { type: "object", properties: {} } }, + }, + ], + }), + ) + expect(create).toHaveBeenCalledWith( + expect.objectContaining({ + model: "gpt-6.1-sol", + service_tier: "priority", + prompt_cache_key: "sol-task", + reasoning: { effort: "low", summary: "auto", context: "all_turns" }, + tool_choice: "auto", + parallel_tool_calls: false, + input: expect.arrayContaining([ + expect.objectContaining({ + type: "additional_tools", + tools: [expect.objectContaining({ type: "function", name: "read_file" })], + }), + ]), + }), + expect.objectContaining({ + headers: expect.objectContaining({ + "x-openai-internal-codex-responses-lite": "true", + "session-id": "sol-task", + }), + }), + ) + const body = create.mock.calls[0][0] + expect(body).not.toHaveProperty("instructions") + expect(body).not.toHaveProperty("tools") + expect(body).not.toHaveProperty("max_output_tokens") + expect(body).not.toHaveProperty("temperature") + }) +}) diff --git a/src/api/providers/openai-codex.ts b/src/api/providers/openai-codex.ts index 1498aafc66..ff8f4a24b0 100644 --- a/src/api/providers/openai-codex.ts +++ b/src/api/providers/openai-codex.ts @@ -41,7 +41,7 @@ type OpenAiCodexRequestServiceTier = typeof OpenAiCodexServiceTier.Priority const CODEX_API_BASE_URL = "https://chatgpt.com/backend-api/codex" const LUNA_MODEL_ID = "gpt-5.6-luna" const LUNA_CODEX_VERSION = "0.144.0" -const RESPONSES_LITE_MODEL_IDS = new Set([LUNA_MODEL_ID, "gpt-6-astra"]) +const RESPONSES_LITE_MODEL_IDS = new Set([LUNA_MODEL_ID, "gpt-6-astra", "gpt-6.1-sol"]) /** * A refusal is streamed as text so the chat still shows why the model declined, but it is not part From acd2e0b2f418c0e0786fd192a67cb67e58a8c99d Mon Sep 17 00:00:00 2001 From: gubin-dev Date: Wed, 30 Sep 2026 02:32:01 +0300 Subject: [PATCH 2/3] fix(openai): use supported 872K Codex context for GPT-6.1 Sol --- packages/types/src/__tests__/openai-models.test.ts | 2 +- packages/types/src/providers/openai-codex.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/types/src/__tests__/openai-models.test.ts b/packages/types/src/__tests__/openai-models.test.ts index 026dc0e015..78a3259f09 100644 --- a/packages/types/src/__tests__/openai-models.test.ts +++ b/packages/types/src/__tests__/openai-models.test.ts @@ -50,7 +50,7 @@ describe("OpenAI native models", () => { ]) expect(openAiCodexModels["gpt-6.1-sol"]).toMatchObject({ maxTokens: 128_000, - contextWindow: 272_000, + contextWindow: 872_000, includedTools: ["apply_patch"], excludedTools: ["apply_diff", "write_to_file"], supportsImages: true, diff --git a/packages/types/src/providers/openai-codex.ts b/packages/types/src/providers/openai-codex.ts index ae0eae22b6..95ba2dfae1 100644 --- a/packages/types/src/providers/openai-codex.ts +++ b/packages/types/src/providers/openai-codex.ts @@ -27,8 +27,8 @@ export const openAiCodexModels = { // https://github.com/openai/codex/blob/main/codex-rs/models-manager/models.json "gpt-6.1-sol": { maxTokens: 128000, - // Codex's default context window; the optional 872K maximum is not enabled here. - contextWindow: 272000, + // Use Codex's supported maximum rather than its 272K default compaction budget. + contextWindow: 872000, includedTools: ["apply_patch"], excludedTools: ["apply_diff", "write_to_file"], supportsImages: true, From c79f556207d7528d3ee772812cdb5a94b8f694ef Mon Sep 17 00:00:00 2001 From: Elliott de Launay Date: Wed, 30 Sep 2026 21:15:01 +0000 Subject: [PATCH 3/3] fix(build): rebuild dependents when dependency packages change --- turbo.json | 1 + 1 file changed, 1 insertion(+) diff --git a/turbo.json b/turbo.json index 2d8f8a2f98..185020e5f2 100644 --- a/turbo.json +++ b/turbo.json @@ -15,6 +15,7 @@ "cache": false }, "build": { + "dependsOn": ["^build"], "outputs": ["dist/**"], "inputs": ["src/**", "package.json", "tsconfig.json", "tsup.config.ts", "vite.config.ts"] },