Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions docs-site/src/content/docs/guides/codex-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,22 @@ 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 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.

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.
Expand Down
33 changes: 33 additions & 0 deletions src/cli/doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,37 @@ 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<string, unknown> | undefined,
): string | null {
const openai = providers?.openai;
if (!openai || typeof openai !== "object") {
return null;
}
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) {
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.";
}

export async function runDoctor(args: string[] = []): Promise<void> {
if (args.includes("--fix-codex-runtime")) {
const resolved = resolveCodexRuntime();
Expand Down Expand Up @@ -1330,6 +1361,8 @@ export async function runDoctor(args: string[] = []): Promise<void> {

// 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,
Expand Down
34 changes: 34 additions & 0 deletions tests/codex-integration/doctor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
collectConfiguredProxy,
collectProxyEnv,
collectRunningProxyEnv,
chatgptPublicEndpointHint,
collectWslDualInstall,
fetchServiceMemory,
formatResponseTempLines,
Expand Down Expand Up @@ -641,6 +642,23 @@ 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).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();
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.

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");
Expand Down Expand Up @@ -957,4 +975,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");
});
});
Loading