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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,206 @@
1
+ # Codex Claude Proxy
2
+
3
+ ![Architecture banner](./images/f757093f-507b-4453-994e-f8275f8b07a9.png)
4
+
5
+ A local proxy server that exposes an **Anthropic-compatible API** (Claude Messages) backed by the **ChatGPT Codex backend**.
6
+
7
+ It is designed primarily for **Claude Code CLI** (Anthropic-format client) while actually executing requests against Codex.
8
+
9
+ > This project is inspired by the structure of the Antigravity Claude Proxy README in `antigravity-claude-proxy-main/README.md`, but this proxy targets **ChatGPT Codex** and does **not** implement all Antigravity features.
10
+
11
+ ---
12
+
13
+ ## Disclaimer (read before use)
14
+
15
+ - **Not affiliated with OpenAI or Anthropic.** This is an independent open-source project and is not endorsed by, sponsored by, or affiliated with OpenAI or Anthropic.
16
+ - “ChatGPT”, “OpenAI”, and “Codex” are trademarks of their respective owners.
17
+ - “Claude” and “Anthropic” are trademarks of Anthropic PBC.
18
+ - Software is provided **“as is”**, without warranty. You are responsible for complying with all applicable Terms of Service and Acceptable Use Policies.
19
+
20
+ ### Legal
21
+
22
+ - **Not affiliated with OpenAI or Anthropic.** This is an independent open-source project and is not endorsed by, sponsored by, or affiliated with OpenAI or Anthropic.
23
+ - “ChatGPT”, “OpenAI”, and “Codex” are trademarks of their respective owners.
24
+ - “Claude” and “Anthropic” are trademarks of Anthropic PBC.
25
+ - Software is provided "as is", without warranty. You are responsible for complying with all applicable Terms of Service and Acceptable Use Policies.
26
+
27
+ ---
28
+
29
+ ## How it works
30
+
31
+ ```
32
+ ┌──────────────────┐ ┌─────────────────────┐ ┌────────────────────────────┐
33
+ │ Claude Code │────▶│ This Proxy Server │────▶│ ChatGPT Codex Backend API │
34
+ │ (Anthropic API) │ │ (Anthropic ⇄ OpenAI)│ │ (codex/responses) │
35
+ └──────────────────┘ └─────────────────────┘ └────────────────────────────┘
36
+ ```
37
+
38
+ At a high level:
39
+
40
+ 1. Claude Code calls this proxy using **Anthropic Messages API** endpoints (e.g. `/v1/messages`).
41
+ 2. The proxy maps the incoming “Claude model name” into a Codex model.
42
+ 3. The proxy converts formats as needed and returns responses back in Anthropic-compatible shapes, including streaming and tool calls.
43
+
44
+ ---
45
+
46
+ ## Model mapping (Claude name → Codex)
47
+
48
+ This proxy accepts Claude-style model IDs (what Claude Code expects) and maps them to Codex-backed models.
49
+
50
+ | Claude Code model (input) | Codex model used | Auth required | Notes |
51
+ |---|---|---:|---|
52
+ | `claude-sonnet-4-5` | **GPT-5.2 Codex** | Yes | Default “Sonnet” lane |
53
+ | `claude-opus-4-5` | **GPT-5.3 Codex** | Yes | Default “Opus” lane |
54
+ | `claude-haiku-4` | **GPT-5.2** (fast lane) | Yes | Lightweight / fast |
55
+
56
+ ---
57
+
58
+ ## Features
59
+
60
+ - **Anthropic-compatible** API surface (works with Claude Code)
61
+ - **OpenAI-compatible** endpoint (`/v1/chat/completions`) for quick testing and compatibility
62
+ - **Streaming (SSE)** for both Messages and logs
63
+ - **Native tool calling support** (proxy converts tool calls between formats)
64
+ - **Multi-account ChatGPT OAuth** (switch accounts, refresh tokens, import from Codex)
65
+ - **Web Dashboard** at `/`:
66
+ - manage accounts
67
+ - view quota snapshots
68
+ - quick test prompts
69
+ - live logs stream
70
+ - model mapping (Claude names → Codex models)
71
+
72
+ ---
73
+
74
+ ## Requirements
75
+
76
+ - Node.js **18+**
77
+ - A ChatGPT account authorized via OAuth
78
+
79
+ ---
80
+
81
+ ## Quick start
82
+
83
+ ```bash
84
+ npm install
85
+ npm start
86
+
87
+ # WebUI
88
+ open http://localhost:8081
89
+
90
+ # Health
91
+ curl http://localhost:8081/health
92
+ ```
93
+
94
+ Default ports:
95
+
96
+ | Port | Purpose |
97
+ |---:|---|
98
+ | `8081` | main server (API + WebUI) |
99
+ | `1455` | OAuth callback (temporary) |
100
+
101
+ ---
102
+
103
+ ## Authenticate (Codex / ChatGPT)
104
+
105
+ Codex-backed routes require at least one authenticated ChatGPT account.
106
+
107
+ ### Option A: Web Dashboard (recommended)
108
+
109
+ 1. Start the server
110
+ 2. Open `http://localhost:8081`
111
+ 3. Go to Accounts and add an account via OAuth (or use the Manual mode for headless environments)
112
+
113
+ ### Option B: CLI helpers
114
+
115
+ Desktop (opens browser):
116
+
117
+ ```bash
118
+ npm run accounts:add
119
+ ```
120
+
121
+ Headless / VM:
122
+
123
+ ```bash
124
+ npm run accounts:add:headless
125
+ # Prints URL, you paste callback URL/code
126
+ ```
127
+
128
+ ### Option C: Import from Codex app
129
+
130
+ ```bash
131
+ curl -X POST http://localhost:8081/accounts/import
132
+ # Imports from ~/.codex/auth.json
133
+ ```
134
+
135
+ Details: `docs/OAUTH.md` and `docs/ACCOUNTS.md`.
136
+
137
+ ---
138
+
139
+ ## Configure Claude Code to use the proxy
140
+
141
+ ### Automatic (recommended)
142
+
143
+ ```bash
144
+ curl -X POST http://localhost:8081/claude/config/proxy
145
+ ```
146
+
147
+ This updates `~/.claude/settings.json` to point Claude Code at `http://localhost:8081`.
148
+
149
+ ### Manual (env vars)
150
+
151
+ ```bash
152
+ export ANTHROPIC_BASE_URL=http://localhost:8081
153
+ export ANTHROPIC_API_KEY=any-key
154
+ claude
155
+ ```
156
+
157
+ More details: `docs/CLAUDE_INTEGRATION.md`.
158
+
159
+ ---
160
+
161
+ ## API surface (high level)
162
+
163
+ - Anthropic-compatible:
164
+ - `POST /v1/messages`
165
+ - `GET /v1/models`
166
+ - `POST /v1/messages/count_tokens`
167
+
168
+ - OpenAI-compatible:
169
+ - `POST /v1/chat/completions`
170
+
171
+ - Accounts:
172
+ - `GET /accounts`
173
+ - `POST /accounts/add`
174
+ - `POST /accounts/add/manual`
175
+ - `POST /accounts/switch`
176
+ - `POST /accounts/refresh`, `POST /accounts/refresh/all`
177
+
178
+ - Logs:
179
+ - `GET /api/logs`
180
+ - `GET /api/logs/stream?history=true`
181
+
182
+ Full reference: `docs/API.md`.
183
+
184
+ ---
185
+
186
+ ## Documentation
187
+
188
+ - `docs/ARCHITECTURE.md` — system overview and data flow
189
+ - `docs/API.md` — endpoints, formats, streaming
190
+ - `docs/OAUTH.md` — OAuth PKCE implementation + headless flow
191
+ - `docs/ACCOUNTS.md` — multi-account storage, switching, refresh, quota caching
192
+ - `docs/CLAUDE_INTEGRATION.md` — how to use with Claude Code
193
+ - `docs/OPENCLAW.md` — using the proxy behind OpenClaw
194
+
195
+ ---
196
+
197
+ ## Notes / limitations
198
+
199
+ - This project is intended for **local** or **trusted** environments.
200
+ - Some behavior differs from Antigravity proxy; this repo focuses on ChatGPT Codex routing.
201
+
202
+ ---
203
+
204
+ ## License
205
+
206
+ MIT
@@ -0,0 +1,202 @@
1
+ # Account Management
2
+
3
+ ## Storage Structure
4
+
5
+ ### Main Registry
6
+
7
+ **Location:** `~/.codex-claude-proxy/accounts.json`
8
+
9
+ ```json
10
+ {
11
+ "accounts": [
12
+ {
13
+ "email": "user@gmail.com",
14
+ "accountId": "d41e9636-16d8-42be-91da-7ea8773bfb7e",
15
+ "planType": "plus",
16
+ "accessToken": "eyJhbGciOiJSUzI1NiIs...",
17
+ "refreshToken": "rt_WpTMn1...",
18
+ "idToken": "eyJhbGciOiJSUzI1NiIs...",
19
+ "expiresAt": 1770886178000,
20
+ "addedAt": "2026-02-13T04:00:00.000Z",
21
+ "lastUsed": "2026-02-13T04:30:00.000Z",
22
+ "quota": {
23
+ "usage": {...},
24
+ "account": {...},
25
+ "lastChecked": "2026-02-14T10:00:00.000Z"
26
+ }
27
+ }
28
+ ],
29
+ "activeAccount": "user@gmail.com",
30
+ "version": 1
31
+ }
32
+ ```
33
+
34
+ ### Per-Account Tokens
35
+
36
+ **Location:** `~/.codex-claude-proxy/accounts/<email>/auth.json`
37
+
38
+ ```json
39
+ {
40
+ "auth_mode": "chatgpt",
41
+ "OPENAI_API_KEY": null,
42
+ "tokens": {
43
+ "id_token": "...",
44
+ "access_token": "...",
45
+ "refresh_token": "...",
46
+ "account_id": "..."
47
+ },
48
+ "last_refresh": "2026-02-14T10:00:00.000Z"
49
+ }
50
+ ```
51
+
52
+ ## Operations
53
+
54
+ ### Add Account (OAuth)
55
+
56
+ ```bash
57
+ curl -X POST http://localhost:8081/accounts/add
58
+
59
+ # Returns OAuth URL to open in browser
60
+ ```
61
+
62
+ ### Import from Codex App
63
+
64
+ ```bash
65
+ curl -X POST http://localhost:8081/accounts/import
66
+
67
+ # Imports from ~/.codex/auth.json
68
+ ```
69
+
70
+ ### List Accounts
71
+
72
+ ```bash
73
+ curl http://localhost:8081/accounts
74
+
75
+ # Response
76
+ {
77
+ "accounts": [
78
+ {
79
+ "email": "user@gmail.com",
80
+ "accountId": "...",
81
+ "planType": "plus",
82
+ "addedAt": "...",
83
+ "lastUsed": "...",
84
+ "isActive": true,
85
+ "tokenExpired": false,
86
+ "quota": {...}
87
+ }
88
+ ],
89
+ "activeAccount": "user@gmail.com",
90
+ "total": 1
91
+ }
92
+ ```
93
+
94
+ ### Switch Active Account
95
+
96
+ ```bash
97
+ curl -X POST http://localhost:8081/accounts/switch \
98
+ -H "Content-Type: application/json" \
99
+ -d '{"email":"other@gmail.com"}'
100
+ ```
101
+
102
+ Switching:
103
+ 1. Updates `activeAccount` in `accounts.json`
104
+ 2. Updates auth file for the account
105
+ 3. Next API calls use new account's credentials
106
+
107
+ ### Remove Account
108
+
109
+ ```bash
110
+ curl -X DELETE http://localhost:8081/accounts/user@gmail.com
111
+ ```
112
+
113
+ Removes:
114
+ - Account from registry
115
+ - Per-account token directory
116
+
117
+ ### Refresh Tokens
118
+
119
+ ```bash
120
+ # Active account
121
+ curl -X POST http://localhost:8081/accounts/refresh
122
+
123
+ # Specific account
124
+ curl -X POST http://localhost:8081/accounts/user@gmail.com/refresh
125
+
126
+ # All accounts
127
+ curl -X POST http://localhost:8081/accounts/refresh/all
128
+ ```
129
+
130
+ ## Token Lifecycle
131
+
132
+ ### Expiration
133
+
134
+ - Access tokens expire in ~1 hour (3600 seconds)
135
+ - Refresh tokens are long-lived (weeks/months)
136
+
137
+ ### Auto-Refresh
138
+
139
+ - Background refresh every **55 minutes**
140
+ - Startup refresh 2 seconds after server start
141
+ - Proactive refresh 5 minutes before expiry
142
+
143
+ ### Token Validation
144
+
145
+ Before each API call:
146
+ 1. Check if token is expired or expiring within 5 minutes
147
+ 2. If yes, refresh using refresh token
148
+ 3. Use new access token for the call
149
+
150
+ ## Quota Tracking
151
+
152
+ ### Fetch Quota
153
+
154
+ ```bash
155
+ curl http://localhost:8081/accounts/quota
156
+
157
+ # Response
158
+ {
159
+ "success": true,
160
+ "email": "user@gmail.com",
161
+ "quota": {
162
+ "usage": {
163
+ "totalTokenUsage": 15,
164
+ "limit": 100,
165
+ "remaining": 85,
166
+ "percentage": 15,
167
+ "resetAt": "..."
168
+ },
169
+ "account": {...}
170
+ },
171
+ "cached": false
172
+ }
173
+ ```
174
+
175
+ ### Web UI Quota Display Rules
176
+
177
+ - The Accounts table displays **remaining quota** as a percentage.
178
+ - Remaining percentage is normalized to `0-100` to avoid broken UI values.
179
+ - If `limitReached=true` or `allowed=false`, UI shows quota as exhausted even when percentage data is missing.
180
+ - If usage data is unavailable, UI shows `-` instead of rendering a broken bar.
181
+ - Reset window is shown using `usage.resetAt` (with fallback to `usage.raw.rate_limit.primary_window.reset_at`).
182
+ - UI also shows a relative countdown (e.g. `Resets in 6d 13h`) when reset data is available.
183
+
184
+ ### Refresh All Quotas
185
+
186
+ ```bash
187
+ curl http://localhost:8081/accounts/quota/all
188
+ ```
189
+
190
+ ## Account Persistence
191
+
192
+ On server startup:
193
+ 1. `ensureAccountsPersist()` loads accounts
194
+ 2. Restores active account's auth
195
+ 3. Starts auto-refresh timer
196
+
197
+ ## Security
198
+
199
+ - Tokens stored locally in `~/.codex-claude-proxy/`
200
+ - Directory permissions: user read/write only
201
+ - Never logged or exposed in API responses
202
+ - Per-account isolation via separate directories
package/docs/API.md ADDED
@@ -0,0 +1,274 @@
1
+ # API Reference
2
+
3
+ ## Main Endpoints
4
+
5
+ ### Haiku Routing Settings
6
+
7
+ ```bash
8
+ GET /settings/haiku-model
9
+
10
+ # Response
11
+ {
12
+ "success": true,
13
+ "haikuKiloModel": "glm-5"
14
+ }
15
+ ```
16
+
17
+ ```bash
18
+ POST /settings/haiku-model
19
+ Content-Type: application/json
20
+
21
+ {
22
+ "haikuKiloModel": "minimax-2.5"
23
+ }
24
+
25
+ # Response
26
+ {
27
+ "success": true,
28
+ "haikuKiloModel": "minimax-2.5"
29
+ }
30
+ ```
31
+
32
+ `claude-haiku-4` requests are routed according to this server-wide setting.
33
+
34
+ ### Chat Completions (OpenAI-compatible)
35
+
36
+ ```bash
37
+ POST /v1/chat/completions
38
+ Content-Type: application/json
39
+
40
+ {
41
+ "model": "gpt-5.2",
42
+ "messages": [{"role": "user", "content": "Hello"}],
43
+ "tools": [...],
44
+ "stream": true
45
+ }
46
+ ```
47
+
48
+ ### Messages (Anthropic-compatible)
49
+
50
+ ```bash
51
+ POST /v1/messages
52
+ Content-Type: application/json
53
+
54
+ {
55
+ "model": "claude-sonnet-4-5",
56
+ "max_tokens": 1024,
57
+ "system": "You are helpful.",
58
+ "messages": [{"role": "user", "content": "Hello"}],
59
+ "tools": [...],
60
+ "stream": true
61
+ }
62
+ ```
63
+
64
+ ### Models
65
+
66
+ ```bash
67
+ GET /v1/models
68
+ ```
69
+
70
+ ### Token Counting
71
+
72
+ ```bash
73
+ POST /v1/messages/count_tokens
74
+ Content-Type: application/json
75
+
76
+ {
77
+ "messages": [...],
78
+ "tools": [...]
79
+ }
80
+ ```
81
+
82
+ ## Account Management
83
+
84
+ | Endpoint | Method | Description |
85
+ |----------|--------|-------------|
86
+ | `/accounts` | GET | List all accounts |
87
+ | `/accounts/status` | GET | Get account status summary |
88
+ | `/accounts/add` | POST | Start OAuth flow (returns URL) |
89
+ | `/accounts/switch` | POST | Switch active account |
90
+ | `/accounts/:email` | DELETE | Remove account |
91
+ | `/accounts/refresh` | POST | Refresh active account token |
92
+ | `/accounts/refresh/all` | POST | Refresh all tokens |
93
+ | `/accounts/:email/refresh` | POST | Refresh specific account |
94
+ | `/accounts/import` | POST | Import from `~/.codex/auth.json` |
95
+ | `/accounts/models` | GET | Get models for account |
96
+ | `/accounts/quota` | GET | Get quota info |
97
+ | `/accounts/quota/all` | GET | Refresh all quotas |
98
+ | `/accounts/usage` | GET | Get usage stats |
99
+
100
+ ### Add Account
101
+
102
+ ```bash
103
+ POST /accounts/add
104
+ Content-Type: application/json
105
+
106
+ # Optional: specify callback port
107
+ {"port": 1455}
108
+
109
+ # Response
110
+ {
111
+ "status": "oauth_url",
112
+ "oauth_url": "https://auth.openai.com/oauth/authorize?...",
113
+ "state": "...",
114
+ "callback_port": 1455
115
+ }
116
+ ```
117
+
118
+ ### Switch Account
119
+
120
+ ```bash
121
+ POST /accounts/switch
122
+ Content-Type: application/json
123
+
124
+ {"email": "user@gmail.com"}
125
+
126
+ # Response
127
+ {"success": true, "message": "Switched to account: user@gmail.com"}
128
+ ```
129
+
130
+ ### OAuth Callback
131
+
132
+ ```bash
133
+ GET /auth/callback?code=...&state=...
134
+ ```
135
+
136
+ ## Claude CLI Configuration
137
+
138
+ | Endpoint | Method | Description |
139
+ |----------|--------|-------------|
140
+ | `/claude/config` | GET | View current config |
141
+ | `/claude/config/proxy` | POST | Configure for proxy |
142
+ | `/claude/config/direct` | POST | Configure for direct API |
143
+
144
+ ### Configure Proxy Mode
145
+
146
+ ```bash
147
+ POST /claude/config/proxy
148
+
149
+ # Response
150
+ {
151
+ "success": true,
152
+ "message": "Claude CLI configured to use proxy at http://localhost:8081",
153
+ "config": {...}
154
+ }
155
+ ```
156
+
157
+ ## Health
158
+
159
+ ```bash
160
+ GET /health
161
+
162
+ # Response
163
+ {
164
+ "status": "ok",
165
+ "total": 2,
166
+ "active": "user@gmail.com",
167
+ "accounts": [...]
168
+ }
169
+ ```
170
+
171
+ ## Error Responses
172
+
173
+ ### Authentication Error
174
+
175
+ ```json
176
+ {
177
+ "type": "error",
178
+ "error": {
179
+ "type": "authentication_error",
180
+ "message": "No active account with valid credentials"
181
+ }
182
+ }
183
+ ```
184
+
185
+ ### Rate Limit Error
186
+
187
+ ```json
188
+ {
189
+ "type": "error",
190
+ "error": {
191
+ "type": "rate_limit_error",
192
+ "message": "Rate limited: ..."
193
+ }
194
+ }
195
+ ```
196
+
197
+ ## Streaming Events
198
+
199
+ Anthropic SSE format:
200
+
201
+ ```
202
+ event: message_start
203
+ data: {"type":"message_start","message":{...}}
204
+
205
+ event: content_block_start
206
+ data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}
207
+
208
+ event: content_block_delta
209
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hello"}}
210
+
211
+ event: content_block_stop
212
+ data: {"type":"content_block_stop","index":0}
213
+
214
+ event: message_delta
215
+ data: {"type":"message_delta","delta":{"stop_reason":"end_turn"},"usage":{...}}
216
+
217
+ event: message_stop
218
+ data: {"type":"message_stop"}
219
+
220
+ data: [DONE]
221
+ ```
222
+
223
+ ## Tool Calling
224
+
225
+ ### Request with Tools
226
+
227
+ ```json
228
+ {
229
+ "model": "claude-sonnet-4-5",
230
+ "messages": [
231
+ {"role": "user", "content": "What's the weather in Tokyo?"}
232
+ ],
233
+ "tools": [{
234
+ "name": "get_weather",
235
+ "description": "Get weather for a location",
236
+ "input_schema": {
237
+ "type": "object",
238
+ "properties": {
239
+ "location": {"type": "string"}
240
+ },
241
+ "required": ["location"]
242
+ }
243
+ }]
244
+ }
245
+ ```
246
+
247
+ ### Response with Tool Use
248
+
249
+ ```json
250
+ {
251
+ "id": "msg_...",
252
+ "type": "message",
253
+ "role": "assistant",
254
+ "content": [{
255
+ "type": "tool_use",
256
+ "id": "toolu_...",
257
+ "name": "get_weather",
258
+ "input": {"location": "Tokyo"}
259
+ }],
260
+ "stop_reason": "tool_use"
261
+ }
262
+ ```
263
+
264
+ ### Tool Result
265
+
266
+ ```json
267
+ {
268
+ "messages": [
269
+ {"role": "user", "content": "What's the weather?"},
270
+ {"role": "assistant", "content": [{"type": "tool_use", "id": "toolu_123", "name": "get_weather", "input": {"location": "Tokyo"}}]},
271
+ {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "toolu_123", "content": "Sunny, 22°C"}]}
272
+ ]
273
+ }
274
+ ```