feat: route GPT-5.6 Pro through standard ChatGPT - #757
Conversation
📝 WalkthroughWalkthroughThe PR adds an experimental Oracle-backed ChatGPT Browser adapter for GPT-5.6 Pro, integrates provider routing and readiness checks, disables incompatible sidecars, narrows local-provider classification, and updates tests and multilingual documentation. ChangesChatGPT Browser route
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant Responses
participant ChatGptBrowserAdapter
participant Oracle
Client->>Responses: Submit chatgpt-browser/gpt-5.6-pro request
Responses->>ChatGptBrowserAdapter: Resolve adapter and run turn
ChatGptBrowserAdapter->>Oracle: Send nonce-bound prompt
Oracle-->>ChatGptBrowserAdapter: Return captured text or tool call
ChatGptBrowserAdapter-->>Responses: Emit response events
Responses-->>Client: Stream result or fail-closed error
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
756b11f to
102f00e
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 102f00e7f6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const required = parsed.options.toolChoice === "required" | ||
| || (isAllowedToolChoice(parsed.options.toolChoice) && parsed.options.toolChoice.mode === "required"); |
There was a problem hiding this comment.
When a Responses request specifies a named tool_choice, the parser represents it as { name: ... }, which semantically forces that tool, but required only recognizes the string and allowed_tools forms. If the browser model returns a final answer—or the requested tool is absent—this adapter accepts it instead of failing closed, breaking workflows that rely on a guaranteed tool invocation. Treat a named choice as required and reject final responses or an empty matching tool set.
AGENTS.md reference: src/AGENTS.md:L19-L19
Useful? React with 👍 / 👎.
| try { child?.kill("SIGINT"); } catch { /* best-effort cancellation */ } | ||
| if (child && !hardKillTimer) { | ||
| hardKillTimer = setTimeout(() => { | ||
| try { child?.kill("SIGKILL"); } catch { /* best-effort hard cancellation */ } | ||
| }, 5_000); |
There was a problem hiding this comment.
Kill the Oracle process tree on Windows
On Windows with the documented global npm installation, commandInvocation() resolves oracle.cmd through cmd.exe, so these child.kill() calls terminate only the command wrapper rather than the Node/Oracle descendant. The repository's own process-control.ts uses taskkill /T /F for this reason. After cancellation, Oracle can therefore continue the browser submission for up to 60 minutes, consume allowance, and write into a directory that the finally block has removed; terminate and await the full process tree instead.
AGENTS.md reference: src/AGENTS.md:L17-L19
Useful? React with 👍 / 👎.
| const [exitCode, stdout, stderr] = await Promise.all([ | ||
| child.exited, | ||
| readProcessText(child.stdout), | ||
| readProcessText(child.stderr), | ||
| ]); |
There was a problem hiding this comment.
Bound the Oracle version probe
If the configured executable or its --version path hangs, this unbounded Promise.all never settles. runOracleBrowserTurn() does not install its abort listener until after awaiting this probe and only checks the signal afterward, while /api/providers/test has no cancellation path at all, so the first browser request or health check can remain stuck indefinitely and impede shutdown. Apply a deadline/abort handler that terminates and reaps the probe process.
AGENTS.md reference: src/AGENTS.md:L17-L19
Useful? React with 👍 / 👎.
| return [ | ||
| "Continue the OpenAI Responses conversation encoded as JSON below.", | ||
| "Follow the system and developer instructions represented in the conversation.", | ||
| "Tools listed in conversation.tools are executed by the client, not by ChatGPT. You may request exactly one listed tool call when needed.", | ||
| "Return exactly one JSON object and no surrounding prose or Markdown fence. Copy responseProtocol.nonce exactly.", |
There was a problem hiding this comment.
Preserve instruction-role authority
Every system, developer, user, assistant, and tool message is serialized into one string that Oracle submits as a single ChatGPT user prompt. Consequently, when user input or repository content conflicts with a system/developer instruction, ChatGPT receives both at the same transport-level authority; the prose asking it to simulate the encoded roles does not preserve the Responses API's instruction hierarchy. This permits ordinary prompt injection to override developer constraints on this route, so use a transport that can preserve roles or explicitly reject contexts requiring privileged instructions rather than claiming equivalent Responses behavior.
Useful? React with 👍 / 👎.
| if (value.type !== "tool_call" || typeof value.name !== "string" || !plainRecord(value.arguments)) { | ||
| throw new ChatGptBrowserError("protocol_error"); | ||
| } |
There was a problem hiding this comment.
Validate browser tool arguments against their schema
A browser answer is accepted as a tool call whenever arguments is merely a JSON object and the name exists; selected.parameters is never evaluated. Unlike a native function-calling response, this value is plain model-generated text, so missing required fields, wrong types, disallowed enum values, and unexpected properties are relayed directly to the client tool and can fail or mis-execute the turn. Validate the arguments against the selected tool schema before emitting the call.
AGENTS.md reference: src/AGENTS.md:L19-L19
Useful? React with 👍 / 👎.
| logCtx.providerAdapter = adapter.name; | ||
| sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, adapter.name); | ||
| const isPassthrough = "passthrough" in adapter && !!adapter.passthrough; | ||
| const isChatGptBrowserRoute = route.provider.adapter === "chatgpt-browser"; |
There was a problem hiding this comment.
Fail closed when browser continuation state is missing
When a previous_response_id entry has been evicted from the bounded state cache or the server has restarted, _previousResponseInputExpanded remains false. Unlike Cursor, this adapter has no upstream conversation identifier because every Oracle invocation starts a fresh browser chat, yet after identifying the browser route the server proceeds with only the current delta or tool output. This silently drops the prior conversation and spends another Pro turn on an invalid context; add the same fail-closed replay-miss check used for Kiro before invoking Oracle.
AGENTS.md reference: src/AGENTS.md:L17-L19
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs-site/src/content/docs/guides/providers.md`:
- Around line 26-29: Replace “GPT-5.6 Sol Pro” with “GPT-5.6 Pro” in
docs-site/src/content/docs/guides/providers.md lines 26-29 and
structure/04_transports-and-sidecars.md line 56, keeping the browser route
documented consistently as chatgpt-browser/gpt-5.6-pro.
In `@src/adapters/chatgpt-browser.ts`:
- Around line 105-119: Update the catch block around the error normalization in
the ChatGPT browser adapter to record the original error before converting
non-ChatGptBrowserError values to the generic browser_failed error. Preserve the
existing emitted safe error payload and non-retryable behavior, while ensuring
the original exception message and stack are available through the established
server-side logging mechanism.
In `@src/config.ts`:
- Around line 436-438: Make oracleCommand normalization consistent across the
config schema and management-write path by extracting the raw validation and
trimming logic into a shared helper, then reuse it at both call sites. Preserve
rejection of CR/LF and NUL while trimming valid values identically; add coverage
for surrounding whitespace, line breaks, and NUL input.
In `@src/providers/registry.ts`:
- Around line 445-459: The chatgpt-browser provider must not be rewritten by
subagent model fallback. Add an explicit provider guard in the fallback logic
centered on the relevant symbol in subagent-model-fallback.ts, ensuring
thread-spawn requests preserve the registry’s gpt-5.6-pro and no-fallback
contract before route re-selection in core.ts. Add a regression test covering
thread-spawn behavior for chatgpt-browser.
In `@tests/chatgpt-browser-adapter.test.ts`:
- Around line 279-301: Update the message assertion in the “preserves actionable
fail-closed error categories” test so every error code has a non-empty,
code-specific expected message fragment. Replace the conditional fallback that
produces an empty string with explicit expectations for login_required, timeout,
oracle_missing, and oracle_incompatible, while preserving the existing
model_unavailable and quota_exhausted checks.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 8da3589f-6424-499b-9d01-883cf937beb9
📒 Files selected for processing (36)
docs-site/src/content/docs/guides/providers.mddocs-site/src/content/docs/ja/reference/adapters.mddocs-site/src/content/docs/ja/reference/architecture.mddocs-site/src/content/docs/ko/reference/adapters.mddocs-site/src/content/docs/ko/reference/architecture.mddocs-site/src/content/docs/reference/adapters.mddocs-site/src/content/docs/reference/architecture.mddocs-site/src/content/docs/reference/configuration.mddocs-site/src/content/docs/ru/reference/adapters.mddocs-site/src/content/docs/ru/reference/architecture.mddocs-site/src/content/docs/zh-cn/reference/adapters.mddocs-site/src/content/docs/zh-cn/reference/architecture.mdgui/src/provider-workspace/catalog.tsgui/src/provider-workspace/kind.tssrc/adapters/chatgpt-browser-oracle.tssrc/adapters/chatgpt-browser.tssrc/codex/catalog/parsing.tssrc/codex/catalog/provider-fetch.tssrc/codex/catalog/sync.tssrc/config.tssrc/providers/derive.tssrc/providers/registry.tssrc/server/adapter-resolve.tssrc/server/auth-cors.tssrc/server/management/context.tssrc/server/management/provider-routes.tssrc/server/responses.tssrc/server/responses/core.tssrc/types.tsstructure/01_runtime.mdstructure/04_transports-and-sidecars.mdtests/chatgpt-browser-adapter.test.tstests/images/z-handler-activation.test.tstests/provider-connection-test.test.tstests/provider-workspace-data.test.tstests/responses-state.test.ts
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/server/responses/core.ts (1)
2082-2118: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winNon-streaming chatgpt-browser turns can't surface fail-closed errors — always return HTTP 200.
The streaming branch (Lines 2025-2039) explicitly runs
preflightAdapterEventsforadapterNeedsErrorPreflight(adapter.name)adapters (currently onlychatgpt-browser) and converts a leading error event into a realformatErrorResponse(status, errorType, message, {code}). The non-streaming branch below only special-casesoptions.comboAttempt(Line 2084-2092); for an ordinary non-streaming request it passes every event — including a leading{type:"error", status, errorType, code}from the ChatGPT Browser adapter — straight intobuildResponseJSON, then returns:return new Response(JSON.stringify(json), { headers: { "Content-Type": "application/json" } });No
statusis set, so this is always HTTP 200, regardless of whether the browser turn actually failed closed (login required, quota exhausted, Oracle missing/incompatible, etc.). This defeats the PR's "distinct fail-closed errors" objective for any client that setsstream:falseagainstchatgpt-browser/gpt-5.6-pro, and there's no test in this PR covering that path.🐛 Proposed fix
await runTurn(); const events = await queue.collect(); - if (options.comboAttempt) { + if (options.comboAttempt || adapterNeedsErrorPreflight(adapter.name)) { const firstMeaningful = events.find(event => event.type !== "heartbeat"); if (!firstMeaningful || firstMeaningful.type === "error") { const message = firstMeaningful?.type === "error" ? firstMeaningful.message : "Adapter ended before producing a response"; - return formatErrorResponse(502, "upstream_error", redactSecretString(message)); + if (options.comboAttempt) { + return formatErrorResponse(502, "upstream_error", redactSecretString(message)); + } + return formatErrorResponse( + firstMeaningful?.type === "error" ? (firstMeaningful.status ?? 502) : 502, + firstMeaningful?.type === "error" ? (firstMeaningful.errorType ?? "upstream_error") : "upstream_error", + redactSecretString(message), + firstMeaningful?.type === "error" ? { code: firstMeaningful.code } : undefined, + ); } }Please also add a focused regression test near tests/images/z-handler-activation.test.ts covering
stream:falseagainst the chatgpt-browser route with a fail-closed adapter error, per the tests/** convention that behavior changes need adjacent regression coverage.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/server/responses/core.ts` around lines 2082 - 2118, Update the non-streaming turn handling around runTurn, queue.collect, and buildResponseJSON to preflight adapter events for adapters requiring error preflight, matching the streaming branch. When a leading fail-closed error is present, return formatErrorResponse with its status, errorType, message, and code instead of constructing the normal HTTP 200 response; preserve comboAttempt handling and successful responses. Add a focused regression test near z-handler-activation.test.ts covering stream:false on the chatgpt-browser route with a fail-closed adapter error and asserting the propagated status and error payload.Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/adapters/chatgpt-browser-oracle.ts`:
- Line 5: Add regression tests covering the pinned fromJSONSchema behavior used
by chatgpt-browser-oracle.ts: validate required fields, reject wrong types, and
handle additional properties before arguments reach dispatch. Reuse the existing
adapter test setup and target the schema/parsing flow around the tool-argument
handling symbols near lines 144-156 and 254-256; do not change dependency
versions.
---
Outside diff comments:
In `@src/server/responses/core.ts`:
- Around line 2082-2118: Update the non-streaming turn handling around runTurn,
queue.collect, and buildResponseJSON to preflight adapter events for adapters
requiring error preflight, matching the streaming branch. When a leading
fail-closed error is present, return formatErrorResponse with its status,
errorType, message, and code instead of constructing the normal HTTP 200
response; preserve comboAttempt handling and successful responses. Add a focused
regression test near z-handler-activation.test.ts covering stream:false on the
chatgpt-browser route with a fail-closed adapter error and asserting the
propagated status and error payload.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: a7a48685-21c2-43a2-9f60-af0fc186e693
📒 Files selected for processing (13)
docs-site/src/content/docs/guides/providers.mddocs-site/src/content/docs/reference/adapters.mdsrc/adapters/chatgpt-browser-oracle.tssrc/adapters/chatgpt-browser.tssrc/codex/subagent-model-fallback.tssrc/config.tssrc/lib/process-control.tssrc/server/responses/core.tsstructure/04_transports-and-sidecars.mdtests/chatgpt-browser-adapter.test.tstests/config.test.tstests/images/z-handler-activation.test.tstests/subagent-model-fallback.test.ts
| import { chmod, lstat, mkdtemp, readFile, rm } from "node:fs/promises"; | ||
| import { tmpdir } from "node:os"; | ||
| import { join } from "node:path"; | ||
| import { fromJSONSchema, type ZodType } from "zod"; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
Is zod's z.fromJSONSchema() still marked experimental in the latest zod release, and what changed in its behavior across recent versions?
💡 Result:
As of July 30, 2026, the z.fromJSONSchema function in Zod is still officially marked as experimental and is not considered part of Zod's stable API [1][2][3]. It is expected to undergo implementation changes in future releases [1][4][3]. Key aspects of its development and behavior in recent versions include: 1. Initial Implementation and Evolution: The function was introduced in Zod v4.2.0 [5] and refined in subsequent releases like v4.3.0 [6]. 2. Purpose and Support: It is designed to convert JSON Schema definitions (supporting drafts 4, 7, 2020-12, and OpenAPI 3.0) into Zod schemas [7][6]. 3. Metadata Handling: Recent updates, such as those in PR #5586 (included in v4.3.0), added support for capturing JSON Schema metadata (e.g., title, deprecated, examples, and custom extension fields) and storing them in a registry [8]. 4. Limitations: The maintainers note that there is no guarantee of perfect 1:1 round-trip soundness (MySchema > z.toJSONSchema > z.fromJSONSchema) due to fundamental differences between Zod and JSON Schema capabilities [6]. Additionally, the implementation uses a JSON round-trip process to normalize inputs, meaning it will fail on cyclic inputs (unless handled via $defs/$ref) and convert class instances to plain objects [4].
Citations:
- 1: https://zod.dev/json-schema
- 2: https://github.com/colinhacks/zod/blob/c7805073/packages/docs/content/json-schema.mdx
- 3: https://github.com/colinhacks/zod/blob/main/packages/docs/content/json-schema.mdx
- 4: https://github.com/colinhacks/zod/blob/76e8f706/packages/zod/src/v4/classic/from-json-schema.ts
- 5: https://github.com/colinhacks/zod/releases/tag/v4.2.0
- 6: https://github.com/colinhacks/zod/releases/tag/v4.3.0
- 7: Add
z.fromJSONSchema(),z.looseRecord(),z.xor()colinhacks/zod#5534 - 8: Support metadata in
z.fromJSONSchema()colinhacks/zod#5586
🏁 Script executed:
printf '%s\n' '--- dependency declarations ---'
rg -n '"zod"|fromJSONSchema|toolArgumentsMatchSchema' --glob 'package.json' --glob 'bun.lockb' --glob 'bun.lock' --glob 'yarn.lock' --glob 'package-lock.json' --glob 'pnpm-lock.yaml' --glob 'src/adapters/chatgpt-browser-oracle.ts'
printf '%s\n' '--- relevant implementation ---'
sed -n '135,165p' src/adapters/chatgpt-browser-oracle.ts
sed -n '240,260p' src/adapters/chatgpt-browser-oracle.tsRepository: lidge-jun/opencodex
Length of output: 189
🏁 Script executed:
printf '%s\n' '--- tracked dependency/config files ---'
git ls-files | rg '(^|/)(package\.json|bun\.lockb?|yarn\.lock|package-lock\.json|pnpm-lock\.yaml)$' || true
printf '%s\n' '--- tracked Oracle adapter files ---'
git ls-files | rg 'chatgpt-browser-oracle\.ts$' || true
printf '%s\n' '--- repository references ---'
rg -n 'fromJSONSchema|toolArgumentsMatchSchema|"zod"' . --hidden -g '!node_modules' -g '!dist' -g '!build' || trueRepository: lidge-jun/opencodex
Length of output: 5253
Add regression coverage for the pinned fromJSONSchema contract.
package.json and bun.lock already pin Zod to 4.4.3, so no further pinning is needed. Add tests for required fields, wrong types, and additional properties around src/adapters/chatgpt-browser-oracle.ts:144-156 to catch behavior changes before tool arguments reach dispatch at lines 254-256.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/adapters/chatgpt-browser-oracle.ts` at line 5, Add regression tests
covering the pinned fromJSONSchema behavior used by chatgpt-browser-oracle.ts:
validate required fields, reject wrong types, and handle additional properties
before arguments reach dispatch. Reuse the existing adapter test setup and
target the schema/parsing flow around the tool-argument handling symbols near
lines 144-156 and 254-256; do not change dependency versions.
|
Closing this. It is browser automation of the consumer ChatGPT web UI, and this project will not carry that. What the patch actually does: Secondary problems, none of which are the reason for closing:
The To be direct: do not open another pull request or issue that automates or scrapes a provider's web UI to obtain model access outside its published API. One more submission of this kind and issue/pull-request access for this account will be blocked. |
Closes #650.
Summary
Adds an experimental, explicit
chatgpt-browser/gpt-5.6-proprovider that routes through the standard ChatGPT browser surface using Oracle 0.16.1+, rather than the Codex/Work backend or OpenAI API.chatgpt.com, and explicit Pro selection;previous_response_idreplay when Codex sendsstore:false;Why
GPT-5.6 Pro is available through standard ChatGPT limits, while Codex and Work share a separate agentic usage pool. This provider is deliberately namespaced and isolated so bare GPT model IDs continue to use the canonical OpenAI provider.
Validation
exec_command, Codex executedpwd, and the next browser turn returned the exact result;store:falsecontinuation throughprevious_response_id;ocx1:checkpoint;git diff --checkpassed.Limitations
AI assistance disclosure
This PR was created with the assistance of GPT-5.6 Sol Ultra.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation