Skip to content

Add codex oauth support#138

Open
shuv1337 wants to merge 7 commits intoMirrowel:devfrom
shuv1337:main
Open

Add codex oauth support#138
shuv1337 wants to merge 7 commits intoMirrowel:devfrom
shuv1337:main

Conversation

@shuv1337
Copy link

@shuv1337 shuv1337 commented Feb 17, 2026

Description

adds codex oauth multi-account support

Re-targeted from #137 (was against main, now against dev).

Testing Done

image image

Checklist

  • I have tested these changes locally
  • I have added license headers to new files (LGPL for library, MIT for proxy)
  • I have updated documentation (README/DOCUMENTATION.md) if needed
  • Related issue: #

Follow-up pushed (c3e244d)

Addressed review notes:

  • clarified that the Codex OAuth client ID is public (code + docs)
  • removed fire-and-forget reauth queueing in refresh failures (now awaited/tracked)
  • extracted shared Codex JWT helpers and reused them in auth + import paths
  • tightened quota parsing to avoid false positives from generic "quota" wording
  • refactored duplicate dedupe logic and added focused tests

Ran pytest -q (35 passed).

- Add _extract_explicit_email_from_payload to prefer email claim over sub
- Prefer id_token explicit email over access_token sub for metadata
- Harden _find_existing_credential_by_identity to require both email and
  account_id to match when both are available (prevents workspace collisions)
- Fall back to single-field matching only when one side is missing
- Add tests for identity matching edge cases and setup_credential flows
gpt-5.3-codex returns 400 for max_output_tokens parameter.
Omit it and let the API use its default.
@shuv1337 shuv1337 requested a review from Mirrowel as a code owner February 17, 2026 10:15
@mirrobot-agent
Copy link
Contributor

Starting my review of this comprehensive OpenAI Codex OAuth multi-account support PR. With 27 files changed and over 5,500 lines added, this is a substantial feature addition. I'll be diving into the auth base implementation, provider integration, credential manager updates, and the new test suite. Will report back with detailed findings shortly.

@Mirrowel Mirrowel added enhancement New feature or request Agent Monitored Monitored for AI Agent to review PR's and commits Priority labels Feb 17, 2026
Copy link
Contributor

@mirrobot-agent mirrobot-agent bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall Assessment

This is a comprehensive and well-structured PR that adds OpenAI Codex OAuth multi-account support to the LLM-API-Key-Proxy. The implementation follows existing patterns in the codebase and includes:

  • Complete OAuth Authorization Code + PKCE flow implementation
  • File-based and environment variable credential support
  • First-run import from Codex CLI credentials
  • SSE translation for streaming responses
  • Comprehensive test suite (994 lines of tests across 5 test files)
  • Updated documentation (README, DOCUMENTATION.md, .env.example)

The code quality is generally high with good error handling, logging, and follows the existing architecture. The PR demonstrates solid engineering practices with proper separation of concerns between auth base, provider, and credential management.

Key Strengths

  1. Protocol Implementation: The SSE event translation (CodexSSETranslator) is thorough and handles multiple event types with proper fallback chains
  2. Testing: Good test coverage with 379 lines dedicated to auth tests alone, including JWT decoding, expiry logic, and env loading
  3. Documentation: All user-facing documentation has been updated consistently
  4. Integration: Clean integration with existing credential tool, settings UI, and launcher TUI

Suggestions for Improvement

  1. Security: The hardcoded OAuth client ID app_EMoamEEZ73f0CkXaXp7hrann in openai_codex_auth_base.py should be confirmed as intentionally public (it's a public client ID by design for OAuth flows, but should be documented)

  2. Async Task Handling: The _refresh_token method creates fire-and-forget tasks with asyncio.create_task() for re-auth queueing. These tasks are not awaited or tracked, which could lead to unhandled exceptions being silently dropped.

  3. Resource Management: Consider reusing httpx.AsyncClient instances rather than creating new ones per refresh attempt for better performance.

  4. Code Duplication: JWT decoding logic is duplicated between credential_manager.py and openai_codex_auth_base.py - consider extracting to a shared utility.

  5. Error Parsing: The parse_quota_error method uses substring matching that could have false positives - consider more specific patterns.

  6. TODOs: Two TODOs remain regarding quota window tuning (lines 376, 382 in provider) - acceptable for initial implementation but should be tracked.

Minor Notes

  • Architecture: The nested async generators in acompletion could be flattened for better readability
  • The deduplication logic in import methods could be refactored to reduce duplication
  • Consider adding stress tests for queue management concurrency paths

This PR is ready for merge. The suggestions above are improvements rather than blockers. Great work on this substantial feature addition!

This review was generated by an AI assistant.

Remove non-existent gpt-4.1-codex and add all current models:
gpt-5.3-codex, gpt-5.3-codex-spark, gpt-5.2-codex, gpt-5.2,
gpt-5.1-codex-max, gpt-5.1-codex-mini
Copy link
Contributor

@mirrobot-agent mirrobot-agent bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up Review

Assessment of New Changes:

The model list update in openai_codex_provider.py looks good. Adding the newer GPT-5.x model variants (5.3, 5.2, 5.1 codex flavors) while removing the older gpt-4.1-codex keeps the hardcoded fallback list current and relevant.

Verification of Previous Feedback:

From my initial review, the following items were noted:

  1. ✅ Hardcoded OAuth client ID - confirmed as intentionally public
  2. ⚠️ Async task handling with fire-and-forget tasks - still applies
  3. ⚠️ httpx.AsyncClient reuse - still applies
  4. ⚠️ JWT decoding logic duplication - still applies
  5. ⚠️ Error parsing substring matching - still applies
  6. ⚠️ TODOs for quota window tuning - still present

These previous suggestions remain as potential improvements but don't block merging.

Overall Status:

This incremental update to the model list is clean and appropriate. The PR continues to be ready for merge.

This review was generated by an AI assistant.

Not generally available — requires special account access.
Can still be used via OPENAI_CODEX_MODELS env or dynamic discovery.
Copy link
Contributor

@mirrobot-agent mirrobot-agent bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up Review

Assessment of New Changes:

The removal of gpt-5.3-codex-spark from the hardcoded model list is a clean cleanup. This keeps the model list focused on actively supported variants.

Verification of Previous Feedback:

The following items from my initial review remain applicable:

  1. Async task handling with fire-and-forget tasks - still applies
  2. httpx.AsyncClient reuse opportunity - still applies
  3. JWT decoding logic duplication - still applies
  4. Error parsing substring matching - still applies
  5. TODOs for quota window tuning - still present

These are non-blocking improvement suggestions for future consideration.

Overall Status:

This incremental cleanup is appropriate. The PR remains ready for merge.

This review was generated by an AI assistant.

Copy link
Contributor

@mirrobot-agent mirrobot-agent bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up Review

Excellent work addressing the previous feedback! This follow-up commit demonstrates solid engineering practices and responsiveness to review comments.

Verification of Previous Feedback:

Item Status Notes
OAuth client ID documentation ✅ Fixed Added clear comments explaining it is public/non-secret in both code and docs
Fire-and-forget async tasks ✅ Fixed Now tracked with _background_tasks set and exceptions logged via _track_background_task()
JWT decoding duplication ✅ Fixed Extracted to new openai_codex_jwt.py shared utility module
Error parsing substring matching ✅ Fixed Replaced with precise regex patterns (INVALID_GRANT_PATTERN, rate limit patterns)
Deduplication logic ✅ Fixed Extracted _dedupe_openai_codex_records() to eliminate duplication
httpx.AsyncClient reuse ⚠️ Not addressed Still creates new clients per request (non-blocking)
TODOs for quota window tuning ⚠️ Still present Now have better comments referencing tracking doc (acceptable for MVP)

Assessment of New Changes:

The new openai_codex_jwt.py utility module is well-structured with clear documentation. The test additions (test_queue_refresh_deduplicates_under_concurrency, test_refresh_invalid_grant_queues_reauth_sync, test_parse_quota_error_does_not_match_generic_quota_substrings) provide good coverage for the fixed issues.

Overall Status:

This PR is in excellent shape and ready for merge. The addressed items significantly improve code quality and maintainability. The remaining open items are minor optimizations that don't block functionality.

This review was generated by an AI assistant.

Comment on lines +280 to +285
account_id = self._extract_account_id_from_payload(
access_payload
) or self._extract_account_id_from_payload(id_payload)

# Prefer explicit email claim from id_token first (most user-specific),
# then explicit access-token email, then fall back to sub-based extraction.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good improvement tracking background tasks with _background_tasks set and surfacing exceptions via _track_background_task(). This addresses the previous fire-and-forget concern. Consider also adding a cleanup method to cancel pending tasks on shutdown to prevent warnings about unfinished tasks.

@@ -0,0 +1,105 @@
# SPDX-License-Identifier: LGPL-3.0-only
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent extraction of JWT helpers into a shared utility module. The clear module docstring explaining the intentional lack of signature verification is appreciated. This eliminates the duplication between credential_manager.py and openai_codex_auth_base.py.

Comment on lines +298 to +302

# Keep top-level expiry_date synchronized from token exp as fallback
if not creds.get("expiry_date"):
expiry_ms = self._extract_expiry_ms_from_payload(access_payload) or self._extract_expiry_ms_from_payload(
id_payload
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The INVALID_GRANT_PATTERN regex is much more precise than the previous substring matching. Good fix for avoiding false positives.

Comment on lines +588 to +600

if isinstance(content, str):
return content

if isinstance(content, list):
parts: List[str] = []
for item in content:
if isinstance(item, dict):
# OpenAI chat content blocks
if item.get("type") == "text" and isinstance(item.get("text"), str):
parts.append(item["text"])
elif item.get("type") in {"input_text", "output_text"} and isinstance(
item.get("text"), str
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rate limit regex patterns are well-designed and the _looks_like_rate_limit() method properly avoids false positives from generic "quota" substrings (as verified by the new test). Clean implementation.

Comment on lines +150 to +177
access_payload = decode_jwt_unverified(access_token)
id_payload = decode_jwt_unverified(id_token) if id_token else None

account_id = extract_account_id_from_payload(access_payload) or extract_account_id_from_payload(
id_payload
)
email = extract_email_from_payload(id_payload) or extract_email_from_payload(access_payload)
exp_ms = extract_expiry_ms_from_payload(access_payload) or extract_expiry_ms_from_payload(
id_payload
)

return account_id, email, exp_ms

def _normalize_openai_codex_auth_json_record(self, auth_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Normalize ~/.codex/auth.json format to proxy schema."""
tokens = auth_data.get("tokens")
if not isinstance(tokens, dict):
return None

access_token = tokens.get("access_token")
refresh_token = tokens.get("refresh_token")
id_token = tokens.get("id_token")

if not isinstance(access_token, str) or not isinstance(refresh_token, str):
return None

account_id, email, exp_ms = self._extract_codex_identity(access_token, id_token)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice deduplication refactor extracting _dedupe_openai_codex_records(). This eliminates the duplicate dedupe logic between the two import paths.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Agent Monitored Monitored for AI Agent to review PR's and commits enhancement New feature or request Priority

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments