From 166309cbe6d53629db3ab1f671257d8ab4275027 Mon Sep 17 00:00:00 2001 From: chilung Date: Tue, 8 Sep 2026 20:14:09 +0000 Subject: [PATCH 1/6] feat(doctor): explain ChatGPT public endpoint latency Document the public ChatGPT channel used by proxy pool routing and report the same read-only guidance from ocx doctor without hard-coding historical latency measurements or changing service-tier confirmation semantics. Closes #2455 --- .../src/content/docs/guides/codex-integration.md | 12 ++++++++++++ src/cli/doctor.ts | 13 +++++++++++++ tests/codex-integration/doctor.test.ts | 10 ++++++++++ 3 files changed, 35 insertions(+) diff --git a/docs-site/src/content/docs/guides/codex-integration.md b/docs-site/src/content/docs/guides/codex-integration.md index 5b75a21b41..483bce8eff 100644 --- a/docs-site/src/content/docs/guides/codex-integration.md +++ b/docs-site/src/content/docs/guides/codex-integration.md @@ -76,6 +76,18 @@ adding a `[features]` table. Fast mode is separate from voice transport. A supported model's service-tier speed description does not guarantee lower microphone, WebRTC, or end-to-end voice latency through OpenCodex. +### ChatGPT-family channel and latency + +Native ChatGPT-family requests routed through opencodex use the public ChatGPT endpoint. The +native Codex app channel is not available through the proxy pool, so the upstream may spend time +queueing a request before the first output even when the local proxy and network path are healthy. +This channel difference can make the same request feel slower than a DeepSeek or Kimi provider. + +`service_tier: priority` is a request preference. It does not prove that the upstream granted that +tier. Check the response tier shown in request logs when you need to distinguish the requested +preference from the backend's decision. For latency-sensitive work, choose a provider with a +shorter observed queue or run Codex natively when the app channel is required. + The proxy listens on port `10100` by default and serves `POST /v1/responses`, `POST /v1/responses/compact`, `POST /v1/images/generations`, `POST /v1/images/edits`, `GET /v1/models`, `GET /healthz`, and the `/api/*` management surface. diff --git a/src/cli/doctor.ts b/src/cli/doctor.ts index d7148530a0..98fce8840d 100644 --- a/src/cli/doctor.ts +++ b/src/cli/doctor.ts @@ -1000,6 +1000,17 @@ export function proxyDownRestartHint(input: { return `The ocx proxy is not running. ${uncleanExit}Codex/Claude clients pinned to 127.0.0.1:${input.port} fail with errors like "error sending request for url (http://127.0.0.1:${input.port}/v1/responses)". ${restart}`; } +/** Explain the expected channel and latency trade-off for native ChatGPT routing. */ +export function chatgptPublicEndpointHint( + providers: Record | undefined, +): string | null { + const openai = providers?.openai; + if (!openai || typeof openai !== "object" || (openai as { adapter?: unknown }).adapter !== "openai-responses") { + return null; + } + return "ChatGPT-family requests use the public ChatGPT endpoint through this proxy, so upstream queue delay before the first output can be higher than DeepSeek/Kimi. The native Codex app channel is unavailable through the proxy pool; use a latency-sensitive provider or run Codex natively when that channel matters. service_tier=priority is a request preference; inspect response tier in logs to see what the backend granted."; +} + export async function runDoctor(args: string[] = []): Promise { if (args.includes("--fix-codex-runtime")) { const resolved = resolveCodexRuntime(); @@ -1330,6 +1341,8 @@ export async function runDoctor(args: string[] = []): Promise { // Hints, not fixes. const hints: string[] = []; + const chatgptHint = chatgptPublicEndpointHint(doctorConfig.providers); + if (chatgptHint) hints.push(chatgptHint); const proxyDown = proxyDownRestartHint({ proxyRunning: Boolean(live), port: live?.port ?? doctorConfig.port ?? 10100, diff --git a/tests/codex-integration/doctor.test.ts b/tests/codex-integration/doctor.test.ts index acb0f1b87e..66bfb9a721 100644 --- a/tests/codex-integration/doctor.test.ts +++ b/tests/codex-integration/doctor.test.ts @@ -12,6 +12,7 @@ import { collectConfiguredProxy, collectProxyEnv, collectRunningProxyEnv, + chatgptPublicEndpointHint, collectWslDualInstall, fetchServiceMemory, formatResponseTempLines, @@ -641,6 +642,15 @@ describe("service memory section (#314 WP4)", () => { expect(hint).toContain("ocx service install"); }); + test("ChatGPT public endpoint hint explains channel latency without claiming a fixed delay", () => { + const hint = chatgptPublicEndpointHint({ openai: { adapter: "openai-responses" } }); + expect(hint).toContain("public ChatGPT endpoint"); + expect(hint).toContain("native Codex app channel"); + expect(hint).toContain("DeepSeek/Kimi"); + expect(hint).not.toContain("11s"); + expect(chatgptPublicEndpointHint({})).toBeNull(); + }); + test("proxyDownRestartHint prefers 'ocx service start' when a service is installed", () => { const hint = proxyDownRestartHint({ proxyRunning: false, port: 12000, serviceViable: true }); expect(hint).toContain("ocx service start"); From 7bd742948167e5e7f1aa6f9db8118ffd39e52817 Mon Sep 17 00:00:00 2001 From: chilung Date: Tue, 8 Sep 2026 20:23:33 +0000 Subject: [PATCH 2/6] docs(codex-integration): clarify ChatGPT channel scope and verify doctor hint wiring --- .../src/content/docs/guides/codex-integration.md | 12 ++++++++---- tests/codex-integration/doctor.test.ts | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/docs-site/src/content/docs/guides/codex-integration.md b/docs-site/src/content/docs/guides/codex-integration.md index 483bce8eff..3595f7e628 100644 --- a/docs-site/src/content/docs/guides/codex-integration.md +++ b/docs-site/src/content/docs/guides/codex-integration.md @@ -78,10 +78,14 @@ does not guarantee lower microphone, WebRTC, or end-to-end voice latency through ### ChatGPT-family channel and latency -Native ChatGPT-family requests routed through opencodex use the public ChatGPT endpoint. The -native Codex app channel is not available through the proxy pool, so the upstream may spend time -queueing a request before the first output even when the local proxy and network path are healthy. -This channel difference can make the same request feel slower than a DeepSeek or Kimi provider. +Native ChatGPT-family requests routed through opencodex via the canonical ChatGPT-login `openai` +forward provider (covering both Pool and Direct modes) use the public ChatGPT endpoint. The +native Codex app channel is not available through the proxy pool, and provider routing or account +selection does not bypass the upstream ChatGPT channel. The upstream may spend time queueing a +request before the first output even when the local proxy and network path are healthy. This +behavior is specific to ChatGPT-login routing and does not apply to `openai-apikey` or custom +providers, which connect directly to their respective API endpoints without public ChatGPT channel +queueing. `service_tier: priority` is a request preference. It does not prove that the upstream granted that tier. Check the response tier shown in request logs when you need to distinguish the requested diff --git a/tests/codex-integration/doctor.test.ts b/tests/codex-integration/doctor.test.ts index 66bfb9a721..9dc37fd6ff 100644 --- a/tests/codex-integration/doctor.test.ts +++ b/tests/codex-integration/doctor.test.ts @@ -967,4 +967,20 @@ describe("doctor reports an unclean prior proxy exit", () => { expect(logged.join("\n")).not.toContain("may have exited unexpectedly"); }); + + test("runDoctor outputs ChatGPT public endpoint hint when openai adapter is configured", async () => { + const { writeFileSync } = await import("fs"); + const { join } = await import("path"); + writeFileSync( + join(tempHome, "config.json"), + JSON.stringify({ port: 9, codexAutoStart: false, providers: { openai: { adapter: "openai-responses" } } }), + "utf8", + ); + + await runDoctor([]); + + const output = logged.join("\n"); + expect(output).toContain("public ChatGPT endpoint"); + expect(output).toContain("native Codex app channel"); + }); }); From b8853a4a8d4bdfff9ea37494182e507b2d84fc47 Mon Sep 17 00:00:00 2001 From: chilung Date: Tue, 8 Sep 2026 21:33:27 +0000 Subject: [PATCH 3/6] fix(doctor): restrict ChatGPT hint to canonical forward auth and clarify mode parity --- .../src/content/docs/guides/codex-integration.md | 2 +- src/cli/doctor.ts | 16 ++++++++++++++-- tests/codex-integration/doctor.test.ts | 3 +++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/docs-site/src/content/docs/guides/codex-integration.md b/docs-site/src/content/docs/guides/codex-integration.md index 3595f7e628..00f7b65941 100644 --- a/docs-site/src/content/docs/guides/codex-integration.md +++ b/docs-site/src/content/docs/guides/codex-integration.md @@ -80,7 +80,7 @@ does not guarantee lower microphone, WebRTC, or end-to-end voice latency through Native ChatGPT-family requests routed through opencodex via the canonical ChatGPT-login `openai` forward provider (covering both Pool and Direct modes) use the public ChatGPT endpoint. The -native Codex app channel is not available through the proxy pool, and provider routing or account +native Codex app channel is not available through OpenCodex routing in either Pool or Direct mode, and provider routing or account selection does not bypass the upstream ChatGPT channel. The upstream may spend time queueing a request before the first output even when the local proxy and network path are healthy. This behavior is specific to ChatGPT-login routing and does not apply to `openai-apikey` or custom diff --git a/src/cli/doctor.ts b/src/cli/doctor.ts index 98fce8840d..6e87e8e26c 100644 --- a/src/cli/doctor.ts +++ b/src/cli/doctor.ts @@ -1005,10 +1005,22 @@ export function chatgptPublicEndpointHint( providers: Record | undefined, ): string | null { const openai = providers?.openai; - if (!openai || typeof openai !== "object" || (openai as { adapter?: unknown }).adapter !== "openai-responses") { + if (!openai || typeof openai !== "object") { return null; } - return "ChatGPT-family requests use the public ChatGPT endpoint through this proxy, so upstream queue delay before the first output can be higher than DeepSeek/Kimi. The native Codex app channel is unavailable through the proxy pool; use a latency-sensitive provider or run Codex natively when that channel matters. service_tier=priority is a request preference; inspect response tier in logs to see what the backend granted."; + const typed = openai as { adapter?: unknown; authMode?: unknown; baseUrl?: unknown }; + if (typed.adapter !== "openai-responses") { + return null; + } + const authMode = typed.authMode ?? "forward"; + if (authMode !== "forward") { + return null; + } + const baseUrl = typeof typed.baseUrl === "string" ? typed.baseUrl : ""; + if (baseUrl && !baseUrl.includes("chatgpt.com")) { + return null; + } + return "ChatGPT-family requests use the public ChatGPT endpoint through this proxy, so upstream queue delay before the first output can be higher than DeepSeek/Kimi. The native Codex app channel is unavailable through OpenCodex routing (in both Pool and Direct modes); use a latency-sensitive provider or run Codex natively when that channel matters. service_tier=priority is a request preference; inspect response tier in logs to see what the backend granted."; } export async function runDoctor(args: string[] = []): Promise { diff --git a/tests/codex-integration/doctor.test.ts b/tests/codex-integration/doctor.test.ts index 9dc37fd6ff..3aaed6f358 100644 --- a/tests/codex-integration/doctor.test.ts +++ b/tests/codex-integration/doctor.test.ts @@ -647,8 +647,11 @@ describe("service memory section (#314 WP4)", () => { expect(hint).toContain("public ChatGPT endpoint"); expect(hint).toContain("native Codex app channel"); expect(hint).toContain("DeepSeek/Kimi"); + expect(hint).toContain("both Pool and Direct modes"); expect(hint).not.toContain("11s"); expect(chatgptPublicEndpointHint({})).toBeNull(); + expect(chatgptPublicEndpointHint({ openai: { adapter: "openai-responses", authMode: "key" } })).toBeNull(); + expect(chatgptPublicEndpointHint({ openai: { adapter: "openai-responses", baseUrl: "https://api.openai.com/v1" } })).toBeNull(); }); test("proxyDownRestartHint prefers 'ocx service start' when a service is installed", () => { From 22420a76e395334c337419f6f34ea6f99ffe9d55 Mon Sep 17 00:00:00 2001 From: chilung Date: Wed, 9 Sep 2026 00:28:04 +0000 Subject: [PATCH 4/6] fix(doctor): validate baseUrl hostname for ChatGPT public endpoint hint --- src/cli/doctor.ts | 12 ++++++++++-- tests/codex-integration/doctor.test.ts | 5 +++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/cli/doctor.ts b/src/cli/doctor.ts index 6e87e8e26c..e5eda3afcc 100644 --- a/src/cli/doctor.ts +++ b/src/cli/doctor.ts @@ -1017,8 +1017,16 @@ export function chatgptPublicEndpointHint( return null; } const baseUrl = typeof typed.baseUrl === "string" ? typed.baseUrl : ""; - if (baseUrl && !baseUrl.includes("chatgpt.com")) { - return null; + if (baseUrl) { + let hostname: string; + try { + hostname = new URL(baseUrl).hostname.toLowerCase(); + } catch { + return null; + } + if (hostname !== "chatgpt.com" && !hostname.endsWith(".chatgpt.com")) { + return null; + } } return "ChatGPT-family requests use the public ChatGPT endpoint through this proxy, so upstream queue delay before the first output can be higher than DeepSeek/Kimi. The native Codex app channel is unavailable through OpenCodex routing (in both Pool and Direct modes); use a latency-sensitive provider or run Codex natively when that channel matters. service_tier=priority is a request preference; inspect response tier in logs to see what the backend granted."; } diff --git a/tests/codex-integration/doctor.test.ts b/tests/codex-integration/doctor.test.ts index 3aaed6f358..f6c1a0baa2 100644 --- a/tests/codex-integration/doctor.test.ts +++ b/tests/codex-integration/doctor.test.ts @@ -652,6 +652,11 @@ describe("service memory section (#314 WP4)", () => { expect(chatgptPublicEndpointHint({})).toBeNull(); expect(chatgptPublicEndpointHint({ openai: { adapter: "openai-responses", authMode: "key" } })).toBeNull(); expect(chatgptPublicEndpointHint({ openai: { adapter: "openai-responses", baseUrl: "https://api.openai.com/v1" } })).toBeNull(); + expect(chatgptPublicEndpointHint({ openai: { adapter: "openai-responses", baseUrl: "https://chatgpt.com.example/v1" } })).toBeNull(); + expect(chatgptPublicEndpointHint({ openai: { adapter: "openai-responses", baseUrl: "https://gateway.example/chatgpt.com/v1" } })).toBeNull(); + expect(chatgptPublicEndpointHint({ openai: { adapter: "openai-responses", baseUrl: "not-a-valid-url" } })).toBeNull(); + expect(chatgptPublicEndpointHint({ openai: { adapter: "openai-responses", baseUrl: "https://chatgpt.com/backend-api" } })).not.toBeNull(); + expect(chatgptPublicEndpointHint({ openai: { adapter: "openai-responses", baseUrl: "https://subdomain.chatgpt.com/v1" } })).not.toBeNull(); }); test("proxyDownRestartHint prefers 'ocx service start' when a service is installed", () => { From 11d6617fd86bd6e43bbf9dac6979612c5f743ffe Mon Sep 17 00:00:00 2001 From: t Date: Wed, 9 Sep 2026 15:25:36 +0900 Subject: [PATCH 5/6] fix(doctor): gate ChatGPT hint on canonical forward provider, correct tier and transport wording --- .../content/docs/guides/codex-integration.md | 26 ++++++++------- src/cli/doctor.ts | 26 ++++----------- tests/codex-integration/doctor.test.ts | 32 +++++++++++-------- 3 files changed, 40 insertions(+), 44 deletions(-) diff --git a/docs-site/src/content/docs/guides/codex-integration.md b/docs-site/src/content/docs/guides/codex-integration.md index 00f7b65941..894c4416d4 100644 --- a/docs-site/src/content/docs/guides/codex-integration.md +++ b/docs-site/src/content/docs/guides/codex-integration.md @@ -79,18 +79,20 @@ does not guarantee lower microphone, WebRTC, or end-to-end voice latency through ### ChatGPT-family channel and latency Native ChatGPT-family requests routed through opencodex via the canonical ChatGPT-login `openai` -forward provider (covering both Pool and Direct modes) use the public ChatGPT endpoint. The -native Codex app channel is not available through OpenCodex routing in either Pool or Direct mode, and provider routing or account -selection does not bypass the upstream ChatGPT channel. The upstream may spend time queueing a -request before the first output even when the local proxy and network path are healthy. This -behavior is specific to ChatGPT-login routing and does not apply to `openai-apikey` or custom -providers, which connect directly to their respective API endpoints without public ChatGPT channel -queueing. - -`service_tier: priority` is a request preference. It does not prove that the upstream granted that -tier. Check the response tier shown in request logs when you need to distinguish the requested -preference from the backend's decision. For latency-sensitive work, choose a provider with a -shorter observed queue or run Codex natively when the app channel is required. +forward provider (covering both Pool and Direct modes) use the public ChatGPT endpoint. Provider +routing or account selection does not bypass the upstream ChatGPT channel. The upstream may spend +time queueing a request before the first output even when the local proxy and network path are +healthy. Streaming turns already ride the ChatGPT websocket transport — the same +`responses_websockets` lane Codex CLI defaults to — so the remaining gap is the public-endpoint +queue itself, not the transport. This behavior is specific to ChatGPT-login routing and does not +apply to `openai-apikey` or custom providers, which connect directly to their respective API +endpoints without public ChatGPT channel queueing. + +`service_tier: priority` is a request preference. On the ChatGPT backend the echoed +`service_tier` cannot confirm or deny the granted tier: turns scheduled as priority can still +echo `default`, so request logs show the response tier as an observation with confirmation +`assumed`. For latency-sensitive work, choose a provider with a shorter observed queue or run +Codex natively when the native app channel is required. The proxy listens on port `10100` by default and serves `POST /v1/responses`, `POST /v1/responses/compact`, `POST /v1/images/generations`, `POST /v1/images/edits`, diff --git a/src/cli/doctor.ts b/src/cli/doctor.ts index e5eda3afcc..1b435a926d 100644 --- a/src/cli/doctor.ts +++ b/src/cli/doctor.ts @@ -45,6 +45,8 @@ import { probeCodexCoordinatorNamespace, resolveEffectiveUserIdentity, } from "../codex/user-identity"; +import { isCanonicalOpenAiForwardProvider } from "../providers/openai-tiers-destination"; +import type { OcxProviderConfig } from "../types/provider"; import { collectProjectCodexConfigWarnings, formatProjectCodexConfigWarningsForDoctor } from "../codex/project-config-warnings"; import { collectLegacyCodexConfigKeyDiagnostics, @@ -1008,27 +1010,13 @@ export function chatgptPublicEndpointHint( if (!openai || typeof openai !== "object") { return null; } - const typed = openai as { adapter?: unknown; authMode?: unknown; baseUrl?: unknown }; - if (typed.adapter !== "openai-responses") { + // Same classification the router uses: adapter + forward auth + the exact + // canonical ChatGPT-login URL. A hostname lookalike must not get this + // guidance, and a missing authMode is the runtime "key" default, not forward. + if (!isCanonicalOpenAiForwardProvider(openai as OcxProviderConfig)) { return null; } - const authMode = typed.authMode ?? "forward"; - if (authMode !== "forward") { - return null; - } - const baseUrl = typeof typed.baseUrl === "string" ? typed.baseUrl : ""; - if (baseUrl) { - let hostname: string; - try { - hostname = new URL(baseUrl).hostname.toLowerCase(); - } catch { - return null; - } - if (hostname !== "chatgpt.com" && !hostname.endsWith(".chatgpt.com")) { - return null; - } - } - return "ChatGPT-family requests use the public ChatGPT endpoint through this proxy, so upstream queue delay before the first output can be higher than DeepSeek/Kimi. The native Codex app channel is unavailable through OpenCodex routing (in both Pool and Direct modes); use a latency-sensitive provider or run Codex natively when that channel matters. service_tier=priority is a request preference; inspect response tier in logs to see what the backend granted."; + return "ChatGPT-family requests use the public ChatGPT endpoint through this proxy, so upstream queue delay before the first output can be higher than providers without that public queue. Streaming turns already ride the ChatGPT websocket transport (the same responses_websockets lane Codex CLI defaults to); the remaining gap is the public-endpoint queue itself, in both Pool and Direct modes. service_tier=priority is a request preference: this backend can echo service_tier \"default\" even on turns it scheduled as priority (#2558), so the echoed response tier in request logs stays an observation with confirmation \"assumed\" and cannot confirm or deny the granted tier. For latency-sensitive work, choose a provider with a shorter observed queue or run Codex natively."; } export async function runDoctor(args: string[] = []): Promise { diff --git a/tests/codex-integration/doctor.test.ts b/tests/codex-integration/doctor.test.ts index f6c1a0baa2..e91a8bbb30 100644 --- a/tests/codex-integration/doctor.test.ts +++ b/tests/codex-integration/doctor.test.ts @@ -643,20 +643,26 @@ describe("service memory section (#314 WP4)", () => { }); test("ChatGPT public endpoint hint explains channel latency without claiming a fixed delay", () => { - const hint = chatgptPublicEndpointHint({ openai: { adapter: "openai-responses" } }); + const canonical = { adapter: "openai-responses", authMode: "forward", baseUrl: "https://chatgpt.com/backend-api/codex" }; + const hint = chatgptPublicEndpointHint({ openai: canonical }); expect(hint).toContain("public ChatGPT endpoint"); - expect(hint).toContain("native Codex app channel"); - expect(hint).toContain("DeepSeek/Kimi"); + expect(hint).toContain("assumed"); + expect(hint).toContain("websocket"); expect(hint).toContain("both Pool and Direct modes"); expect(hint).not.toContain("11s"); expect(chatgptPublicEndpointHint({})).toBeNull(); - expect(chatgptPublicEndpointHint({ openai: { adapter: "openai-responses", authMode: "key" } })).toBeNull(); - expect(chatgptPublicEndpointHint({ openai: { adapter: "openai-responses", baseUrl: "https://api.openai.com/v1" } })).toBeNull(); - expect(chatgptPublicEndpointHint({ openai: { adapter: "openai-responses", baseUrl: "https://chatgpt.com.example/v1" } })).toBeNull(); - expect(chatgptPublicEndpointHint({ openai: { adapter: "openai-responses", baseUrl: "https://gateway.example/chatgpt.com/v1" } })).toBeNull(); - expect(chatgptPublicEndpointHint({ openai: { adapter: "openai-responses", baseUrl: "not-a-valid-url" } })).toBeNull(); - expect(chatgptPublicEndpointHint({ openai: { adapter: "openai-responses", baseUrl: "https://chatgpt.com/backend-api" } })).not.toBeNull(); - expect(chatgptPublicEndpointHint({ openai: { adapter: "openai-responses", baseUrl: "https://subdomain.chatgpt.com/v1" } })).not.toBeNull(); + // A missing authMode is the runtime "key" default, not the forward login. + expect(chatgptPublicEndpointHint({ openai: { adapter: "openai-responses", baseUrl: "https://chatgpt.com/backend-api/codex" } })).toBeNull(); + expect(chatgptPublicEndpointHint({ openai: { adapter: "openai-responses", authMode: "key", baseUrl: "https://chatgpt.com/backend-api/codex" } })).toBeNull(); + expect(chatgptPublicEndpointHint({ openai: { adapter: "openai-responses", authMode: "forward", baseUrl: "https://api.openai.com/v1" } })).toBeNull(); + expect(chatgptPublicEndpointHint({ openai: { adapter: "openai-responses", authMode: "forward", baseUrl: "https://chatgpt.com.example/v1" } })).toBeNull(); + expect(chatgptPublicEndpointHint({ openai: { adapter: "openai-responses", authMode: "forward", baseUrl: "https://gateway.example/chatgpt.com/v1" } })).toBeNull(); + expect(chatgptPublicEndpointHint({ openai: { adapter: "openai-responses", authMode: "forward", baseUrl: "not-a-valid-url" } })).toBeNull(); + // Only the exact canonical URL qualifies: no parent path, no subdomain. + expect(chatgptPublicEndpointHint({ openai: { adapter: "openai-responses", authMode: "forward", baseUrl: "https://chatgpt.com/backend-api" } })).toBeNull(); + expect(chatgptPublicEndpointHint({ openai: { adapter: "openai-responses", authMode: "forward", baseUrl: "https://subdomain.chatgpt.com/v1" } })).toBeNull(); + // Trailing slashes still normalize to the canonical URL. + expect(chatgptPublicEndpointHint({ openai: { adapter: "openai-responses", authMode: "forward", baseUrl: "https://chatgpt.com/backend-api/codex/" } })).not.toBeNull(); }); test("proxyDownRestartHint prefers 'ocx service start' when a service is installed", () => { @@ -976,12 +982,12 @@ describe("doctor reports an unclean prior proxy exit", () => { expect(logged.join("\n")).not.toContain("may have exited unexpectedly"); }); - test("runDoctor outputs ChatGPT public endpoint hint when openai adapter is configured", async () => { + test("runDoctor outputs ChatGPT public endpoint hint when the canonical openai provider is configured", async () => { const { writeFileSync } = await import("fs"); const { join } = await import("path"); writeFileSync( join(tempHome, "config.json"), - JSON.stringify({ port: 9, codexAutoStart: false, providers: { openai: { adapter: "openai-responses" } } }), + JSON.stringify({ port: 9, codexAutoStart: false, providers: { openai: { adapter: "openai-responses", authMode: "forward", baseUrl: "https://chatgpt.com/backend-api/codex" } } }), "utf8", ); @@ -989,6 +995,6 @@ describe("doctor reports an unclean prior proxy exit", () => { const output = logged.join("\n"); expect(output).toContain("public ChatGPT endpoint"); - expect(output).toContain("native Codex app channel"); + expect(output).toContain("assumed"); }); }); From 3bd66da64dbfda7983f8a937bebd02add433476e Mon Sep 17 00:00:00 2001 From: t Date: Wed, 9 Sep 2026 16:12:14 +0900 Subject: [PATCH 6/6] fix(doctor): hedge ChatGPT latency copy to what the classifier can support --- .../content/docs/guides/codex-integration.md | 20 ++++++++++++------- src/cli/doctor.ts | 2 +- tests/codex-integration/doctor.test.ts | 5 +++++ 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/docs-site/src/content/docs/guides/codex-integration.md b/docs-site/src/content/docs/guides/codex-integration.md index 894c4416d4..d4b6329025 100644 --- a/docs-site/src/content/docs/guides/codex-integration.md +++ b/docs-site/src/content/docs/guides/codex-integration.md @@ -82,17 +82,23 @@ Native ChatGPT-family requests routed through opencodex via the canonical ChatGP forward provider (covering both Pool and Direct modes) use the public ChatGPT endpoint. Provider routing or account selection does not bypass the upstream ChatGPT channel. The upstream may spend time queueing a request before the first output even when the local proxy and network path are -healthy. Streaming turns already ride the ChatGPT websocket transport — the same -`responses_websockets` lane Codex CLI defaults to — so the remaining gap is the public-endpoint -queue itself, not the transport. This behavior is specific to ChatGPT-login routing and does not -apply to `openai-apikey` or custom providers, which connect directly to their respective API -endpoints without public ChatGPT channel queueing. +healthy. + +Eligible streaming turns dial the ChatGPT websocket transport — the same `responses_websockets` +lane Codex CLI defaults to — and fall back to SSE over HTTP when a turn is not eligible: an +unsupported Bun runtime, an oversized `response.create` frame, or a proxy route that cannot carry +the socket. Local provider pacing can also hold a request before it is dispatched at all. So a slow +first output has several possible contributors, and upstream queueing is only one of them. `ocx +doctor` classifies configuration and measures none of these: compare actual transport, pacing, +network, and provider observations before concluding. This routing behavior is specific to +ChatGPT-login routing and does not apply to `openai-apikey` or custom providers, which connect +directly to their respective API endpoints without public ChatGPT channel queueing. `service_tier: priority` is a request preference. On the ChatGPT backend the echoed `service_tier` cannot confirm or deny the granted tier: turns scheduled as priority can still echo `default`, so request logs show the response tier as an observation with confirmation -`assumed`. For latency-sensitive work, choose a provider with a shorter observed queue or run -Codex natively when the native app channel is required. +`assumed`. For latency-sensitive work, compare observed first-output times across the providers you +actually use rather than assuming any particular channel is faster. The proxy listens on port `10100` by default and serves `POST /v1/responses`, `POST /v1/responses/compact`, `POST /v1/images/generations`, `POST /v1/images/edits`, diff --git a/src/cli/doctor.ts b/src/cli/doctor.ts index 1b435a926d..91be20d8d5 100644 --- a/src/cli/doctor.ts +++ b/src/cli/doctor.ts @@ -1016,7 +1016,7 @@ export function chatgptPublicEndpointHint( if (!isCanonicalOpenAiForwardProvider(openai as OcxProviderConfig)) { return null; } - return "ChatGPT-family requests use the public ChatGPT endpoint through this proxy, so upstream queue delay before the first output can be higher than providers without that public queue. Streaming turns already ride the ChatGPT websocket transport (the same responses_websockets lane Codex CLI defaults to); the remaining gap is the public-endpoint queue itself, in both Pool and Direct modes. service_tier=priority is a request preference: this backend can echo service_tier \"default\" even on turns it scheduled as priority (#2558), so the echoed response tier in request logs stays an observation with confirmation \"assumed\" and cannot confirm or deny the granted tier. For latency-sensitive work, choose a provider with a shorter observed queue or run Codex natively."; + return "ChatGPT-family requests use the public ChatGPT endpoint through this proxy, in both Pool and Direct modes. Eligible streaming turns dial the ChatGPT websocket transport (the same responses_websockets lane Codex CLI defaults to) and fall back to SSE over HTTP when a turn is not eligible - an unsupported Bun runtime, an oversized create frame, or a proxy route that cannot carry the socket - and local provider pacing can hold a request before it is dispatched at all. This hint classifies configuration only and measures nothing, so upstream queueing is one possible contributor to a slow first output: compare actual transport, pacing, network, and provider observations before concluding. service_tier=priority is a request preference: this backend can echo service_tier \"default\" even on turns it scheduled as priority (#2558), so the echoed response tier in request logs stays an observation with confirmation \"assumed\" and cannot confirm or deny the granted tier."; } export async function runDoctor(args: string[] = []): Promise { diff --git a/tests/codex-integration/doctor.test.ts b/tests/codex-integration/doctor.test.ts index e91a8bbb30..07ec5991ba 100644 --- a/tests/codex-integration/doctor.test.ts +++ b/tests/codex-integration/doctor.test.ts @@ -650,6 +650,11 @@ describe("service memory section (#314 WP4)", () => { expect(hint).toContain("websocket"); expect(hint).toContain("both Pool and Direct modes"); expect(hint).not.toContain("11s"); + // The helper classifies configuration; it measures no latency. The copy has + // to stay hedged because eligible turns can still fall back to SSE and + // local pacing can delay dispatch before any upstream work starts. + expect(hint).toContain("fall back"); + expect(hint).toContain("one possible contributor"); expect(chatgptPublicEndpointHint({})).toBeNull(); // A missing authMode is the runtime "key" default, not the forward login. expect(chatgptPublicEndpointHint({ openai: { adapter: "openai-responses", baseUrl: "https://chatgpt.com/backend-api/codex" } })).toBeNull();