Yes, this is possible.
But for ChatGPT Plus/Pro specifically (using Codex via ChatGPT subscription), this is not the same as normal OpenAI API-key auth. It relies on ChatGPT OAuth tokens and ChatGPT backend endpoints, so we need a small transport/auth layer beyond the current API-key-only flow.
auth.jsonpersistence exists and already supportstype: "api"andtype: "oauth"insrc/persistence/auth.rs./connectcurrently always routes provider selection to API key entry insrc/app.rs+src/ui/components/api_key_input.rs.- LLM calls are built from
stream_llm_with_cancellation()insrc/llm/client.rsand currently assume API-key style provider setup.
From _dev_reference1/packages/opencode/src/plugin/codex.ts:
- Three methods for
openai:ChatGPT Pro/Plus (browser)(OAuth + PKCE + local callback)ChatGPT Pro/Plus (headless)(device auth + polling)Manually enter API Key
- OAuth issuer and token exchange endpoints:
https://auth.openai.com/oauth/authorizehttps://auth.openai.com/oauth/token
- Codex request target is rewritten to:
https://chatgpt.com/backend-api/codex/responses
- It sets
Authorization: Bearer <oauth_access_token>and, when present,ChatGPT-Account-Id. - It stores extra OAuth data (not just refresh/access/expires), including optional
accountId.
From /Users/carlo/Desktop/Projects/aisdk-rs:
- OpenAI provider currently has:
- fixed request path (
/v1/responses) - fixed header construction (Content-Type + Authorization)
- no built-in request interceptor/custom fetch hook
- no generic extra header injection on OpenAI provider settings
- fixed request path (
- OpenAI builder also enforces non-empty API key.
Conclusion: aisdk-rs in current form is not enough for full ChatGPT Plus/Pro Codex transport behavior without extending it (or bypassing it for this provider).
- OpenAI-only OAuth support in crabcode.
/connectmethod selection for OpenAI:- ChatGPT Plus/Pro (browser)
- ChatGPT Plus/Pro (headless)
- Manually enter API key (existing path)
- Use OAuth tokens for Codex completions.
- OAuth for other providers.
- Full plugin framework like opencode.
- Multi-provider OAuth abstractions beyond what OpenAI needs.
src/persistence/auth.rs
- Extend OAuth variant to include optional fields used by OpenAI OAuth:
accountId(serde rename fromaccount_id)- optional
enterpriseUrl(future-safe; can be ignored in logic)
- Keep existing
refresh,access,expiresunchanged.
- You said auth.json uses opencode-compatible shape.
ChatGPT-Account-Idheader should be set when available.
src/auth/openai_oauth.rs(orsrc/llm/openai_oauth.rsif you want to keep auth+transport together)
- Build browser OAuth authorize URL (PKCE + state).
- Run local callback server for browser flow (localhost callback).
- Implement headless/device flow:
- request user code
- poll token readiness
- exchange code for tokens
- Parse JWT claims to extract optional
chatgpt_account_id. - Refresh access token when expired.
- Return normalized auth payload ready to persist into
auth.json.
- Reuse opencode-known constants/endpoints for compatibility.
- All network calls via
reqwest.
- Non-openai providers can continue using current API key flow.
- After selecting
openaiin/connect, show a second step for method selection:- ChatGPT Plus/Pro (browser)
- ChatGPT Plus/Pro (headless)
- Manually enter API key
- If method 3 selected: show existing API key input.
- If method 1/2 selected: show OAuth progress/status overlay and finalize into OAuth auth config.
src/app.rssrc/views/connect_dialog.rs- maybe a new lightweight overlay component for OAuth status/code display
When provider is openai and auth type is oauth, requests must go to ChatGPT Codex endpoint with OAuth bearer token (+ optional account header), not standard API-key flow.
Add to OpenAI provider settings in aisdk-rs:
- customizable response path (default
/v1/responses) - additional headers map
Then crabcode can configure:
- base URL:
https://chatgpt.com/backend-api/codex - response path:
responses - auth token: OAuth access token
- extra headers:
ChatGPT-Account-Idwhen present- optional
originator/ user-agent parity headers if required
This keeps crabcode on one streaming stack and avoids a separate custom SSE client for OpenAI OAuth.
Implement a dedicated reqwest SSE transport in crabcode just for OpenAI OAuth Codex and map events into existing ChunkMessage flow.
This works, but increases maintenance and duplicates provider logic already centralized in aisdk-rs.
- On every OpenAI OAuth request, check expiry.
- If expired (or near expiry), refresh first and persist updated token.
- If refresh fails:
- surface clear toast/actionable error
- keep auth entry but mark as needing re-auth in UX messaging
For OpenAI + OAuth auth type:
- Prefer showing a codex-focused allowlist (as opencode does) to reduce unsupported model failures.
- Initial allowlist can include:
gpt-5.3-codexgpt-5.2-codexgpt-5.1-codexgpt-5.1-codex-minigpt-5.1-codex-maxcodex-mini-latest
- Keep API-key OpenAI auth path unchanged (full OpenAI model list).
- Extend
AuthConfig::OAuthwith optionalaccountId/enterpriseUrlfields. - Add serde tests to confirm opencode-compatible roundtrip JSON.
- Build OpenAI OAuth service module for browser flow.
- Add headless/device flow.
- Add refresh-token support.
- Add JWT claim parsing helper for account id extraction.
- Add openai method-selection step in
/connectflow. - Wire method actions:
- browser OAuth
- headless OAuth
- manual API key (existing)
- Add cancellation + timeout handling in UI state.
- Implement recommended aisdk-rs extension (path override + extra headers), then bump/update usage.
- In crabcode stream path, branch OpenAI auth mode:
- API key: standard OpenAI API
- OAuth: Codex endpoint + OAuth headers
- Add base URL fallback for OpenAI when models.dev
apiis empty (https://api.openai.com).
- Apply codex-focused model filtering when auth type is OpenAI OAuth.
- Improve known error mapping (e.g.,
usage_not_included) to user-friendly guidance. - Ensure
/modelsand model dialog behavior remain coherent across API vs OAuth auth types.
- Unit tests:
- OAuth URL + PKCE/state generation
- JWT account id extraction
- auth.json serde compatibility
- token refresh behavior
- Integration-style tests:
/connectopenai -> method select -> persisted auth- OAuth auth path can stream with OpenAI provider branch
- Manual smoke checks:
- browser flow on macOS
- headless flow on terminal-only path
- fallback to manual API key
- Private/undocumented endpoints may change:
- Mitigation: isolate constants + transport logic in one module, clear errors, easy hotfix points.
- OAuth token refresh failures:
- Mitigation: proactive refresh + explicit reconnect flow + preserved auth state.
- Divergence from aisdk-rs upstream:
- Mitigation: keep patch minimal and generic (path/header extensibility useful beyond this feature).
/connectforopenaioffers exactly three methods (browser OAuth, headless OAuth, manual API key).- OAuth success writes opencode-compatible
auth.jsonentry withtype: "oauth"and token fields. - OpenAI OAuth path can request Codex completions without API key.
- Manual API key path for OpenAI still works as before.
- Non-openai provider behavior remains unchanged.
This feature is feasible and a good fit for crabcode.
The only notable blocker is that current aisdk-rs OpenAI transport is too rigid for ChatGPT Plus/Pro Codex endpoint requirements. Once we add minimal path/header configurability (or implement a temporary custom transport), the rest is straightforward engineering work in auth flow + connect UX.
- Codex OAuth path can execute one tool call, then frequently ends the stream before the follow-up model step.
- Repro pattern: assistant emits short preamble text + one tool call, tool runs, then turn ends without the next assistant/tool step.
- In
aisdk-rsstream_text()orchestration, step termination currently marksStopReason::Finishas soon as anyDone(Text|Reasoning)output appears. - Codex can return mixed outputs in a single completed response (for example: an output text message plus one or more function calls).
- If text is seen first, the loop marks the step finished even when tool calls are also present in that same step payload.
- This explains intermittency: it depends on output composition/order from the backend for that turn.
- Update
aisdk-rsstep-finalization logic to decide finish based on the whole step, not the first non-tool output seen. - Continue looping when a step contains any tool call, even if text/reasoning is also present.
- Only set
StopReason::Finishwhen a step has terminal assistant content and no tool calls. - Apply the same mixed-output guard to non-streaming
generate_text()for consistency. - Add regression tests for mixed output (
Text + ToolCall) to prevent future regressions.
cargo testtargeted for new mixed-output tests inaisdk-rs.cargo check --features openaiinaisdk-rs.cargo checkincrabcode.- Manual smoke: ask for a task that typically needs
glob -> read -> summarizeand verify multi-step tool loop no longer stops after first call.