oc-codex-multi-auth 6.10.1 → 6.11.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/README.md +28 -10
- package/config/README.md +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -220
- package/dist/index.js.map +1 -1
- package/dist/lib/auto-update-checker.d.ts.map +1 -1
- package/dist/lib/auto-update-checker.js +3 -2
- package/dist/lib/auto-update-checker.js.map +1 -1
- package/dist/lib/codex-usage.d.ts +1 -0
- package/dist/lib/codex-usage.d.ts.map +1 -1
- package/dist/lib/codex-usage.js +17 -2
- package/dist/lib/codex-usage.js.map +1 -1
- package/package.json +7 -5
- package/scripts/install-oc-codex-multi-auth-core.js +195 -46
package/README.md
CHANGED
|
@@ -46,7 +46,7 @@ Use it when you want OpenCode to run Codex-style coding workflows from your own
|
|
|
46
46
|
|
|
47
47
|
| Surface | Purpose |
|
|
48
48
|
| --- | --- |
|
|
49
|
-
| `oc-codex-multi-auth` | npm
|
|
49
|
+
| `oc-codex-multi-auth` | npm CLI; explicit install modes manage OpenCode provider/TUI config, while `update` only clears the managed package cache. Also runs standalone commands: `doctor`, `status`, `list`, `limits`, `dashboard`, `health`, `diag`, `warm` |
|
|
50
50
|
| OpenCode plugin entry (`index.ts`) | auth loader, OAuth login modes, provider fetch pipeline, account rotation, retry/failover, and `codex-*` tool registry |
|
|
51
51
|
| OpenCode TUI plugin (`tui.ts`) | prompt quota status, quota details, shared quota cache, and active-account-aware display |
|
|
52
52
|
| 24 `codex-*` tools | setup, help, status, list, switch, warm, limits, health, metrics, doctor, dashboard, pool, backup, keychain, diagnostics, and recovery actions |
|
|
@@ -76,9 +76,9 @@ The plugin does not replace OpenCode. OpenCode remains the host; this package in
|
|
|
76
76
|
<details open>
|
|
77
77
|
<summary><b>For Humans</b></summary>
|
|
78
78
|
|
|
79
|
-
### Option A: Standard install (
|
|
79
|
+
### Option A: Standard install (preserve provider config)
|
|
80
80
|
|
|
81
|
-
Default mode
|
|
81
|
+
Default mode registers the OpenCode and TUI plugin entries without changing `provider.openai`.
|
|
82
82
|
|
|
83
83
|
```bash
|
|
84
84
|
npx -y oc-codex-multi-auth@latest
|
|
@@ -88,13 +88,22 @@ Installer flags:
|
|
|
88
88
|
|
|
89
89
|
| Flag | Effect |
|
|
90
90
|
| --- | --- |
|
|
91
|
-
| (default) / `--
|
|
91
|
+
| (default) / `--plugin-only` | Register the plugin and TUI integration without changing `provider.openai` |
|
|
92
|
+
| `--modern` | Install compact modern catalog: 12 bases, 53 variants |
|
|
92
93
|
| `--full` | Compact bases plus 53 explicit selector IDs |
|
|
93
94
|
| `--legacy` | Explicit-only catalog for older OpenCode |
|
|
94
|
-
| `--dry-run` | Show
|
|
95
|
+
| `--dry-run` | Show changed config paths without values or writes |
|
|
95
96
|
| `--no-cache-clear` | Skip clearing the OpenCode plugin cache |
|
|
96
97
|
|
|
97
|
-
### Option B:
|
|
98
|
+
### Option B: Compact modern model catalog
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
npx -y oc-codex-multi-auth@latest --modern
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Use this when OpenCode does not already provide the OAuth model definitions or you want the shipped variant presets.
|
|
105
|
+
|
|
106
|
+
### Option C: Full explicit model catalog
|
|
98
107
|
|
|
99
108
|
Use this when you want direct selector IDs such as `openai/gpt-5.5-medium` in addition to OpenCode variants.
|
|
100
109
|
|
|
@@ -102,7 +111,15 @@ Use this when you want direct selector IDs such as `openai/gpt-5.5-medium` in ad
|
|
|
102
111
|
npx -y oc-codex-multi-auth@latest --full
|
|
103
112
|
```
|
|
104
113
|
|
|
105
|
-
###
|
|
114
|
+
### Updating without config changes
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
npx -y oc-codex-multi-auth@latest update
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`update` clears only the OpenCode-managed package cache. It does not read or write `opencode.json` or `tui.json`; restart OpenCode afterward to install the current package.
|
|
121
|
+
|
|
122
|
+
### Option D: Verify wiring
|
|
106
123
|
|
|
107
124
|
```bash
|
|
108
125
|
opencode --version
|
|
@@ -110,7 +127,7 @@ opencode debug config
|
|
|
110
127
|
opencode auth login
|
|
111
128
|
```
|
|
112
129
|
|
|
113
|
-
The installer
|
|
130
|
+
The default installer only normalizes the plugin entry in `~/.config/opencode/opencode.json`, enables the TUI status plugin in `~/.config/opencode/tui.json`, and clears the cached plugin copy. Catalog modes additionally merge their selected `provider.openai` definitions. Changed config files are backed up before writing.
|
|
114
131
|
|
|
115
132
|
### Standalone CLI (no agent / no token cost)
|
|
116
133
|
|
|
@@ -133,13 +150,14 @@ oc-codex-multi-auth diag
|
|
|
133
150
|
|
|
134
151
|
### Step-by-step
|
|
135
152
|
|
|
136
|
-
1.
|
|
153
|
+
1. Register the plugin without changing `provider.openai`:
|
|
137
154
|
- `npx -y oc-codex-multi-auth@latest`
|
|
155
|
+
- Use `--modern` only when the shipped compact model catalog is required.
|
|
138
156
|
2. Run first login flow:
|
|
139
157
|
- `opencode auth login`
|
|
140
158
|
3. Validate config:
|
|
141
159
|
- `opencode debug config`
|
|
142
|
-
4. Run a smoke request (
|
|
160
|
+
4. Run a smoke request (after OpenCode or `--modern` supplies the selector):
|
|
143
161
|
- `opencode run "Explain this repository" --model=openai/gpt-5.5 --variant=medium`
|
|
144
162
|
5. Inspect plugin state with the OpenCode tool surface:
|
|
145
163
|
- `codex-status`
|
package/config/README.md
CHANGED
|
@@ -13,17 +13,19 @@ This directory contains the official OpenCode config templates for `oc-codex-mul
|
|
|
13
13
|
|
|
14
14
|
| Installer flag | What gets written |
|
|
15
15
|
|----------------|-------------------|
|
|
16
|
-
| default / `--
|
|
16
|
+
| default / `--plugin-only` | Register plugin entries; preserve `provider.openai` |
|
|
17
|
+
| `--modern` | Compact modern: 12 base OAuth families + variant picker |
|
|
17
18
|
| `--full` | Modern bases **plus** explicit legacy selector IDs |
|
|
18
19
|
| `--legacy` | Explicit-only catalog (53 preset model entries) |
|
|
19
20
|
|
|
20
21
|
```bash
|
|
21
|
-
npx -y oc-codex-multi-auth@latest #
|
|
22
|
+
npx -y oc-codex-multi-auth@latest # plugin entries only
|
|
23
|
+
npx -y oc-codex-multi-auth@latest --modern # compact modern catalog
|
|
22
24
|
npx -y oc-codex-multi-auth@latest --full # modern + explicit IDs
|
|
23
25
|
npx -y oc-codex-multi-auth@latest --legacy # explicit only
|
|
24
26
|
```
|
|
25
27
|
|
|
26
|
-
|
|
28
|
+
Run the installer with `--modern` to remove explicit preset IDs and stale base models left by earlier plugin catalogs.
|
|
27
29
|
|
|
28
30
|
## Quick pick
|
|
29
31
|
|
|
@@ -76,7 +78,7 @@ Shared template requirements:
|
|
|
76
78
|
- `gpt-5.4-mini` / `gpt-5.4-nano` / Codex models (`gpt-5-codex`, `gpt-5.1-codex*`, …): context **400,000**, output **128,000**
|
|
77
79
|
- `gpt-5.1`: context **272,000**, output **128,000**
|
|
78
80
|
|
|
79
|
-
Use `opencode debug config` to verify that these template entries were merged into your effective config.
|
|
81
|
+
Use `opencode debug config` to verify that these template entries were merged into your effective config. A `--modern` install shows base OAuth entries such as `gpt-5.5` / `gpt-5.6-sol`; the separate OpenCode variant picker exposes the reasoning presets. The default install writes no catalog, so these entries appear only if OpenCode supplies them itself.
|
|
80
82
|
|
|
81
83
|
If your OpenCode runtime supports global compaction tuning, you can also set values near the largest context windows (for example ~1M context / slightly lower auto-compact limit). Prefer values that match your selected model family.
|
|
82
84
|
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAKH,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAKH,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAC;AAqN/D;;;;;;;;;;;;;;;GAeG;AAEH,eAAO,MAAM,iBAAiB,EAAE,MA+hH/B,CAAC;AAEF,eAAO,MAAM,gBAAgB,QAAoB,CAAC;AAElD,eAAe,iBAAiB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -43,7 +43,7 @@ import { CodexAuthError } from "./lib/errors.js";
|
|
|
43
43
|
import { getStoragePath, loadAccounts, saveAccounts, withAccountStorageTransaction, clearAccounts, setStoragePath, loadFlaggedAccounts, saveFlaggedAccounts, withFlaggedAccountStorageTransaction, clearFlaggedAccounts, getWorkspaceIdentityKey, StorageError, formatStorageErrorHint, } from "./lib/storage.js";
|
|
44
44
|
import { createCodexHeaders, extractRequestUrl, handleErrorResponse, handleSuccessResponse, isDeactivatedWorkspaceError, isInvalidatedAuthTokenError, createAbortError, getUnsupportedCodexModelInfo, resolveUnsupportedCodexFallbackModel, refreshAndUpdateToken, rewriteUrlForCodex, shouldRefreshToken, transformRequestForCodex, } from "./lib/request/fetch-helpers.js";
|
|
45
45
|
import { shapeBodyForModel } from "./lib/request/helpers/responses-lite.js";
|
|
46
|
-
import {
|
|
46
|
+
import { DEACTIVATED_WORKSPACE_ERROR_CODE, isDeactivatedWorkspaceErrorMessage, isInvalidatedAuthTokenMessage, } from "./lib/error-sentinels.js";
|
|
47
47
|
import { applyFastSessionDefaults, clampReasoningForModel, upsertBackendModelIdentityMessage, } from "./lib/request/request-transformer.js";
|
|
48
48
|
import { getRateLimitBackoff, RATE_LIMIT_SHORT_RETRY_THRESHOLD_MS, resetRateLimitBackoff, } from "./lib/request/rate-limit-backoff.js";
|
|
49
49
|
import { isEmptyResponse } from "./lib/request/response-handler.js";
|
|
@@ -58,7 +58,7 @@ import { prewarmOpenCodeCodexPrompt } from "./lib/prompts/opencode-codex.js";
|
|
|
58
58
|
import { createSessionRecoveryHook, isRecoverableError, detectErrorType, getRecoveryToastContent, } from "./lib/recovery.js";
|
|
59
59
|
import { matchesWorkspaceIdentity, upsertFlaggedAccountRecord, createRetryBudgetUsage, serializeSelectionExplainability, formatRoutingValue, formatExplainabilitySummary, } from "./lib/runtime.js";
|
|
60
60
|
import { createToolRegistry, } from "./lib/tools/index.js";
|
|
61
|
-
import { createUsageAccountFingerprint } from "./lib/codex-usage.js";
|
|
61
|
+
import { createUsageAccountFingerprint, fetchCodexUsage, formatUsageLimitSummary, formatUsageLimitTitle, hasUsageWindow, parseCodexUsagePayload, } from "./lib/codex-usage.js";
|
|
62
62
|
import { clearTuiQuotaSnapshot, parseTuiQuotaSnapshotFromHeaders, writeTuiQuotaSnapshot, } from "./lib/tui-quota-cache.js";
|
|
63
63
|
/**
|
|
64
64
|
* OpenAI Codex OAuth authentication plugin for opencode
|
|
@@ -2187,226 +2187,26 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
2187
2187
|
message.includes("invalid refresh") ||
|
|
2188
2188
|
message.includes("token has been revoked"));
|
|
2189
2189
|
};
|
|
2190
|
-
const
|
|
2191
|
-
const
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
};
|
|
2197
|
-
const parseFiniteIntHeader = (headers, name) => {
|
|
2198
|
-
const raw = headers.get(name);
|
|
2199
|
-
if (!raw)
|
|
2200
|
-
return undefined;
|
|
2201
|
-
const parsed = Number.parseInt(raw, 10);
|
|
2202
|
-
return Number.isFinite(parsed) ? parsed : undefined;
|
|
2203
|
-
};
|
|
2204
|
-
const parseResetAtMs = (headers, prefix) => {
|
|
2205
|
-
const resetAfterSeconds = parseFiniteIntHeader(headers, `${prefix}-reset-after-seconds`);
|
|
2206
|
-
if (typeof resetAfterSeconds === "number" &&
|
|
2207
|
-
Number.isFinite(resetAfterSeconds) &&
|
|
2208
|
-
resetAfterSeconds > 0) {
|
|
2209
|
-
return Date.now() + resetAfterSeconds * 1000;
|
|
2190
|
+
const formatCodexQuotaLine = (usage) => {
|
|
2191
|
+
const parts = [];
|
|
2192
|
+
for (const window of [usage.primary, usage.secondary]) {
|
|
2193
|
+
if (!hasUsageWindow(window))
|
|
2194
|
+
continue;
|
|
2195
|
+
parts.push(`${formatUsageLimitTitle(window.windowMinutes)} ${formatUsageLimitSummary(window)}`);
|
|
2210
2196
|
}
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
return undefined;
|
|
2214
|
-
const trimmed = resetAtRaw.trim();
|
|
2215
|
-
if (/^\d+$/.test(trimmed)) {
|
|
2216
|
-
const parsedNumber = Number.parseInt(trimmed, 10);
|
|
2217
|
-
if (Number.isFinite(parsedNumber) && parsedNumber > 0) {
|
|
2218
|
-
// Upstream sometimes returns seconds since epoch.
|
|
2219
|
-
return parsedNumber < 10_000_000_000 ? parsedNumber * 1000 : parsedNumber;
|
|
2220
|
-
}
|
|
2197
|
+
if (hasUsageWindow(usage.codeReview)) {
|
|
2198
|
+
parts.push(`Code review ${formatUsageLimitSummary(usage.codeReview)}`);
|
|
2221
2199
|
}
|
|
2222
|
-
const
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
const hasCodexQuotaHeaders = (headers) => {
|
|
2226
|
-
const keys = [
|
|
2227
|
-
"x-codex-primary-used-percent",
|
|
2228
|
-
"x-codex-primary-window-minutes",
|
|
2229
|
-
"x-codex-primary-reset-at",
|
|
2230
|
-
"x-codex-primary-reset-after-seconds",
|
|
2231
|
-
"x-codex-secondary-used-percent",
|
|
2232
|
-
"x-codex-secondary-window-minutes",
|
|
2233
|
-
"x-codex-secondary-reset-at",
|
|
2234
|
-
"x-codex-secondary-reset-after-seconds",
|
|
2235
|
-
];
|
|
2236
|
-
return keys.some((key) => headers.get(key) !== null);
|
|
2237
|
-
};
|
|
2238
|
-
const parseCodexQuotaSnapshot = (headers, status) => {
|
|
2239
|
-
if (!hasCodexQuotaHeaders(headers))
|
|
2240
|
-
return null;
|
|
2241
|
-
const primaryPrefix = "x-codex-primary";
|
|
2242
|
-
const secondaryPrefix = "x-codex-secondary";
|
|
2243
|
-
const primary = {
|
|
2244
|
-
usedPercent: parseFiniteNumberHeader(headers, `${primaryPrefix}-used-percent`),
|
|
2245
|
-
windowMinutes: parseFiniteIntHeader(headers, `${primaryPrefix}-window-minutes`),
|
|
2246
|
-
resetAtMs: parseResetAtMs(headers, primaryPrefix),
|
|
2247
|
-
};
|
|
2248
|
-
const secondary = {
|
|
2249
|
-
usedPercent: parseFiniteNumberHeader(headers, `${secondaryPrefix}-used-percent`),
|
|
2250
|
-
windowMinutes: parseFiniteIntHeader(headers, `${secondaryPrefix}-window-minutes`),
|
|
2251
|
-
resetAtMs: parseResetAtMs(headers, secondaryPrefix),
|
|
2252
|
-
};
|
|
2253
|
-
const planTypeRaw = headers.get("x-codex-plan-type");
|
|
2254
|
-
const planType = planTypeRaw && planTypeRaw.trim() ? planTypeRaw.trim() : undefined;
|
|
2255
|
-
const activeLimit = parseFiniteIntHeader(headers, "x-codex-active-limit");
|
|
2256
|
-
return { status, planType, activeLimit, primary, secondary };
|
|
2257
|
-
};
|
|
2258
|
-
const formatQuotaWindowLabel = (windowMinutes) => {
|
|
2259
|
-
if (!windowMinutes || !Number.isFinite(windowMinutes) || windowMinutes <= 0) {
|
|
2260
|
-
return "quota";
|
|
2261
|
-
}
|
|
2262
|
-
if (windowMinutes % 1440 === 0)
|
|
2263
|
-
return `${windowMinutes / 1440}d`;
|
|
2264
|
-
if (windowMinutes % 60 === 0)
|
|
2265
|
-
return `${windowMinutes / 60}h`;
|
|
2266
|
-
return `${windowMinutes}m`;
|
|
2267
|
-
};
|
|
2268
|
-
const formatResetAt = (resetAtMs) => {
|
|
2269
|
-
if (!resetAtMs || !Number.isFinite(resetAtMs) || resetAtMs <= 0)
|
|
2270
|
-
return undefined;
|
|
2271
|
-
const date = new Date(resetAtMs);
|
|
2272
|
-
if (!Number.isFinite(date.getTime()))
|
|
2273
|
-
return undefined;
|
|
2274
|
-
const now = new Date();
|
|
2275
|
-
const sameDay = now.getFullYear() === date.getFullYear() &&
|
|
2276
|
-
now.getMonth() === date.getMonth() &&
|
|
2277
|
-
now.getDate() === date.getDate();
|
|
2278
|
-
const time = date.toLocaleTimeString(undefined, {
|
|
2279
|
-
hour: "2-digit",
|
|
2280
|
-
minute: "2-digit",
|
|
2281
|
-
hour12: false,
|
|
2282
|
-
});
|
|
2283
|
-
if (sameDay)
|
|
2284
|
-
return time;
|
|
2285
|
-
const day = date.toLocaleDateString(undefined, { month: "short", day: "2-digit" });
|
|
2286
|
-
return `${time} on ${day}`;
|
|
2287
|
-
};
|
|
2288
|
-
const formatCodexQuotaLine = (snapshot) => {
|
|
2289
|
-
const summarizeWindow = (label, window) => {
|
|
2290
|
-
const used = window.usedPercent;
|
|
2291
|
-
const left = typeof used === "number" && Number.isFinite(used)
|
|
2292
|
-
? Math.max(0, Math.min(100, Math.round(100 - used)))
|
|
2293
|
-
: undefined;
|
|
2294
|
-
const reset = formatResetAt(window.resetAtMs);
|
|
2295
|
-
let summary = label;
|
|
2296
|
-
if (left !== undefined)
|
|
2297
|
-
summary = `${summary} ${left}% left`;
|
|
2298
|
-
if (reset)
|
|
2299
|
-
summary = `${summary} (resets ${reset})`;
|
|
2300
|
-
return summary;
|
|
2301
|
-
};
|
|
2302
|
-
const primaryLabel = formatQuotaWindowLabel(snapshot.primary.windowMinutes);
|
|
2303
|
-
const secondaryLabel = formatQuotaWindowLabel(snapshot.secondary.windowMinutes);
|
|
2304
|
-
const parts = [
|
|
2305
|
-
summarizeWindow(primaryLabel, snapshot.primary),
|
|
2306
|
-
summarizeWindow(secondaryLabel, snapshot.secondary),
|
|
2307
|
-
];
|
|
2308
|
-
if (snapshot.planType)
|
|
2309
|
-
parts.push(`plan:${snapshot.planType}`);
|
|
2310
|
-
if (typeof snapshot.activeLimit === "number" && Number.isFinite(snapshot.activeLimit)) {
|
|
2311
|
-
parts.push(`active:${snapshot.activeLimit}`);
|
|
2312
|
-
}
|
|
2313
|
-
if (snapshot.status === 429)
|
|
2314
|
-
parts.push("rate-limited");
|
|
2315
|
-
return parts.join(", ");
|
|
2316
|
-
};
|
|
2317
|
-
const fetchCodexQuotaSnapshot = async (params) => {
|
|
2318
|
-
const QUOTA_PROBE_MODELS = ["gpt-5.4", "gpt-5-codex", "gpt-5.3-codex", "gpt-5.2-codex"];
|
|
2319
|
-
let lastError = null;
|
|
2320
|
-
for (const model of QUOTA_PROBE_MODELS) {
|
|
2321
|
-
try {
|
|
2322
|
-
const instructions = await getCodexInstructions(model);
|
|
2323
|
-
const probeBody = {
|
|
2324
|
-
model,
|
|
2325
|
-
stream: true,
|
|
2326
|
-
store: false,
|
|
2327
|
-
include: ["reasoning.encrypted_content"],
|
|
2328
|
-
instructions,
|
|
2329
|
-
input: [
|
|
2330
|
-
{
|
|
2331
|
-
type: "message",
|
|
2332
|
-
role: "user",
|
|
2333
|
-
content: [{ type: "input_text", text: "quota ping" }],
|
|
2334
|
-
},
|
|
2335
|
-
],
|
|
2336
|
-
reasoning: { effort: "none", summary: "auto" },
|
|
2337
|
-
text: { verbosity: "low" },
|
|
2338
|
-
};
|
|
2339
|
-
const headers = createCodexHeaders(undefined, params.accountId, params.accessToken, {
|
|
2340
|
-
model,
|
|
2341
|
-
organizationId: params.organizationId,
|
|
2342
|
-
});
|
|
2343
|
-
headers.set("content-type", "application/json");
|
|
2344
|
-
const controller = new AbortController();
|
|
2345
|
-
const timeout = setTimeout(() => controller.abort(), 15_000);
|
|
2346
|
-
let response;
|
|
2347
|
-
try {
|
|
2348
|
-
response = await fetch(`${CODEX_BASE_URL}/codex/responses`, {
|
|
2349
|
-
method: "POST",
|
|
2350
|
-
headers,
|
|
2351
|
-
body: JSON.stringify(probeBody),
|
|
2352
|
-
signal: controller.signal,
|
|
2353
|
-
});
|
|
2354
|
-
}
|
|
2355
|
-
finally {
|
|
2356
|
-
clearTimeout(timeout);
|
|
2357
|
-
}
|
|
2358
|
-
const snapshot = parseCodexQuotaSnapshot(response.headers, response.status);
|
|
2359
|
-
if (snapshot) {
|
|
2360
|
-
// We only need headers; cancel the SSE stream immediately.
|
|
2361
|
-
try {
|
|
2362
|
-
await response.body?.cancel();
|
|
2363
|
-
}
|
|
2364
|
-
catch {
|
|
2365
|
-
// Ignore cancellation failures.
|
|
2366
|
-
}
|
|
2367
|
-
return snapshot;
|
|
2368
|
-
}
|
|
2369
|
-
if (!response.ok) {
|
|
2370
|
-
const bodyText = await response.text().catch(() => "");
|
|
2371
|
-
let errorBody = undefined;
|
|
2372
|
-
try {
|
|
2373
|
-
errorBody = bodyText ? JSON.parse(bodyText) : undefined;
|
|
2374
|
-
}
|
|
2375
|
-
catch {
|
|
2376
|
-
errorBody = { error: { message: bodyText } };
|
|
2377
|
-
}
|
|
2378
|
-
const unsupportedInfo = getUnsupportedCodexModelInfo(errorBody);
|
|
2379
|
-
if (unsupportedInfo.isUnsupported) {
|
|
2380
|
-
lastError = new Error(unsupportedInfo.message ?? `Model '${model}' unsupported for this account`);
|
|
2381
|
-
continue;
|
|
2382
|
-
}
|
|
2383
|
-
const message = (typeof errorBody?.error?.message === "string"
|
|
2384
|
-
? errorBody.error?.message
|
|
2385
|
-
: bodyText) || `HTTP ${response.status}`;
|
|
2386
|
-
if (isDeactivatedWorkspaceError(errorBody, response.status)) {
|
|
2387
|
-
throw createDeactivatedWorkspaceError();
|
|
2388
|
-
}
|
|
2389
|
-
// A 401 here proves the token is invalid even when the body
|
|
2390
|
-
// carries only a generic "Unauthorized" string. The status is
|
|
2391
|
-
// dropped once we throw a plain Error, so normalize to the
|
|
2392
|
-
// canonical message the catch below matches — otherwise a
|
|
2393
|
-
// non-specific 401 would leave a dead routing slot unflagged
|
|
2394
|
-
// for codex-doctor --fix (issue #171).
|
|
2395
|
-
if (isInvalidatedAuthTokenError(errorBody, response.status)) {
|
|
2396
|
-
throw new Error("Your authentication token has been invalidated. Please try signing in again.");
|
|
2397
|
-
}
|
|
2398
|
-
throw new Error(message);
|
|
2399
|
-
}
|
|
2400
|
-
lastError = new Error("Codex response did not include quota headers");
|
|
2401
|
-
}
|
|
2402
|
-
catch (error) {
|
|
2403
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
2404
|
-
if (isDeactivatedWorkspaceErrorMessage(lastError.message)) {
|
|
2405
|
-
throw lastError;
|
|
2406
|
-
}
|
|
2200
|
+
for (const limit of usage.additionalLimits) {
|
|
2201
|
+
if (hasUsageWindow(limit.window)) {
|
|
2202
|
+
parts.push(`${limit.name} ${formatUsageLimitSummary(limit.window)}`);
|
|
2407
2203
|
}
|
|
2408
2204
|
}
|
|
2409
|
-
|
|
2205
|
+
if (usage.planType)
|
|
2206
|
+
parts.push(`plan:${usage.planType}`);
|
|
2207
|
+
if (usage.credits)
|
|
2208
|
+
parts.push(`credits:${usage.credits}`);
|
|
2209
|
+
return parts.length > 0 ? parts.join(", ") : "quota unavailable";
|
|
2410
2210
|
};
|
|
2411
2211
|
const runAccountCheck = async (deepProbe) => {
|
|
2412
2212
|
const loadedStorage = await hydrateEmails(await loadAccounts());
|
|
@@ -2575,13 +2375,15 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
2575
2375
|
if (!requestAccountId) {
|
|
2576
2376
|
throw new Error("Missing accountId for quota probe");
|
|
2577
2377
|
}
|
|
2578
|
-
const
|
|
2378
|
+
const payload = await fetchCodexUsage({
|
|
2579
2379
|
accountId: requestAccountId,
|
|
2580
2380
|
accessToken,
|
|
2581
2381
|
organizationId: account.organizationId,
|
|
2382
|
+
normalizeAccountErrors: true,
|
|
2582
2383
|
});
|
|
2384
|
+
const usage = parseCodexUsagePayload(payload);
|
|
2583
2385
|
ok += 1;
|
|
2584
|
-
console.log(`[${i + 1}/${total}] ${label}: ${formatCodexQuotaLine(
|
|
2386
|
+
console.log(`[${i + 1}/${total}] ${label}: ${formatCodexQuotaLine(usage)}`);
|
|
2585
2387
|
}
|
|
2586
2388
|
catch (error) {
|
|
2587
2389
|
errors += 1;
|