feat: Reuse Codex CLI OAuth tokens for ChatGPT backend LLM calls#693
feat: Reuse Codex CLI OAuth tokens for ChatGPT backend LLM calls#693ZeroTrust01 wants to merge 10 commits intonearai:mainfrom
Conversation
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances IronClaw's authentication flexibility by allowing it to utilize existing OpenAI Codex CLI OAuth tokens. This means users with an active ChatGPT subscription can now seamlessly integrate IronClaw without the need for additional API key setup, as IronClaw can directly interact with ChatGPT's private backend API. The change introduces new modules for credential management and a dedicated LLM provider for the ChatGPT Responses API, making the system more convenient for a broader user base. Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a great new feature to reuse Codex CLI's OAuth tokens, which simplifies setup for many users. The implementation is well-structured, with new modules for authentication and the custom LLM provider. I've found a couple of areas for improvement. There's a high-severity bug in the credential loading logic that could lead to using the wrong LLM provider, and a medium-severity performance issue related to reqwest::Client instantiation. My detailed comments include code suggestions to address these points. Overall, this is a solid contribution.
| if !is_chatgpt { | ||
| if let Some(key) = auth.openai_api_key.filter(|k| !k.is_empty()) { | ||
| tracing::info!("Loaded API key from Codex auth.json (API key mode)"); | ||
| return Some(CodexCredentials { | ||
| token: key, | ||
| is_chatgpt_mode: false, | ||
| }); | ||
| } | ||
| } |
There was a problem hiding this comment.
The current logic has a bug where if auth_mode is "apiKey" but the key is missing, it incorrectly falls through to check for a ChatGPT token. This can lead to returning credentials with is_chatgpt_mode: true, causing the wrong LLM provider to be used.
To fix this, we should explicitly prevent the fallback when auth_mode is set. If auth_mode is "apiKey" and no key is found, the function should return None.
if !is_chatgpt {
if let Some(key) = auth.openai_api_key.filter(|k| !k.is_empty()) {
tracing::info!("Loaded API key from Codex auth.json (API key mode)");
return Some(CodexCredentials {
token: key,
is_chatgpt_mode: false,
});
}
// If auth_mode was explicitly `apiKey`, do not fall back to checking for a token.
if auth.auth_mode.is_some() {
return None;
}
}
src/llm/codex_chatgpt.rs
Outdated
| async fn fetch_default_model(base_url: &str, api_key: &str) -> Option<String> { | ||
| let url = format!("{base_url}/models?client_version=0.1.0"); | ||
| let client = Client::new(); |
There was a problem hiding this comment.
Creating a new reqwest::Client for a single request is inefficient. The client manages a connection pool and is designed to be reused. This function is called from with_auto_model, which then calls Self::new, creating another client. This results in two clients being created to instantiate one provider.
To improve this, the client should be created once in with_auto_model, passed as an argument to this function, and then moved into the CodexChatGptProvider instance. This will require modifying with_auto_model as well.
| async fn fetch_default_model(base_url: &str, api_key: &str) -> Option<String> { | |
| let url = format!("{base_url}/models?client_version=0.1.0"); | |
| let client = Client::new(); | |
| async fn fetch_default_model(client: &Client, base_url: &str, api_key: &str) -> Option<String> { | |
| let url = format!("{base_url}/models?client_version=0.1.0"); |
References
- While the general rule advises against
reqwest::Clientreuse for one-time or infrequent operations with negligible benefit and high churn, this specific scenario involves creating two clients for a single provider instantiation, indicating a clear inefficiency where the benefit of reuse is not negligible.
When LLM_USE_CODEX_AUTH=true, IronClaw reads the Codex CLI's auth.json (default ~/.codex/auth.json) and extracts the API key or OAuth access token. This lets IronClaw piggyback on a Codex login without implementing its own OAuth flow. New env vars: - LLM_USE_CODEX_AUTH: enable Codex auth fallback (default: false) - CODEX_AUTH_PATH: override path to auth.json
Switch base_url to chatgpt.com/backend-api/codex when auth.json contains ChatGPT OAuth tokens. The access_token is a JWT that only works against the private ChatGPT backend, not the public OpenAI API. Refactored codex_auth.rs to return CodexCredentials (token + is_chatgpt_mode) instead of just a string key.
When LLM_USE_CODEX_AUTH=true, Codex credentials are now loaded before checking env vars or the secrets store overlay. Previously the secrets store key (injected during onboarding) would shadow the Codex token.
- New CodexChatGptProvider speaks the Responses API protocol - Auto-detects model from /models endpoint (gpt-4o -> gpt-5.2-codex) - Adds store=false (required by ChatGPT backend) - Error handling with timeout for HTTP 400 responses - Message format translation: Chat Completions -> Responses API - SSE response parsing for text, tool calls, and usage stats - 7 unit tests for message conversion and SSE parsing
The Responses API sends function_call_arguments.delta events with item_id (e.g. fc_...) not call_id (e.g. call_...). The parser now keys pending tool calls by item_id from output_item.added and tracks call_id separately for result matching.
gpt-5.2-codex fills optional tool parameters with empty strings (e.g. timestamp: ""), which IronClaw's tool validation rejects. Strip them before passing to tool execution.
When auth_mode is explicitly 'apiKey' but the key is missing/empty, do not fall through to check for a ChatGPT access_token. This prevents returning credentials with is_chatgpt_mode: true and routing to the wrong LLM provider.
… calls Create Client once in with_auto_model, pass &Client to fetch_default_model, and move it into the provider struct. Eliminates the redundant Client::new() that wasted a connection pool.
The /models endpoint gates newer models behind client_version. Version 0.1.0 only returns up to gpt-5.2-codex, while 1.0.0+ also returns gpt-5.3-codex and gpt-5.4.
Fetch the full model list from /models endpoint. If LLM_MODEL is set, validate it against the supported list and warn with available models if not found. If LLM_MODEL is not set, auto-detect the highest-priority model. Also bumps client_version to 1.0.0 to unlock gpt-5.3/5.4.
Summary
This PR enables IronClaw to reuse OAuth authentication tokens from the Codex CLI's
auth.jsonfile, allowing users with an active OpenAI/ChatGPT subscription to use IronClaw without needing a separate API key. When enabled, IronClaw communicates directly with ChatGPT's private Responses API backend (chatgpt.com/backend-api/codex) using the subscriber's JWT token.Motivation
Currently, IronClaw requires users to configure a separate LLM API key (e.g., from OpenAI Platform, Anthropic, or a third-party proxy). Users who already have a ChatGPT Plus/Pro/Team subscription and use the Codex CLI have valid OAuth tokens stored locally, but cannot leverage them with IronClaw. This PR bridges that gap, making it easy for ChatGPT subscribers to get started with IronClaw without additional API key setup.
Changes
New Files
src/codex_auth.rs— Reads and parses Codex CLI's~/.codex/auth.json, supporting bothapiKeyandchatgptauthentication modes. ReturnsCodexCredentialswith the token, mode flag, and correct base URL.src/llm/codex_chatgpt.rs— NewLlmProviderimplementation (CodexChatGptProvider) that speaks the OpenAI Responses API protocol (POST /responseswith SSE streaming), which is different from the standard Chat Completions API. Includes:/modelsendpoint (e.g.,gpt-5.2-codex)ChatMessageformat to Responses API input itemsstore: falseflag required by the ChatGPT backendModified Files
src/config/llm.rs— Addedis_codex_chatgptfield toRegistryProviderConfig. WhenLLM_USE_CODEX_AUTH=true, Codex credentials are loaded with highest priority (over env vars and secrets store). In ChatGPT mode, the base URL is automatically overridden to the private backend endpoint.src/llm/mod.rs— Addedmod codex_chatgptand dispatch logic increate_registry_providerto route to the new Responses API provider whenis_codex_chatgptis true.src/lib.rs— Registered thecodex_authmodule.Usage
The feature requires that the user has previously authenticated with the Codex CLI (
codex --login), which stores the OAuth token in~/.codex/auth.json.Compatibility
LLM_USE_CODEX_AUTH=true.LLM_USE_CODEX_AUTH=false(default), all existing LLM provider paths remain completely unchanged.is_codex_chatgptflag only affects the provider selection when Codex ChatGPT mode credentials are detected; all other provider protocols (OpenAI-compatible, Anthropic, Ollama) are unaffected.auth.json(apiKeymode) are also supported and route through the existing OpenAI-compatible provider.Testing
codex_chatgpt.rscovering:instructionsfieldstore: falseassertioncodex_auth.rscovering auth.json parsinggpt-5.2-codexmodelBackground
This PR builds on the observation made during #384 (
feat(setup): Anthropic OAuth onboarding), where Codex OAuth support was initially attempted but then intentionally removed with:This PR solves that exact problem by implementing a dedicated
CodexChatGptProviderthat speaks the Responses API protocol natively (POST /responseswith SSE streaming), rather than trying to use the Codex OAuth token as a standard OpenAI API key. This approach correctly handles the different wire format and endpoint.