oc-codex-multi-auth 6.11.3 → 6.12.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 +9 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +75 -9
- package/dist/index.js.map +1 -1
- package/dist/lib/accounts/persistence.d.ts +21 -0
- package/dist/lib/accounts/persistence.d.ts.map +1 -1
- package/dist/lib/accounts/persistence.js +59 -1
- package/dist/lib/accounts/persistence.js.map +1 -1
- package/dist/lib/accounts/rotation.d.ts +39 -4
- package/dist/lib/accounts/rotation.d.ts.map +1 -1
- package/dist/lib/accounts/rotation.js +111 -17
- package/dist/lib/accounts/rotation.js.map +1 -1
- package/dist/lib/accounts.d.ts +9 -2
- package/dist/lib/accounts.d.ts.map +1 -1
- package/dist/lib/accounts.js +15 -6
- package/dist/lib/accounts.js.map +1 -1
- package/dist/lib/config.d.ts +6 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +48 -3
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/quota-windows.d.ts +59 -0
- package/dist/lib/quota-windows.d.ts.map +1 -0
- package/dist/lib/quota-windows.js +130 -0
- package/dist/lib/quota-windows.js.map +1 -0
- package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
- package/dist/lib/request/fetch-helpers.js +30 -5
- package/dist/lib/request/fetch-helpers.js.map +1 -1
- package/dist/lib/runtime.d.ts +2 -2
- package/dist/lib/runtime.d.ts.map +1 -1
- package/dist/lib/runtime.js.map +1 -1
- package/dist/lib/schemas.d.ts +4 -0
- package/dist/lib/schemas.d.ts.map +1 -1
- package/dist/lib/schemas.js +1 -0
- package/dist/lib/schemas.js.map +1 -1
- package/dist/lib/tools/codex-pool.d.ts.map +1 -1
- package/dist/lib/tools/codex-pool.js +35 -10
- package/dist/lib/tools/codex-pool.js.map +1 -1
- package/dist/lib/tui-quota-cache.d.ts.map +1 -1
- package/dist/lib/tui-quota-cache.js +5 -51
- package/dist/lib/tui-quota-cache.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -307,6 +307,10 @@ reordering accounts does not silently change a model's routing.
|
|
|
307
307
|
"gpt-5.6-terra": [
|
|
308
308
|
"org-another-account-id"
|
|
309
309
|
]
|
|
310
|
+
},
|
|
311
|
+
"modelAccountPoolModes": {
|
|
312
|
+
"gpt-5.6-sol": "strict",
|
|
313
|
+
"gpt-5.6-terra": "preferred"
|
|
310
314
|
}
|
|
311
315
|
}
|
|
312
316
|
```
|
|
@@ -323,6 +327,7 @@ codex-pool
|
|
|
323
327
|
codex-pool action="set" model="gpt-5.6-sol" accounts=[7,8]
|
|
324
328
|
codex-pool action="add" model="gpt-5.6-sol" accounts=[9]
|
|
325
329
|
codex-pool action="remove" model="gpt-5.6-sol" accounts=[7]
|
|
330
|
+
codex-pool action="set-mode" model="gpt-5.6-sol" poolMode="strict"
|
|
326
331
|
codex-pool action="clear" model="gpt-5.6-sol"
|
|
327
332
|
```
|
|
328
333
|
|
|
@@ -334,11 +339,12 @@ the current project is reported but never automatically deleted.
|
|
|
334
339
|
|
|
335
340
|
Routing behavior:
|
|
336
341
|
|
|
337
|
-
- A mapped model uses
|
|
342
|
+
- A mapped model defaults to `preferred` mode and uses healthy, selectable accounts in its pool.
|
|
338
343
|
- Existing rotation strategy, quota, cooldown, and token-health rules still apply within the preferred pool.
|
|
339
|
-
-
|
|
344
|
+
- In `preferred` mode, an unavailable pool falls back to the healthy general account pool.
|
|
345
|
+
- In `strict` mode, routing never leaves the configured pool and immediately returns `strict_pool_unavailable` when no pooled account is selectable.
|
|
340
346
|
- An unmapped model or an empty account list uses the general account pool directly.
|
|
341
|
-
- `codex-status`, `codex-dashboard`, and routing diagnostics report
|
|
347
|
+
- `codex-status`, `codex-dashboard`, and routing diagnostics also report `strict` and `strict-unavailable` modes.
|
|
342
348
|
|
|
343
349
|
Account IDs are local account metadata but should still be treated as private
|
|
344
350
|
configuration. Do not publish a populated configuration file.
|
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;AAwN/D;;;;;;;;;;;;;;;GAeG;AAEH,eAAO,MAAM,iBAAiB,EAAE,MA8oH/B,CAAC;AAEF,eAAO,MAAM,gBAAgB,QAAoB,CAAC;AAElD,eAAe,iBAAiB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -32,7 +32,7 @@ import { queuedRefresh } from "./lib/refresh-queue.js";
|
|
|
32
32
|
import { openBrowserUrl } from "./lib/auth/browser.js";
|
|
33
33
|
import { startLocalOAuthServer } from "./lib/auth/server.js";
|
|
34
34
|
import { promptAddAnotherAccount, promptLoginMode } from "./lib/cli.js";
|
|
35
|
-
import { getCodexMode, getRequestTransformMode, getFastSession, getFastSessionStrategy, getFastSessionMaxInputItems, getRetryProfile, getRetryBudgetOverrides, getRateLimitToastDebounceMs, getRetryAllAccountsMaxRetries, getRetryAllAccountsMaxWaitMs, getRetryAllAccountsRateLimited, getFallbackToGpt52OnUnsupportedGpt53, getUnsupportedCodexPolicy, getUnsupportedCodexFallbackChain, getTokenRefreshSkewMs, getSessionRecovery, getAutoResume, getAutoUpdate, getToastDurationMs, getAccountToastsEnabled, getPerProjectAccounts, getEmptyResponseMaxRetries, getEmptyResponseRetryDelayMs, getPidOffsetEnabled, getRotationStrategy, getModelAccountPool, getFetchTimeoutMs, getStreamStallTimeoutMs, getCodexTuiV2, getCodexTuiColorProfile, getCodexTuiGlyphMode, getBeginnerSafeMode, getCodexTuiMaskEmail, loadPluginConfig, } from "./lib/config.js";
|
|
35
|
+
import { getCodexMode, getRequestTransformMode, getFastSession, getFastSessionStrategy, getFastSessionMaxInputItems, getRetryProfile, getRetryBudgetOverrides, getRateLimitToastDebounceMs, getRetryAllAccountsMaxRetries, getRetryAllAccountsMaxWaitMs, getRetryAllAccountsRateLimited, getFallbackToGpt52OnUnsupportedGpt53, getUnsupportedCodexPolicy, getUnsupportedCodexFallbackChain, getTokenRefreshSkewMs, getSessionRecovery, getAutoResume, getAutoUpdate, getToastDurationMs, getAccountToastsEnabled, getPerProjectAccounts, getEmptyResponseMaxRetries, getEmptyResponseRetryDelayMs, getPidOffsetEnabled, getRotationStrategy, getModelAccountPool, getModelAccountPoolMode, getFetchTimeoutMs, getStreamStallTimeoutMs, getCodexTuiV2, getCodexTuiColorProfile, getCodexTuiGlyphMode, getBeginnerSafeMode, getCodexTuiMaskEmail, loadPluginConfig, } from "./lib/config.js";
|
|
36
36
|
import { AUTH_LABELS, CODEX_BASE_URL, DUMMY_API_KEY, LOG_STAGES, PLUGIN_NAME, PROVIDER_ID, ACCOUNT_LIMITS, } from "./lib/constants.js";
|
|
37
37
|
import { initLogger, logRequest, logDebug, logInfo, logWarn, logError, setCorrelationId, clearCorrelationId, } from "./lib/logger.js";
|
|
38
38
|
import { checkAndNotify } from "./lib/auto-update-checker.js";
|
|
@@ -59,6 +59,7 @@ import { createSessionRecoveryHook, isRecoverableError, detectErrorType, getReco
|
|
|
59
59
|
import { matchesWorkspaceIdentity, upsertFlaggedAccountRecord, createRetryBudgetUsage, serializeSelectionExplainability, formatRoutingValue, formatExplainabilitySummary, } from "./lib/runtime.js";
|
|
60
60
|
import { createToolRegistry, } from "./lib/tools/index.js";
|
|
61
61
|
import { createUsageAccountFingerprint, fetchCodexUsage, formatUsageLimitSummary, formatUsageLimitTitle, hasUsageWindow, parseCodexUsagePayload, } from "./lib/codex-usage.js";
|
|
62
|
+
import { getQuotaExhaustedResetAtMs } from "./lib/quota-windows.js";
|
|
62
63
|
import { clearTuiQuotaSnapshot, parseTuiQuotaSnapshotFromHeaders, writeTuiQuotaSnapshot, } from "./lib/tui-quota-cache.js";
|
|
63
64
|
/**
|
|
64
65
|
* OpenAI Codex OAuth authentication plugin for opencode
|
|
@@ -190,7 +191,10 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
190
191
|
lines.push(formatUiKeyValue(ui, "Fallback from", formatRoutingValue(routing.fallbackFrom), "muted"));
|
|
191
192
|
lines.push(formatUiKeyValue(ui, "Fallback to", formatRoutingValue(routing.fallbackTo), "muted"));
|
|
192
193
|
lines.push(formatUiKeyValue(ui, "Fallback reason", formatRoutingValue(routing.fallbackReason), routing.fallbackReason ? "warning" : "muted"));
|
|
193
|
-
lines.push(formatUiKeyValue(ui, "Account pool", formatRoutingValue(routing.accountPoolMode), routing.accountPoolMode === "general-fallback"
|
|
194
|
+
lines.push(formatUiKeyValue(ui, "Account pool", formatRoutingValue(routing.accountPoolMode), routing.accountPoolMode === "general-fallback" ||
|
|
195
|
+
routing.accountPoolMode === "strict-unavailable"
|
|
196
|
+
? "warning"
|
|
197
|
+
: "muted"));
|
|
194
198
|
lines.push(formatUiKeyValue(ui, "Configured pool size", String(routing.configuredAccountPoolSize), "muted"));
|
|
195
199
|
if (options.includeExplainability) {
|
|
196
200
|
lines.push("");
|
|
@@ -326,6 +330,32 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
326
330
|
logDebug(`[${PLUGIN_NAME}] Failed to record TUI quota headers: ${error instanceof Error ? error.message : String(error)}`);
|
|
327
331
|
}
|
|
328
332
|
};
|
|
333
|
+
/**
|
|
334
|
+
* Take an account out of rotation whenever the backend reports one of its
|
|
335
|
+
* quota windows as fully spent (issue #218).
|
|
336
|
+
*
|
|
337
|
+
* Every Codex response — success or 429 — carries `x-codex-*-used-percent`
|
|
338
|
+
* and the matching reset time, so the moment an account hits 0% left we
|
|
339
|
+
* can record it instead of rediscovering it with a failed request on every
|
|
340
|
+
* subsequent prompt. The block lands on the persisted `rateLimitResetTimes`
|
|
341
|
+
* map, so it is remembered across restarts and shared with other processes,
|
|
342
|
+
* and it clears itself once the window rolls over.
|
|
343
|
+
*/
|
|
344
|
+
const applyQuotaExhaustion = (manager, headers, account, family, model) => {
|
|
345
|
+
try {
|
|
346
|
+
const resetAtMs = getQuotaExhaustedResetAtMs(headers);
|
|
347
|
+
if (resetAtMs === undefined)
|
|
348
|
+
return;
|
|
349
|
+
if (!manager.markQuotaExhausted(account, resetAtMs, family, model))
|
|
350
|
+
return;
|
|
351
|
+
account.lastSwitchReason = "rate-limit";
|
|
352
|
+
manager.saveToDiskDebounced();
|
|
353
|
+
logWarn(`Account ${account.index + 1} (${account.email ?? "unknown"}) has no ${family} quota left; skipping it for ${formatWaitTime(resetAtMs - Date.now())}.`);
|
|
354
|
+
}
|
|
355
|
+
catch (error) {
|
|
356
|
+
logDebug(`[${PLUGIN_NAME}] Failed to apply quota exhaustion: ${error instanceof Error ? error.message : String(error)}`);
|
|
357
|
+
}
|
|
358
|
+
};
|
|
329
359
|
const resolveActiveIndex = (storage, family = "codex") => {
|
|
330
360
|
const total = storage.accounts.length;
|
|
331
361
|
if (total === 0)
|
|
@@ -1407,6 +1437,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1407
1437
|
let restartAccountTraversalWithFallback = false;
|
|
1408
1438
|
let restartAccountTraversalAfterWorkspaceDeactivation = false;
|
|
1409
1439
|
const preferredAccountIds = getModelAccountPool(pluginConfig, model);
|
|
1440
|
+
const accountPoolMode = getModelAccountPoolMode(pluginConfig, model);
|
|
1441
|
+
const strictAccountPool = preferredAccountIds.length > 0 && accountPoolMode === "strict";
|
|
1410
1442
|
while (attempted.size < Math.max(1, accountCount)) {
|
|
1411
1443
|
const selectionExplainability = accountManager.getSelectionExplainability(modelFamily, model, Date.now());
|
|
1412
1444
|
runtimeMetrics.lastSelectionSnapshot = {
|
|
@@ -1423,8 +1455,9 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1423
1455
|
fallbackTo,
|
|
1424
1456
|
fallbackReason,
|
|
1425
1457
|
configuredAccountPoolSize: preferredAccountIds.length,
|
|
1458
|
+
accountPoolMode: strictAccountPool ? "strict" : undefined,
|
|
1426
1459
|
};
|
|
1427
|
-
const account = accountManager.getAccountForStrategy(rotationStrategy, modelFamily, model, { pidOffsetEnabled }, preferredAccountIds);
|
|
1460
|
+
const account = accountManager.getAccountForStrategy(rotationStrategy, modelFamily, model, { pidOffsetEnabled }, preferredAccountIds, accountPoolMode, attempted);
|
|
1428
1461
|
if (!account || attempted.has(account.index)) {
|
|
1429
1462
|
break;
|
|
1430
1463
|
}
|
|
@@ -1442,12 +1475,14 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1442
1475
|
fallbackFrom,
|
|
1443
1476
|
fallbackTo,
|
|
1444
1477
|
fallbackReason,
|
|
1445
|
-
accountPoolMode:
|
|
1446
|
-
? "
|
|
1447
|
-
:
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1478
|
+
accountPoolMode: strictAccountPool
|
|
1479
|
+
? "strict"
|
|
1480
|
+
: preferredAccountIds.length === 0
|
|
1481
|
+
? "general"
|
|
1482
|
+
: account.accountId !== undefined &&
|
|
1483
|
+
preferredAccountIds.includes(account.accountId)
|
|
1484
|
+
? "preferred"
|
|
1485
|
+
: "general-fallback",
|
|
1451
1486
|
configuredAccountPoolSize: preferredAccountIds.length,
|
|
1452
1487
|
};
|
|
1453
1488
|
}
|
|
@@ -1683,6 +1718,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1683
1718
|
headers: Object.fromEntries(response.headers.entries()),
|
|
1684
1719
|
});
|
|
1685
1720
|
void recordPromptQuotaHeaders(response, account, accountCount);
|
|
1721
|
+
applyQuotaExhaustion(accountManager, response.headers, account, modelFamily, model);
|
|
1686
1722
|
if (!response.ok) {
|
|
1687
1723
|
const contextOverflowResult = await handleContextOverflow(response, model);
|
|
1688
1724
|
if (contextOverflowResult.handled) {
|
|
@@ -2104,6 +2140,36 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
2104
2140
|
if (restartAccountTraversalAfterWorkspaceDeactivation) {
|
|
2105
2141
|
continue;
|
|
2106
2142
|
}
|
|
2143
|
+
if (strictAccountPool) {
|
|
2144
|
+
const poolWaitMs = accountManager.getMinWaitTimeForFamily(modelFamily, model, preferredAccountIds);
|
|
2145
|
+
const effectiveModel = model ?? requestedModel ?? "requested model";
|
|
2146
|
+
const waitDetail = poolWaitMs > 0
|
|
2147
|
+
? ` Try again in ${formatWaitTime(poolWaitMs)}.`
|
|
2148
|
+
: " Check the pooled accounts with `codex-health`.";
|
|
2149
|
+
const message = `Strict account pool unavailable for ${effectiveModel}. ` +
|
|
2150
|
+
`None of its ${preferredAccountIds.length} configured account(s) is selectable.` +
|
|
2151
|
+
waitDetail;
|
|
2152
|
+
if (runtimeMetrics.lastSelectionSnapshot) {
|
|
2153
|
+
runtimeMetrics.lastSelectionSnapshot = {
|
|
2154
|
+
...runtimeMetrics.lastSelectionSnapshot,
|
|
2155
|
+
accountPoolMode: "strict-unavailable",
|
|
2156
|
+
};
|
|
2157
|
+
}
|
|
2158
|
+
runtimeMetrics.failedRequests++;
|
|
2159
|
+
runtimeMetrics.lastError = message;
|
|
2160
|
+
runtimeMetrics.lastErrorCategory = "strict-pool-unavailable";
|
|
2161
|
+
return new Response(JSON.stringify({
|
|
2162
|
+
error: {
|
|
2163
|
+
code: "strict_pool_unavailable",
|
|
2164
|
+
message,
|
|
2165
|
+
},
|
|
2166
|
+
}), {
|
|
2167
|
+
status: poolWaitMs > 0 ? 429 : 503,
|
|
2168
|
+
headers: {
|
|
2169
|
+
"content-type": "application/json; charset=utf-8",
|
|
2170
|
+
},
|
|
2171
|
+
});
|
|
2172
|
+
}
|
|
2107
2173
|
const waitMs = accountManager.getMinWaitTimeForFamily(modelFamily, model);
|
|
2108
2174
|
const count = accountManager.getAccountCount();
|
|
2109
2175
|
if (retryAllAccountsRateLimited &&
|