codex-claude-proxy 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,133 @@
1
+ # Architecture
2
+
3
+ ## Overview
4
+
5
+ ```
6
+ ┌──────────────────┐ ┌─────────────────────┐ ┌────────────────────────────┐
7
+ │ Claude Code │────▶│ This Proxy Server │────▶│ ChatGPT Backend API │
8
+ │ (Anthropic │ │ (Anthropic format) │ │ (chatgpt.com/backend-api/ │
9
+ │ API format) │ │ │ │ codex/responses) │
10
+ └──────────────────┘ └─────────────────────┘ └────────────────────────────┘
11
+
12
+
13
+ ┌─────────────────────┐
14
+ │ Account Manager │
15
+ │ (~/.codex-claude- │
16
+ │ proxy/accounts.json)
17
+ └─────────────────────┘
18
+ ```
19
+
20
+ ## Key Discovery
21
+
22
+ The endpoint `https://chatgpt.com/backend-api/codex/responses` bypasses Cloudflare protection when called with proper authentication headers.
23
+
24
+ ## Project Structure
25
+
26
+ ```
27
+ codex-claude-proxy/
28
+ ├── package.json
29
+ ├── README.md
30
+ ├── docs/
31
+ │ ├── ARCHITECTURE.md
32
+ │ ├── API.md
33
+ │ ├── OAUTH.md
34
+ │ ├── ACCOUNTS.md
35
+ │ └── CLAUDE_INTEGRATION.md
36
+ ├── public/
37
+ │ ├── index.html
38
+ │ ├── css/style.css
39
+ │ └── js/app.js # Alpine UI logic
40
+ └── src/
41
+ ├── index.js # Express server + routes
42
+ ├── oauth.js # OAuth 2.0 with PKCE
43
+ ├── account-manager.js # Multi-account storage
44
+ ├── format-converter.js # Anthropic ↔ OpenAI format
45
+ ├── response-streamer.js # SSE streaming
46
+ ├── direct-api.js # ChatGPT API client
47
+ ├── kilo-api.js # Alternate upstream client
48
+ ├── kilo-format-converter.js # Anthropic ↔ OpenAI Chat conversion
49
+ ├── kilo-streamer.js # Streaming adapter
50
+ ├── model-api.js # Models & quota
51
+ ├── server-settings.js # Server-wide settings persistence
52
+ └── claude-config.js # Claude CLI config
53
+ ```
54
+
55
+ ## Module Responsibilities
56
+
57
+ | File | Purpose |
58
+ |------|---------|
59
+ | `index.js` | Entry point (starts server) |
60
+ | `server.js` | Express server, routes, request handling |
61
+ | `routes/api-routes.js` | API route registrations (mounted by server) |
62
+ | `oauth.js` | OAuth 2.0 PKCE flow, token exchange |
63
+ | `account-manager.js` | Account persistence, switching, token refresh |
64
+ | `format-converter.js` | Convert between Anthropic and OpenAI Responses API formats |
65
+ | `response-streamer.js` | Parse SSE events, convert to Anthropic streaming format |
66
+ | `direct-api.js` | HTTP client for ChatGPT backend |
67
+ | `kilo-api.js` | Alternate upstream client |
68
+ | `kilo-format-converter.js` | Anthropic ↔ OpenAI Chat conversion |
69
+ | `kilo-streamer.js` | Streaming adapter |
70
+ | `server-settings.js` | Server-wide settings persistence |
71
+ | `model-api.js` | Fetch models, usage, quota |
72
+ | `claude-config.js` | Read/write `~/.claude/settings.json` |
73
+
74
+ ## Data Flow
75
+
76
+ ### Request Flow
77
+
78
+ 1. Claude Code sends Anthropic-format request to `/v1/messages`
79
+ 2. The proxy maps the requested model to an upstream target
80
+ 3. If the mapped path requires ChatGPT auth, the account manager loads/refreshes credentials
81
+ 4. Request is converted and sent upstream
82
+ 5. Response is streamed back as Anthropic SSE events
83
+
84
+ ### Web UI Account/Quota Flow
85
+
86
+ 1. Web UI loads account list from `/accounts`
87
+ 2. Web UI fetches quota snapshots from `/accounts/quota/all`
88
+ 3. Quota values are merged into account rows for table + modal views
89
+ 4. Remaining quota is rendered from normalized usage percentages
90
+ 5. On mobile/tablet, sidebar navigation auto-closes after tab change and account table uses horizontal scrolling
91
+
92
+ ### Format Conversion
93
+
94
+ **Anthropic → OpenAI Responses API:**
95
+ - `messages` → `input` array with `type: 'message'`
96
+ - `system` → `instructions`
97
+ - `tools` → OpenAI function format
98
+ - `tool_use` → `function_call` input item
99
+ - `tool_result` → `function_call_output` input item
100
+
101
+ **OpenAI → Anthropic:**
102
+ - `output_text` → `{ type: 'text', text: ... }`
103
+ - `function_call` → `{ type: 'tool_use', id, name, input }`
104
+ - SSE events converted to Anthropic streaming format
105
+
106
+ ## Available Models
107
+
108
+ | Model | Description |
109
+ |-------|-------------|
110
+ | `gpt-5.3-codex` | Latest agentic coding model |
111
+ | `gpt-5.2-codex` | Frontier agentic coding model |
112
+ | `gpt-5.2` | General-purpose frontier model |
113
+
114
+ ## Model Mapping
115
+
116
+ Claude model names are automatically mapped:
117
+
118
+ | Claude Model | Codex Model |
119
+ |--------------|-------------|
120
+ | `claude-opus-4-5` | `gpt-5.3-codex` |
121
+ | `claude-sonnet-4-5` | `gpt-5.2` |
122
+ | `claude-haiku-4` | routed by server setting |
123
+
124
+ Haiku routing is controlled by a server-wide setting (`/settings/haiku-model`).
125
+
126
+ ## Data Storage
127
+
128
+ | Location | Contents |
129
+ |----------|----------|
130
+ | `~/.codex-claude-proxy/accounts.json` | Account registry, active account |
131
+ | `~/.codex-claude-proxy/accounts/<email>/auth.json` | Per-account tokens |
132
+ | `~/.claude/settings.json` | Claude CLI config (we update this) |
133
+ | `~/.codex/auth.json` | Codex app auth (read for import) |
@@ -0,0 +1,163 @@
1
+ # Claude Code Integration
2
+
3
+ ## Setup
4
+
5
+ ### Automatic Configuration
6
+
7
+ ```bash
8
+ curl -X POST http://localhost:8081/claude/config/proxy
9
+ ```
10
+
11
+ Updates `~/.claude/settings.json`:
12
+
13
+ ```json
14
+ {
15
+ "env": {
16
+ "ANTHROPIC_BASE_URL": "http://localhost:8081",
17
+ "ANTHROPIC_API_KEY": "any-key",
18
+ "ANTHROPIC_MODEL": "claude-sonnet-4-5",
19
+ "ANTHROPIC_DEFAULT_OPUS_MODEL": "claude-opus-4-5",
20
+ "ANTHROPIC_DEFAULT_SONNET_MODEL": "claude-sonnet-4-5",
21
+ "ANTHROPIC_DEFAULT_HAIKU_MODEL": "claude-haiku-4"
22
+ }
23
+ }
24
+ ```
25
+
26
+ ### Manual Configuration
27
+
28
+ ```bash
29
+ export ANTHROPIC_BASE_URL=http://localhost:8081
30
+ export ANTHROPIC_API_KEY=any-key
31
+ claude
32
+ ```
33
+
34
+ ## Using Claude Code
35
+
36
+ When prompted about API key:
37
+
38
+ ```
39
+ Detected a custom API key in your environment
40
+ ANTHROPIC_API_KEY: any-key
41
+ Do you want to use this API key?
42
+ ❯ 1. Yes <-- Choose this
43
+ 2. No (recommended)
44
+ ```
45
+
46
+ ## How It Works
47
+
48
+ ### Request Flow
49
+
50
+ ```
51
+ Claude Code (Anthropic format)
52
+
53
+ Proxy Server
54
+
55
+ Format Conversion
56
+
57
+ ChatGPT Backend API
58
+
59
+ Response Stream
60
+
61
+ Format Conversion
62
+
63
+ Claude Code (Anthropic format)
64
+ ```
65
+
66
+ ### Format Conversion
67
+
68
+ **Anthropic → OpenAI Responses API:**
69
+
70
+ ```javascript
71
+ // Anthropic request
72
+ {
73
+ "model": "claude-sonnet-4-5",
74
+ "system": "You are helpful.",
75
+ "messages": [
76
+ {"role": "user", "content": "Hello"},
77
+ {"role": "assistant", "content": [{"type": "tool_use", "id": "t1", "name": "fn", "input": {}}]},
78
+ {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "t1", "content": "result"}]}
79
+ ],
80
+ "tools": [...]
81
+ }
82
+
83
+ // Converted to OpenAI Responses API
84
+ {
85
+ "model": "gpt-5.2-codex",
86
+ "instructions": "You are helpful.",
87
+ "input": [
88
+ {"type": "message", "role": "user", "content": "Hello"},
89
+ {"type": "function_call", "id": "fc_t1", "call_id": "fc_t1", "name": "fn", "arguments": "{}"},
90
+ {"type": "function_call_output", "call_id": "fc_t1", "output": "result"}
91
+ ],
92
+ "tools": [...],
93
+ "store": false,
94
+ "stream": true
95
+ }
96
+ ```
97
+
98
+ **Key Conversions:**
99
+ - `system` → `instructions`
100
+ - `messages` → `input` array
101
+ - `tool_use` → `function_call` + `function_call_output` items
102
+ - Tool IDs prefixed with `fc_` for API compatibility
103
+
104
+ ### Streaming Events
105
+
106
+ OpenAI Responses API → Anthropic SSE:
107
+
108
+ | OpenAI Event | Anthropic Event |
109
+ |--------------|-----------------|
110
+ | `response.output_item.added` | `message_start`, `content_block_start` |
111
+ | `response.output_text.delta` | `content_block_delta` (text_delta) |
112
+ | `response.function_call_arguments.delta` | `content_block_delta` (input_json_delta) |
113
+ | `response.completed` | `message_delta`, `message_stop` |
114
+
115
+ ## Tool Calling
116
+
117
+ Works natively via OpenAI Responses API:
118
+
119
+ 1. Claude Code sends tools in Anthropic format
120
+ 2. Proxy converts to OpenAI function format
121
+ 3. ChatGPT executes and returns function calls
122
+ 4. Proxy converts back to Anthropic `tool_use` blocks
123
+ 5. Claude Code processes and returns tool results
124
+
125
+ ## View Configuration
126
+
127
+ ```bash
128
+ curl http://localhost:8081/claude/config
129
+ ```
130
+
131
+ ## Revert to Direct API
132
+
133
+ ```bash
134
+ curl -X POST http://localhost:8081/claude/config/direct \
135
+ -H "Content-Type: application/json" \
136
+ -d '{"apiKey":"sk-ant-..."}'
137
+ ```
138
+
139
+ ## Troubleshooting
140
+
141
+ ### Claude Code hangs
142
+
143
+ 1. Check proxy health: `curl http://localhost:8081/health`
144
+ 2. Verify config: `cat ~/.claude/settings.json`
145
+ 3. Re-configure: `curl -X POST http://localhost:8081/claude/config/proxy`
146
+
147
+ ### "No active account" error
148
+
149
+ Add an account first:
150
+
151
+ ```bash
152
+ curl -X POST http://localhost:8081/accounts/import
153
+ # or use WebUI
154
+ ```
155
+
156
+ ### Tool calls not working
157
+
158
+ Ensure you're using the direct API mode (not CLI subprocess). Check:
159
+
160
+ ```bash
161
+ curl http://localhost:8081/health
162
+ # Should show accounts with valid tokens
163
+ ```
package/docs/OAUTH.md ADDED
@@ -0,0 +1,201 @@
1
+ # OAuth Implementation
2
+
3
+ ## Configuration
4
+
5
+ ```javascript
6
+ OAUTH_CONFIG = {
7
+ clientId: 'app_EMoamEEZ73f0CkXaXp7hrann',
8
+ authUrl: 'https://auth.openai.com/oauth/authorize',
9
+ tokenUrl: 'https://auth.openai.com/oauth/token',
10
+ scopes: ['openid', 'profile', 'email', 'offline_access'],
11
+ callbackPort: 1455,
12
+ callbackPath: '/auth/callback'
13
+ }
14
+ ```
15
+
16
+ ## Authorization Methods
17
+
18
+ ### Method A: WebUI (Recommended)
19
+
20
+ 1. Open `http://localhost:8081` in your browser
21
+ 2. Click **Add Account** → **Connect via OAuth**
22
+ 3. Complete authorization in the popup
23
+
24
+ ### Method B: WebUI Manual Mode (Headless)
25
+
26
+ For servers without a browser:
27
+
28
+ 1. Click **Add Account** → **Manual Authorization**
29
+ 2. Copy the OAuth URL
30
+ 3. Open URL on another device (your local machine)
31
+ 4. Complete authorization
32
+ 5. Copy the callback URL or authorization code
33
+ 6. Paste back in the WebUI
34
+
35
+ ### Method C: CLI (Desktop)
36
+
37
+ ```bash
38
+ npm run accounts:add
39
+ # Opens browser for OAuth
40
+ ```
41
+
42
+ ### Method D: CLI Headless Mode
43
+
44
+ ```bash
45
+ npm run accounts:add:headless
46
+ # Prints URL, you paste the callback URL/code
47
+ ```
48
+
49
+ ### Method E: API (Headless)
50
+
51
+ ```bash
52
+ # 1. Get OAuth URL and verifier
53
+ curl -X POST http://localhost:8081/accounts/add
54
+
55
+ # Response includes oauth_url, verifier, state
56
+
57
+ # 2. Open URL on another device, complete auth
58
+
59
+ # 3. Submit authorization code
60
+ curl -X POST http://localhost:8081/accounts/add/manual \
61
+ -H "Content-Type: application/json" \
62
+ -d '{"code":"<code_or_callback_url>","verifier":"<verifier_from_step_1>"}'
63
+ ```
64
+
65
+ ## PKCE Flow
66
+
67
+ ### 1. Generate PKCE Challenge
68
+
69
+ ```javascript
70
+ // Verifier: 32 random bytes, base64url encoded
71
+ verifier = crypto.randomBytes(32).toString('base64url')
72
+
73
+ // Challenge: SHA256 hash of verifier
74
+ challenge = sha256(verifier).base64url
75
+ ```
76
+
77
+ ### 2. Authorization URL
78
+
79
+ ```
80
+ https://auth.openai.com/oauth/authorize?
81
+ response_type=code
82
+ &client_id=app_EMoamEEZ73f0CkXaXp7hrann
83
+ &redirect_uri=http://localhost:1455/auth/callback
84
+ &scope=openid profile email offline_access
85
+ &code_challenge=<challenge>
86
+ &code_challenge_method=S256
87
+ &state=<random_state>
88
+ &prompt=login
89
+ &max_age=0
90
+ ```
91
+
92
+ **Key parameters for multi-account:**
93
+ - `prompt=login` - Forces login screen (ignores session cookies)
94
+ - `max_age=0` - Forces re-authentication
95
+
96
+ ### 3. Token Exchange
97
+
98
+ ```javascript
99
+ POST https://auth.openai.com/oauth/token
100
+ Content-Type: application/x-www-form-urlencoded
101
+
102
+ grant_type=authorization_code
103
+ &code=<authorization_code>
104
+ &redirect_uri=http://localhost:1455/auth/callback
105
+ &client_id=app_EMoamEEZ73f0CkXaXp7hrann
106
+ &code_verifier=<verifier>
107
+ ```
108
+
109
+ ### 4. Token Response
110
+
111
+ ```json
112
+ {
113
+ "access_token": "eyJhbGciOiJSUzI1NiIs...",
114
+ "refresh_token": "rt_WpTMn1...",
115
+ "id_token": "eyJhbGciOiJSUzI1NiIs...",
116
+ "expires_in": 3600,
117
+ "token_type": "Bearer"
118
+ }
119
+ ```
120
+
121
+ ## JWT Claims
122
+
123
+ Decoded `access_token` payload:
124
+
125
+ ```json
126
+ {
127
+ "https://api.openai.com/auth": {
128
+ "chatgpt_account_id": "d41e9636-...",
129
+ "chatgpt_plan_type": "plus",
130
+ "chatgpt_user_id": "user-..."
131
+ },
132
+ "https://api.openai.com/profile": {
133
+ "email": "user@gmail.com",
134
+ "email_verified": true
135
+ },
136
+ "exp": 1770886178
137
+ }
138
+ ```
139
+
140
+ ## Token Refresh
141
+
142
+ ```javascript
143
+ POST https://auth.openai.com/oauth/token
144
+ Content-Type: application/x-www-form-urlencoded
145
+
146
+ grant_type=refresh_token
147
+ &refresh_token=<refresh_token>
148
+ &client_id=app_EMoamEEZ73f0CkXaXp7hrann
149
+ ```
150
+
151
+ ## Auto-Refresh
152
+
153
+ - Tokens auto-refresh every **55 minutes**
154
+ - Proactive refresh before API calls if expiring within 5 minutes
155
+ - Startup refresh 2 seconds after server start
156
+
157
+ ## Web Flow vs CLI Flow
158
+
159
+ ### Web Flow (WebUI)
160
+
161
+ 1. `POST /accounts/add` → returns OAuth URL
162
+ 2. Frontend opens popup with URL
163
+ 3. User authenticates in popup
164
+ 4. Browser redirects to callback
165
+ 5. Server handles callback, stores tokens
166
+ 6. Popup notifies parent via `postMessage`
167
+
168
+ ### CLI Flow
169
+
170
+ 1. `POST /accounts/add` → starts callback server on port 1455
171
+ 2. Server opens browser with OAuth URL
172
+ 3. User authenticates
173
+ 4. Browser redirects to callback
174
+ 5. Server exchanges code for tokens
175
+
176
+ ## Multi-Account Support
177
+
178
+ The key to multi-account support is forcing a fresh login each time:
179
+
180
+ 1. `prompt=login` - Shows login screen even if already logged in
181
+ 2. `max_age=0` - Requires re-authentication
182
+ 3. Each account stored with unique email as identifier
183
+
184
+ ## Troubleshooting
185
+
186
+ ### OAuth returns existing account
187
+
188
+ - Browser may have aggressive cookie caching
189
+ - Try manual logout: https://auth.openai.com/logout
190
+ - Clear browser cookies for auth.openai.com
191
+
192
+ ### Callback timeout
193
+
194
+ - Default timeout: 2 minutes
195
+ - Ensure port 1455 is available
196
+ - Check firewall isn't blocking localhost
197
+
198
+ ### Token refresh fails
199
+
200
+ - Refresh token may have expired
201
+ - Re-add the account via WebUI