From cc8e1bedf50f7dbfd41b01214e2f2773da912b3f Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Date: Tue, 23 Jun 2026 22:15:12 -0300 Subject: [PATCH] fix(github): route Copilot Codex models to /responses (port from 9router#102) (#4626) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Integrated into release/v3.8.36 — route Copilot Codex models to /responses (port #102); release-green --- open-sse/executors/github.ts | 5 ++++- tests/unit/executor-github.test.ts | 27 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/open-sse/executors/github.ts b/open-sse/executors/github.ts index f4ab762f80..036998c417 100644 --- a/open-sse/executors/github.ts +++ b/open-sse/executors/github.ts @@ -27,7 +27,10 @@ export class GithubExecutor extends BaseExecutor { buildUrl(model: string, _stream: boolean, _urlIndex = 0) { const targetFormat = getModelTargetFormat("gh", model); - if (targetFormat === "openai-responses") { + // 9router#102: Copilot Codex models advertise supported_endpoints: ["/responses"] + // and 400 on /chat/completions. Route any *-codex id to /responses even when it + // isn't in the curated registry, so newly-shipped Codex models work out of the box. + if (targetFormat === "openai-responses" || /codex/i.test(model)) { return ( this.config.responsesBaseUrl || this.config.baseUrl?.replace(/\/chat\/completions\/?$/, "/responses") || diff --git a/tests/unit/executor-github.test.ts b/tests/unit/executor-github.test.ts index 6d8194e043..ddcbf272cc 100644 --- a/tests/unit/executor-github.test.ts +++ b/tests/unit/executor-github.test.ts @@ -71,6 +71,33 @@ test("GithubExecutor.buildUrl keeps GitHub Claude Opus 4.6 on /chat/completions" assert.equal(url, "https://api.githubcopilot.com/chat/completions"); }); +test("GithubExecutor.buildUrl routes unlisted Codex models to /responses (9router#102)", () => { + // Copilot Codex models advertise supported_endpoints: ["/responses"]. When such + // a model isn't in the curated gh registry, getModelTargetFormat returns null and + // the request fell through to /chat/completions -> upstream 400 "model is not + // accessible via the /chat/completions endpoint". Any *-codex id must route to + // /responses regardless of whether it's explicitly registered. + const executor = new GithubExecutor(); + for (const model of [ + "gpt-5-codex", + "gpt-5.1-codex", + "gpt-5.1-codex-mini", + "gpt-5.1-codex-max", + "gpt-5.2-codex", + ]) { + assert.equal( + executor.buildUrl(model, true), + "https://api.githubcopilot.com/responses", + `${model} must route to /responses` + ); + } + // Non-codex unlisted models keep the chat/completions default. + assert.equal( + executor.buildUrl("some-random-chat-model", true), + "https://api.githubcopilot.com/chat/completions" + ); +}); + test("GithubExecutor.transformRequest injects JSON response instructions for Claude and strips reasoning fields", () => { const executor = new GithubExecutor(); const body = {