opencode-openai-multi-auth 5.0.0 → 5.0.2
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/README.md +153 -81
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +190 -8
- package/dist/index.js.map +1 -1
- package/dist/lib/accounts/manager.d.ts +18 -0
- package/dist/lib/accounts/manager.d.ts.map +1 -1
- package/dist/lib/accounts/manager.js +149 -16
- package/dist/lib/accounts/manager.js.map +1 -1
- package/dist/lib/accounts/types.d.ts +1 -0
- package/dist/lib/accounts/types.d.ts.map +1 -1
- package/dist/lib/accounts/types.js.map +1 -1
- package/dist/lib/auth/auth.d.ts.map +1 -1
- package/dist/lib/auth/auth.js +8 -0
- package/dist/lib/auth/auth.js.map +1 -1
- package/dist/lib/codex-status.d.ts +41 -0
- package/dist/lib/codex-status.d.ts.map +1 -0
- package/dist/lib/codex-status.js +349 -0
- package/dist/lib/codex-status.js.map +1 -0
- package/dist/lib/constants.d.ts +8 -0
- package/dist/lib/constants.d.ts.map +1 -1
- package/dist/lib/constants.js +13 -0
- package/dist/lib/constants.js.map +1 -1
- package/dist/lib/models.d.ts +42 -0
- package/dist/lib/models.d.ts.map +1 -0
- package/dist/lib/models.js +85 -0
- package/dist/lib/models.js.map +1 -0
- package/dist/lib/prompts/codex.d.ts.map +1 -1
- package/dist/lib/prompts/codex.js +10 -0
- package/dist/lib/prompts/codex.js.map +1 -1
- package/dist/lib/request/fetch-helpers.d.ts +2 -1
- package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
- package/dist/lib/request/fetch-helpers.js +32 -3
- package/dist/lib/request/fetch-helpers.js.map +1 -1
- package/dist/lib/request/helpers/model-map.d.ts.map +1 -1
- package/dist/lib/request/helpers/model-map.js +43 -11
- package/dist/lib/request/helpers/model-map.js.map +1 -1
- package/dist/lib/request/request-transformer.d.ts.map +1 -1
- package/dist/lib/request/request-transformer.js +32 -7
- package/dist/lib/request/request-transformer.js.map +1 -1
- package/dist/lib/types.d.ts +1 -0
- package/dist/lib/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,70 +1,155 @@
|
|
|
1
1
|

|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
[](https://x.com/zenysTX)
|
|
3
|
+
[](https://www.npmjs.com/package/opencode-openai-multi-auth)
|
|
4
|
+
[](https://github.com/dkraemerwork/opencode-openai-multi-auth/actions)
|
|
5
|
+
[](https://www.npmjs.com/package/opencode-openai-multi-auth)
|
|
7
6
|
|
|
7
|
+
# Multi-Account ChatGPT OAuth for OpenCode
|
|
8
8
|
|
|
9
|
+
**Use multiple ChatGPT Plus/Pro personal or organization accounts with OpenCode. Never hit rate limits again.**
|
|
9
10
|
|
|
11
|
+
```
|
|
12
|
+
┌────────────────────────────────────────────────────────────────┐
|
|
13
|
+
│ │
|
|
14
|
+
│ Account 1 (rate limited) ──┐ │
|
|
15
|
+
│ Account 2 (rate limited) ──┼──► Auto-rotate ──► Keep coding │
|
|
16
|
+
│ Account 3 (available) ─────┘ │
|
|
17
|
+
│ │
|
|
18
|
+
└────────────────────────────────────────────────────────────────┘
|
|
19
|
+
```
|
|
10
20
|
|
|
21
|
+
## Why Multi-Account?
|
|
11
22
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
23
|
+
| Problem | Solution |
|
|
24
|
+
|---------|----------|
|
|
25
|
+
| Hit ChatGPT rate limits while coding | Add multiple accounts, auto-rotate when limited |
|
|
26
|
+
| Team members share one subscription | Each person adds their own account |
|
|
27
|
+
| Different orgs have separate subscriptions | Use accounts from multiple organizations |
|
|
28
|
+
| One account gets throttled | Seamlessly switch to next available account |
|
|
18
29
|
|
|
19
30
|
---
|
|
20
31
|
|
|
21
|
-
##
|
|
32
|
+
## Quick Start
|
|
22
33
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
34
|
+
```bash
|
|
35
|
+
# Install
|
|
36
|
+
npx -y opencode-openai-multi-auth@latest
|
|
26
37
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
# Add your first account
|
|
39
|
+
opencode auth login
|
|
40
|
+
# Select "ChatGPT Plus/Pro (Codex Subscription)"
|
|
41
|
+
|
|
42
|
+
# Add more accounts (optional but recommended)
|
|
43
|
+
opencode auth login
|
|
44
|
+
# Select "Add Another OpenAI Account"
|
|
45
|
+
|
|
46
|
+
# Start coding - accounts rotate automatically on rate limits
|
|
47
|
+
opencode run "write hello world to test.txt" --model=openai/gpt-5.2 --variant=medium
|
|
34
48
|
```
|
|
35
49
|
|
|
36
50
|
---
|
|
37
51
|
|
|
38
|
-
##
|
|
52
|
+
## How Multi-Account Works
|
|
53
|
+
|
|
54
|
+
### Adding Accounts
|
|
39
55
|
|
|
40
56
|
```bash
|
|
41
|
-
|
|
57
|
+
# First account
|
|
58
|
+
opencode auth login
|
|
59
|
+
# → Select "ChatGPT Plus/Pro (Codex Subscription)"
|
|
60
|
+
# → Browser opens, login with ChatGPT
|
|
61
|
+
# → Account saved
|
|
62
|
+
|
|
63
|
+
# Second account (different email/org)
|
|
64
|
+
opencode auth login
|
|
65
|
+
# → Select "Add Another OpenAI Account"
|
|
66
|
+
# → Login with different ChatGPT account
|
|
67
|
+
# → Account added to rotation pool
|
|
68
|
+
|
|
69
|
+
# Repeat for as many accounts as you have
|
|
42
70
|
```
|
|
43
71
|
|
|
44
|
-
|
|
72
|
+
### Automatic Rotation
|
|
73
|
+
|
|
74
|
+
When you hit a rate limit:
|
|
75
|
+
|
|
76
|
+
1. Plugin detects 429 (rate limited) response
|
|
77
|
+
2. Marks current account as limited for that model
|
|
78
|
+
3. Switches to next available account
|
|
79
|
+
4. Retries your request automatically
|
|
80
|
+
5. Shows toast notification: `Switched to account2@example.com`
|
|
45
81
|
|
|
82
|
+
### Account Selection Strategies
|
|
83
|
+
|
|
84
|
+
| Strategy | Behavior | Best For |
|
|
85
|
+
|----------|----------|----------|
|
|
86
|
+
| `sticky` (default) | Stay with one account until rate limited | Single user, predictable usage |
|
|
87
|
+
| `round-robin` | Rotate through accounts on each request | Distribute load evenly |
|
|
88
|
+
| `hybrid` | Sticky within session, rotate across sessions | Multiple terminal sessions |
|
|
89
|
+
|
|
90
|
+
Set via environment variable:
|
|
46
91
|
```bash
|
|
47
|
-
opencode
|
|
48
|
-
opencode run "write hello world to test.txt" --model=openai/gpt-5.2 --variant=medium
|
|
92
|
+
OPENCODE_OPENAI_STRATEGY=round-robin opencode run "task"
|
|
49
93
|
```
|
|
50
94
|
|
|
51
|
-
|
|
95
|
+
### Team Usage
|
|
96
|
+
|
|
97
|
+
Each team member can add their own ChatGPT account:
|
|
52
98
|
|
|
53
99
|
```bash
|
|
54
|
-
|
|
55
|
-
opencode
|
|
100
|
+
# Developer 1 adds their account
|
|
101
|
+
opencode auth login # logs in as dev1@company.com
|
|
102
|
+
|
|
103
|
+
# Developer 2 adds their account
|
|
104
|
+
opencode auth login # → "Add Another OpenAI Account" → dev2@company.com
|
|
105
|
+
|
|
106
|
+
# Developer 3 adds their account
|
|
107
|
+
opencode auth login # → "Add Another OpenAI Account" → dev3@company.com
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
All accounts are pooled - when one person's account is rate limited, the plugin uses the next available.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Environment Variables
|
|
115
|
+
|
|
116
|
+
| Variable | Description | Default |
|
|
117
|
+
|----------|-------------|---------|
|
|
118
|
+
| `OPENCODE_OPENAI_QUIET=1` | Disable toast notifications | Off |
|
|
119
|
+
| `OPENCODE_OPENAI_DEBUG=1` | Enable debug logging | Off |
|
|
120
|
+
| `OPENCODE_OPENAI_STRATEGY` | Account selection strategy | `sticky` |
|
|
121
|
+
| `OPENCODE_OPENAI_PID_OFFSET=1` | Offset account selection by PID | Off |
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Account Management
|
|
126
|
+
|
|
127
|
+
### View Accounts
|
|
128
|
+
```bash
|
|
129
|
+
cat ~/.config/opencode/openai-accounts.json | jq '.accounts[] | {email, planType}'
|
|
56
130
|
```
|
|
57
131
|
|
|
58
|
-
|
|
132
|
+
### Remove All Accounts
|
|
133
|
+
```bash
|
|
134
|
+
rm ~/.config/opencode/openai-accounts.json
|
|
135
|
+
```
|
|
59
136
|
|
|
137
|
+
### Check Rate Limit Status
|
|
60
138
|
```bash
|
|
61
|
-
|
|
62
|
-
|
|
139
|
+
cat ~/.config/opencode/openai-accounts.json | jq '.accounts[] | {email, rateLimitResets}'
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Slash Commands (TUI)
|
|
143
|
+
```text
|
|
144
|
+
/codex-status
|
|
63
145
|
```
|
|
146
|
+
Shows usage status for all configured accounts.
|
|
64
147
|
|
|
65
148
|
---
|
|
66
149
|
|
|
67
|
-
##
|
|
150
|
+
## Models
|
|
151
|
+
|
|
152
|
+
All GPT-5.2 and GPT-5.1 models with reasoning variants:
|
|
68
153
|
|
|
69
154
|
- **gpt-5.2** (none/low/medium/high/xhigh)
|
|
70
155
|
- **gpt-5.2-codex** (low/medium/high/xhigh)
|
|
@@ -73,73 +158,60 @@ npx -y opencode-openai-multi-auth@latest --uninstall --all
|
|
|
73
158
|
- **gpt-5.1-codex-mini** (medium/high)
|
|
74
159
|
- **gpt-5.1** (none/low/medium/high)
|
|
75
160
|
|
|
161
|
+
Note: The model selector reflects what the ChatGPT OAuth backend advertises. API-only models (like gpt-5-mini/nano) may not appear until the backend exposes them.
|
|
162
|
+
|
|
76
163
|
---
|
|
77
164
|
|
|
78
|
-
##
|
|
165
|
+
## Configuration
|
|
79
166
|
|
|
80
|
-
- Modern (OpenCode v1.0.210+): `config/opencode-modern.json`
|
|
81
|
-
- Legacy (
|
|
167
|
+
- **Modern** (OpenCode v1.0.210+): `config/opencode-modern.json`
|
|
168
|
+
- **Legacy** (v1.0.209 and below): `config/opencode-legacy.json`
|
|
82
169
|
|
|
83
|
-
|
|
84
|
-
|
|
170
|
+
```bash
|
|
171
|
+
# Modern install
|
|
172
|
+
npx -y opencode-openai-multi-auth@latest
|
|
85
173
|
|
|
86
|
-
|
|
174
|
+
# Legacy install
|
|
175
|
+
npx -y opencode-openai-multi-auth@latest --legacy
|
|
87
176
|
|
|
88
|
-
|
|
89
|
-
-
|
|
90
|
-
|
|
91
|
-
- Variant system support (v1.0.210+) + legacy presets
|
|
92
|
-
- Multimodal input enabled for all models
|
|
93
|
-
- Toast notifications for account switches and rate limits
|
|
94
|
-
- Usage‑aware errors + automatic token refresh
|
|
177
|
+
# Uninstall
|
|
178
|
+
npx -y opencode-openai-multi-auth@latest --uninstall
|
|
179
|
+
```
|
|
95
180
|
|
|
96
181
|
---
|
|
97
182
|
|
|
98
|
-
##
|
|
183
|
+
## Features
|
|
99
184
|
|
|
100
|
-
Add
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
185
|
+
- **Multi-account rotation** - Add unlimited ChatGPT accounts, auto-rotate on rate limits
|
|
186
|
+
- **Per-model rate tracking** - Each model's limits tracked separately per account
|
|
187
|
+
- **Toast notifications** - Visual feedback when accounts switch
|
|
188
|
+
- **OAuth authentication** - Same secure flow as official Codex CLI
|
|
189
|
+
- **22 model presets** - All GPT-5.2/5.1 variants pre-configured
|
|
190
|
+
- **Automatic token refresh** - Never manually re-authenticate
|
|
191
|
+
- **Multimodal support** - Image input enabled for all models
|
|
106
192
|
|
|
107
|
-
|
|
108
|
-
opencode auth login
|
|
109
|
-
# Select "Add Another OpenAI Account"
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
**Features:**
|
|
113
|
-
- Automatic rotation when an account hits rate limits
|
|
114
|
-
- Per-model rate limit tracking
|
|
115
|
-
- Toast notifications showing active account
|
|
116
|
-
- Seamless failover between accounts
|
|
117
|
-
- Imports existing tokens from OpenCode auth
|
|
193
|
+
---
|
|
118
194
|
|
|
119
|
-
|
|
120
|
-
| Variable | Description |
|
|
121
|
-
|----------|-------------|
|
|
122
|
-
| `OPENCODE_OPENAI_QUIET=1` | Disable toast notifications |
|
|
123
|
-
| `OPENCODE_OPENAI_DEBUG=1` | Enable debug logging |
|
|
124
|
-
| `OPENCODE_OPENAI_STRATEGY` | Account selection: `sticky` (default), `round-robin`, `hybrid` |
|
|
195
|
+
## Documentation
|
|
125
196
|
|
|
126
|
-
|
|
197
|
+
- [Getting Started](docs/getting-started.md)
|
|
198
|
+
- [Configuration Guide](docs/configuration.md)
|
|
199
|
+
- [Troubleshooting](docs/troubleshooting.md)
|
|
200
|
+
- [Architecture](docs/development/ARCHITECTURE.md)
|
|
127
201
|
|
|
128
202
|
---
|
|
129
203
|
|
|
130
|
-
##
|
|
204
|
+
## Credits
|
|
131
205
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
206
|
+
**Maintained by [ZenysTX](https://x.com/zenysTX)**
|
|
207
|
+
**Original implementation by [Numman Ali](https://x.com/nummanali)**
|
|
208
|
+
**Inspired by [opencode-google-antigravity-auth](https://github.com/shekohex/opencode-google-antigravity-auth)**
|
|
209
|
+
|
|
210
|
+
[](https://x.com/zenysTX)
|
|
211
|
+
[](https://x.com/nummanali)
|
|
136
212
|
|
|
137
213
|
---
|
|
138
214
|
|
|
139
|
-
##
|
|
215
|
+
## Usage Notice
|
|
140
216
|
|
|
141
217
|
This plugin is for **personal development use** with your own ChatGPT Plus/Pro subscriptions.
|
|
142
|
-
|
|
143
|
-
**Built for developers who value simplicity.**
|
|
144
|
-
|
|
145
|
-
## Force Build 1
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,MAAM,EAAoB,MAAM,qBAAqB,CAAC;AAuD1E,eAAO,MAAM,gBAAgB,EAAE,MAonB9B,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
import { tool } from "@opencode-ai/plugin";
|
|
1
2
|
import { createAuthorizationFlow, decodeJWT, exchangeAuthorizationCode, parseAuthorizationInput, REDIRECT_URI, } from "./lib/auth/auth.js";
|
|
2
3
|
import { openBrowserUrl } from "./lib/auth/browser.js";
|
|
3
4
|
import { startLocalOAuthServer } from "./lib/auth/server.js";
|
|
4
5
|
import { getCodexMode, loadPluginConfig } from "./lib/config.js";
|
|
5
|
-
import { AUTH_LABELS, CODEX_BASE_URL, DUMMY_API_KEY, ERROR_MESSAGES, JWT_CLAIM_PATH, LOG_STAGES, PROVIDER_ID, HTTP_STATUS, } from "./lib/constants.js";
|
|
6
|
+
import { AUTH_LABELS, CODEX_BASE_URL, DUMMY_API_KEY, ERROR_MESSAGES, JWT_CLAIM_PATH, LOG_STAGES, PROVIDER_ID, HTTP_STATUS, MODEL_FALLBACKS, } from "./lib/constants.js";
|
|
6
7
|
import { logRequest, logDebug } from "./lib/logger.js";
|
|
7
8
|
import { createCodexHeaders, extractRequestUrl, handleErrorResponse, handleSuccessResponse, rewriteUrlForCodex, transformRequestForCodex, } from "./lib/request/fetch-helpers.js";
|
|
8
9
|
import { AccountManager } from "./lib/accounts/index.js";
|
|
10
|
+
import { codexStatus } from "./lib/codex-status.js";
|
|
11
|
+
import { prefetchModels } from "./lib/models.js";
|
|
9
12
|
function extractModelFromBody(body) {
|
|
10
13
|
if (!body)
|
|
11
14
|
return undefined;
|
|
@@ -20,6 +23,8 @@ function extractModelFromBody(body) {
|
|
|
20
23
|
let lastToastAccountIndex = null;
|
|
21
24
|
let lastToastTime = 0;
|
|
22
25
|
const TOAST_DEBOUNCE_MS = 5000;
|
|
26
|
+
/** Track which models we've already shown fallback notifications for */
|
|
27
|
+
const notifiedFallbacks = new Set();
|
|
23
28
|
export const OpenAIAuthPlugin = async ({ client }) => {
|
|
24
29
|
const quietMode = process.env.OPENCODE_OPENAI_QUIET === "1";
|
|
25
30
|
const debugMode = process.env.OPENCODE_OPENAI_DEBUG === "1";
|
|
@@ -81,6 +86,40 @@ export const OpenAIAuthPlugin = async ({ client }) => {
|
|
|
81
86
|
}
|
|
82
87
|
catch { }
|
|
83
88
|
};
|
|
89
|
+
const showModelRetryToast = async (model, failedAccount, nextAccount, triedCount, totalAccounts) => {
|
|
90
|
+
if (quietMode)
|
|
91
|
+
return;
|
|
92
|
+
const failedLabel = failedAccount.email || `Account ${failedAccount.index + 1}`;
|
|
93
|
+
const nextLabel = nextAccount.email || `Account ${nextAccount.index + 1}`;
|
|
94
|
+
const nextPlan = nextAccount.planType ? ` [${nextAccount.planType}]` : "";
|
|
95
|
+
try {
|
|
96
|
+
await client.tui.showToast({
|
|
97
|
+
body: {
|
|
98
|
+
message: `${model} not on ${failedLabel}, trying ${nextLabel}${nextPlan} (${triedCount}/${totalAccounts})`,
|
|
99
|
+
variant: "info",
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
catch { }
|
|
104
|
+
};
|
|
105
|
+
const showModelFallbackToast = async (originalModel, fallbackModel) => {
|
|
106
|
+
if (quietMode)
|
|
107
|
+
return;
|
|
108
|
+
// Only show once per model to avoid spam
|
|
109
|
+
const key = `${originalModel}->${fallbackModel}`;
|
|
110
|
+
if (notifiedFallbacks.has(key))
|
|
111
|
+
return;
|
|
112
|
+
notifiedFallbacks.add(key);
|
|
113
|
+
try {
|
|
114
|
+
await client.tui.showToast({
|
|
115
|
+
body: {
|
|
116
|
+
message: `${originalModel} not available yet. Using ${fallbackModel} instead.`,
|
|
117
|
+
variant: "warning",
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
catch { }
|
|
122
|
+
};
|
|
84
123
|
const accountManager = new AccountManager({
|
|
85
124
|
accountSelectionStrategy: process.env.OPENCODE_OPENAI_STRATEGY || "sticky",
|
|
86
125
|
debug: process.env.OPENCODE_OPENAI_DEBUG === "1",
|
|
@@ -149,13 +188,15 @@ export const OpenAIAuthPlugin = async ({ client }) => {
|
|
|
149
188
|
};
|
|
150
189
|
const pluginConfig = loadPluginConfig();
|
|
151
190
|
const codexMode = getCodexMode(pluginConfig);
|
|
152
|
-
const executeRequest = async (account, input, init, retryCount = 0) => {
|
|
191
|
+
const executeRequest = async (account, input, init, retryCount = 0, triedAccountIndices = new Set()) => {
|
|
192
|
+
// Track this account as tried
|
|
193
|
+
triedAccountIndices.add(account.index);
|
|
153
194
|
const isTokenValid = await accountManager.ensureValidToken(account);
|
|
154
195
|
if (!isTokenValid) {
|
|
155
|
-
const nextAccount = await accountManager.
|
|
196
|
+
const nextAccount = await accountManager.getNextAvailableAccountExcluding(triedAccountIndices);
|
|
156
197
|
if (nextAccount && nextAccount.index !== account.index) {
|
|
157
198
|
await showAccountSwitchToast(account, nextAccount);
|
|
158
|
-
return executeRequest(nextAccount, input, init, retryCount);
|
|
199
|
+
return executeRequest(nextAccount, input, init, retryCount, triedAccountIndices);
|
|
159
200
|
}
|
|
160
201
|
return new Response(JSON.stringify({ error: "All accounts failed token refresh" }), {
|
|
161
202
|
status: HTTP_STATUS.UNAUTHORIZED,
|
|
@@ -183,6 +224,14 @@ export const OpenAIAuthPlugin = async ({ client }) => {
|
|
|
183
224
|
headers: { "Content-Type": "application/json" },
|
|
184
225
|
});
|
|
185
226
|
}
|
|
227
|
+
// Pre-fetch models to "register" client with backend
|
|
228
|
+
// This may help unlock access to newer models like gpt-5.3-codex
|
|
229
|
+
try {
|
|
230
|
+
await prefetchModels(account.access || "", accountId);
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
// Ignore errors - this is a best-effort optimization
|
|
234
|
+
}
|
|
186
235
|
const headers = createCodexHeaders(requestInit, accountId, account.access || "", {
|
|
187
236
|
model: transformation?.body.model,
|
|
188
237
|
promptCacheKey: transformation?.body?.prompt_cache_key,
|
|
@@ -191,6 +240,18 @@ export const OpenAIAuthPlugin = async ({ client }) => {
|
|
|
191
240
|
...requestInit,
|
|
192
241
|
headers,
|
|
193
242
|
});
|
|
243
|
+
try {
|
|
244
|
+
const headersObj = {};
|
|
245
|
+
response.headers.forEach((value, key) => {
|
|
246
|
+
headersObj[key] = value;
|
|
247
|
+
});
|
|
248
|
+
await codexStatus.updateFromHeaders(account, headersObj);
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
if (debugMode) {
|
|
252
|
+
console.log("[openai-multi-auth] codex-status update failed", error);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
194
255
|
logRequest(LOG_STAGES.RESPONSE, {
|
|
195
256
|
status: response.status,
|
|
196
257
|
ok: response.ok,
|
|
@@ -235,19 +296,88 @@ export const OpenAIAuthPlugin = async ({ client }) => {
|
|
|
235
296
|
catch { }
|
|
236
297
|
}
|
|
237
298
|
if (retryCount < accountManager.getAccountCount() - 1) {
|
|
238
|
-
const nextAccount = await accountManager.
|
|
299
|
+
const nextAccount = await accountManager.getNextAvailableAccountExcluding(triedAccountIndices, model);
|
|
239
300
|
if (nextAccount && nextAccount.index !== account.index) {
|
|
240
301
|
await showAccountSwitchToast(account, nextAccount);
|
|
241
|
-
return executeRequest(nextAccount, input, init, retryCount + 1);
|
|
302
|
+
return executeRequest(nextAccount, input, init, retryCount + 1, triedAccountIndices);
|
|
242
303
|
}
|
|
243
304
|
}
|
|
244
305
|
}
|
|
245
306
|
if (response.status === HTTP_STATUS.UNAUTHORIZED && retryCount < 1) {
|
|
246
307
|
accountManager.markRefreshFailed(account, "401 Unauthorized");
|
|
247
|
-
const nextAccount = await accountManager.
|
|
308
|
+
const nextAccount = await accountManager.getNextAvailableAccountExcluding(triedAccountIndices, model);
|
|
248
309
|
if (nextAccount && nextAccount.index !== account.index) {
|
|
249
310
|
await showAccountSwitchToast(account, nextAccount);
|
|
250
|
-
return executeRequest(nextAccount, input, init, retryCount + 1);
|
|
311
|
+
return executeRequest(nextAccount, input, init, retryCount + 1, triedAccountIndices);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
// Handle model not supported errors (400 Bad Request with specific message)
|
|
315
|
+
if (response.status === 400) {
|
|
316
|
+
try {
|
|
317
|
+
const cloned = response.clone();
|
|
318
|
+
const errorBody = await cloned.json();
|
|
319
|
+
const detail = errorBody?.detail || errorBody?.error?.message || "";
|
|
320
|
+
// Always log 400 errors to file for debugging
|
|
321
|
+
const fs = await import("node:fs");
|
|
322
|
+
const path = await import("node:path");
|
|
323
|
+
const os = await import("node:os");
|
|
324
|
+
const logDir = path.join(os.homedir(), ".opencode", "logs", "codex-plugin");
|
|
325
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
326
|
+
fs.writeFileSync(path.join(logDir, "last-400-error.json"), JSON.stringify({
|
|
327
|
+
timestamp: new Date().toISOString(),
|
|
328
|
+
model: transformation?.body.model,
|
|
329
|
+
status: response.status,
|
|
330
|
+
errorBody,
|
|
331
|
+
detail,
|
|
332
|
+
accountIndex: account.index,
|
|
333
|
+
accountEmail: account.email,
|
|
334
|
+
accountPlanType: account.planType,
|
|
335
|
+
triedAccounts: Array.from(triedAccountIndices),
|
|
336
|
+
totalAccounts: accountManager.getAccountCount(),
|
|
337
|
+
}, null, 2));
|
|
338
|
+
// Log the error for debugging
|
|
339
|
+
if (debugMode) {
|
|
340
|
+
console.log(`[openai-multi-auth] 400 error for model ${transformation?.body.model} on account ${account.email || account.index} [${account.planType}]: ${JSON.stringify(errorBody)}`);
|
|
341
|
+
}
|
|
342
|
+
// Check if it's a "model not supported" error
|
|
343
|
+
if (detail.includes("model is not supported") || detail.includes("not supported when using Codex")) {
|
|
344
|
+
const requestedModel = transformation?.body.model || model;
|
|
345
|
+
// STEP 1: Try other accounts first (they might be Plus/Pro/Team and support the model)
|
|
346
|
+
const nextAccount = await accountManager.getNextAvailableAccountExcluding(triedAccountIndices, requestedModel);
|
|
347
|
+
if (nextAccount) {
|
|
348
|
+
if (debugMode) {
|
|
349
|
+
console.log(`[openai-multi-auth] Model ${requestedModel} not supported on ${account.email || account.index} [${account.planType}], trying ${nextAccount.email || nextAccount.index} [${nextAccount.planType}]`);
|
|
350
|
+
}
|
|
351
|
+
await showModelRetryToast(requestedModel, account, nextAccount, triedAccountIndices.size, accountManager.getAccountCount());
|
|
352
|
+
return executeRequest(nextAccount, input, init, retryCount, triedAccountIndices);
|
|
353
|
+
}
|
|
354
|
+
// STEP 2: All accounts tried - fall back to older model
|
|
355
|
+
const fallbackModel = MODEL_FALLBACKS[requestedModel];
|
|
356
|
+
if (fallbackModel) {
|
|
357
|
+
if (debugMode) {
|
|
358
|
+
console.log(`[openai-multi-auth] All ${triedAccountIndices.size} accounts tried for ${requestedModel}, falling back to ${fallbackModel}`);
|
|
359
|
+
}
|
|
360
|
+
await showModelFallbackToast(requestedModel, fallbackModel);
|
|
361
|
+
// Retry with fallback model using first available account (reset tried accounts for new model)
|
|
362
|
+
const modifiedBody = JSON.parse(init?.body || "{}");
|
|
363
|
+
modifiedBody.model = fallbackModel;
|
|
364
|
+
const modifiedInit = {
|
|
365
|
+
...init,
|
|
366
|
+
body: JSON.stringify(modifiedBody),
|
|
367
|
+
};
|
|
368
|
+
// Get first available account for the fallback model
|
|
369
|
+
const fallbackAccount = await accountManager.getNextAvailableAccount(fallbackModel);
|
|
370
|
+
if (fallbackAccount) {
|
|
371
|
+
// Reset tried accounts for the new model
|
|
372
|
+
return executeRequest(fallbackAccount, input, modifiedInit, 0, new Set());
|
|
373
|
+
}
|
|
374
|
+
// If no account available, use current account
|
|
375
|
+
return executeRequest(account, input, modifiedInit, retryCount + 1, new Set());
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
catch {
|
|
380
|
+
// If parsing fails, continue with normal error handling
|
|
251
381
|
}
|
|
252
382
|
}
|
|
253
383
|
if (!response.ok) {
|
|
@@ -315,6 +445,58 @@ export const OpenAIAuthPlugin = async ({ client }) => {
|
|
|
315
445
|
},
|
|
316
446
|
],
|
|
317
447
|
},
|
|
448
|
+
config: async (cfg) => {
|
|
449
|
+
cfg.command = cfg.command || {};
|
|
450
|
+
cfg.command["codex-status"] = {
|
|
451
|
+
template: "Run the codex-status tool and output the result EXACTLY as returned by the tool, without any additional text or commentary.",
|
|
452
|
+
description: "List all configured OpenAI accounts and their current usage status.",
|
|
453
|
+
};
|
|
454
|
+
cfg.experimental = cfg.experimental || {};
|
|
455
|
+
cfg.experimental.primary_tools = cfg.experimental.primary_tools || [];
|
|
456
|
+
if (!cfg.experimental.primary_tools.includes("codex-status")) {
|
|
457
|
+
cfg.experimental.primary_tools.push("codex-status");
|
|
458
|
+
}
|
|
459
|
+
},
|
|
460
|
+
tool: {
|
|
461
|
+
"codex-status": tool({
|
|
462
|
+
description: "List all configured OpenAI accounts and their current usage status.",
|
|
463
|
+
args: {},
|
|
464
|
+
async execute() {
|
|
465
|
+
const accounts = accountManager.getAllAccounts();
|
|
466
|
+
if (accounts.length === 0) {
|
|
467
|
+
return [
|
|
468
|
+
"OpenAI Codex Status",
|
|
469
|
+
"",
|
|
470
|
+
" Accounts: 0",
|
|
471
|
+
"",
|
|
472
|
+
"Add accounts:",
|
|
473
|
+
" opencode auth login",
|
|
474
|
+
].join("\n");
|
|
475
|
+
}
|
|
476
|
+
const now = Date.now();
|
|
477
|
+
await Promise.all(accounts.map(async (acc) => {
|
|
478
|
+
if (acc.access && acc.expires && acc.expires > now) {
|
|
479
|
+
await codexStatus.fetchFromBackend(acc, acc.access);
|
|
480
|
+
}
|
|
481
|
+
}));
|
|
482
|
+
const active = accountManager.getActiveAccount();
|
|
483
|
+
const activeIndex = active?.index ?? 0;
|
|
484
|
+
const lines = ["OpenAI Codex Status", ""];
|
|
485
|
+
for (const account of accounts) {
|
|
486
|
+
const status = account.index === activeIndex ? "ACTIVE" : "READY";
|
|
487
|
+
const email = account.email || `Account ${account.index + 1}`;
|
|
488
|
+
const plan = account.planType || "Unknown";
|
|
489
|
+
lines.push(`${account.index + 1}. ${status} ${email} [${plan}]`);
|
|
490
|
+
const statusLines = await codexStatus.renderStatus(account);
|
|
491
|
+
for (const line of statusLines) {
|
|
492
|
+
lines.push(line);
|
|
493
|
+
}
|
|
494
|
+
lines.push("");
|
|
495
|
+
}
|
|
496
|
+
return lines.join("\n");
|
|
497
|
+
},
|
|
498
|
+
}),
|
|
499
|
+
},
|
|
318
500
|
};
|
|
319
501
|
};
|
|
320
502
|
export default OpenAIAuthPlugin;
|