Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
fe2f269
add codex oauth gateway experiment
wanghuaiqing2010 Apr 27, 2026
376d64b
feat: bootstrap isolated python gateway migration workspace
wanghuaiqing2023-blip Apr 28, 2026
35e47fd
Merge pull request #1 from wanghuaiqing2023-blip/codex/analyze-projec…
wanghuaiqing2023-blip Apr 28, 2026
dd0e0da
feat(python-gateway): implement P0 auth flow and integration tests
wanghuaiqing2023-blip Apr 28, 2026
55ced6e
Resolve merge conflicts
wanghuaiqing2023-blip Apr 28, 2026
0132469
Merge pull request #2 from wanghuaiqing2023-blip/codex/analyze-projec…
wanghuaiqing2023-blip Apr 28, 2026
992afcc
fix(python-gateway): support Python 3.7 type annotations
wanghuaiqing2023-blip Apr 28, 2026
9c348a8
fix(python-gateway): make response annotations Python 3.7 compatible
wanghuaiqing2023-blip Apr 28, 2026
f2f06ba
feat(python-gateway): capture OAuth callback automatically in auth_cli
wanghuaiqing2023-blip Apr 28, 2026
66ee744
fix(python-gateway): inject default instructions for responses
wanghuaiqing2023-blip Apr 28, 2026
fbf5d57
Fix SSE final response reconstruction from text deltas
wanghuaiqing2023-blip Apr 28, 2026
f1e92a3
Improve SSE final response recovery from completed events
wanghuaiqing2023-blip Apr 28, 2026
ab5c3f7
Store gateway tokens under user home
wanghuaiqing2010 Apr 28, 2026
ddf3cb8
Merge remote-tracking branch 'origin/codex/analyze-project-progress-a…
wanghuaiqing2010 Apr 28, 2026
b4f60dd
fix test failed
wanghuaiqing2010 Apr 28, 2026
456e8d4
add test docs
wanghuaiqing2010 Apr 28, 2026
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
4 changes: 4 additions & 0 deletions codex-oauth-gateway-python/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
__pycache__/
*.pyc
.venv/
.tokens/
34 changes: 34 additions & 0 deletions codex-oauth-gateway-python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# codex-oauth-gateway-python

This directory is an **isolated Python migration workspace** for `experiments/codex-oauth-gateway`.

## Scope
- Build a Python implementation of the gateway incrementally.
- Keep `experiments/codex-oauth-gateway` unchanged during migration.

## Current status (v0 scaffold)
- Basic Python HTTP server with `GET /health` and `POST /responses`.
- Model normalization and SSE final-event parsing helpers.
- Structured gateway errors (`status` + `code`).
- Minimal unit tests for normalization/response helpers.

## Run locally
```bash
cd codex-oauth-gateway-python
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
PYTHONPATH=. python -m unittest discover -s tests
python auth_cli.py
python main.py
```

Then check:
```bash
curl -s http://127.0.0.1:8787/health
```

## Notes
- This is migration work-in-progress and not feature-parity yet.
- OAuth token refresh flow is now wired (refresh token grant).
- OAuth tokens are stored at `~/.codex-oauth-gateway-python/openai.json` by default. Set `CODEX_GATEWAY_TOKEN_FILE` to override this path.
87 changes: 87 additions & 0 deletions codex-oauth-gateway-python/auth_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from __future__ import annotations

import threading
import webbrowser
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import parse_qs, urlparse

from gateway.auth import (
create_authorization_flow,
exchange_authorization_code,
parse_authorization_input,
save_tokens,
)


def _wait_for_callback(expected_state: str, timeout_seconds: int = 180) -> tuple[str | None, str | None]:
result = {"code": None, "state": None}
done = threading.Event()

class CallbackHandler(BaseHTTPRequestHandler):
def do_GET(self): # noqa: N802
parsed = urlparse(self.path)
if parsed.path != "/auth/callback":
self.send_response(404)
self.end_headers()
return

query = parse_qs(parsed.query)
result["code"] = query.get("code", [None])[0]
result["state"] = query.get("state", [None])[0]
done.set()

self.send_response(200)
self.send_header("content-type", "text/plain; charset=utf-8")
self.end_headers()
self.wfile.write(b"OAuth callback received. You can return to terminal.")

def log_message(self, format, *args): # noqa: A003
return

try:
server = ThreadingHTTPServer(("127.0.0.1", 1455), CallbackHandler)
except OSError:
return None, None
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
try:
done.wait(timeout_seconds)
return result["code"], result["state"]
finally:
server.shutdown()
server.server_close()
thread.join(timeout=1)


def main() -> int:
flow = create_authorization_flow()
print("Open this URL in your browser and complete login (trying auto-open):\n")
print(flow.url)
try:
webbrowser.open(flow.url)
except Exception:
pass

print("\nWaiting for callback on http://localhost:1455/auth/callback ...")
code, state = _wait_for_callback(flow.state)

if not code:
print("No callback captured. Paste callback URL, 'code=...&state=...', or 'code#state':")
raw = input("> ")
code, state = parse_authorization_input(raw)

if not code:
print("No authorization code found.")
return 1
if state and state != flow.state:
print("State mismatch. Aborting.")
return 1

tokens = exchange_authorization_code(code, flow.verifier)
save_tokens(tokens)
print("OAuth tokens saved successfully.")
return 0


if __name__ == "__main__":
raise SystemExit(main())
Loading