codex-multi-auth 2.21.0 → 2.23.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/.codex-plugin/plugin.json +1 -1
- package/README.md +8 -5
- package/config/schema/config.schema.json +8 -0
- package/dist/lib/accounts.d.ts +5 -0
- package/dist/lib/accounts.js +33 -4
- package/dist/lib/api-route-store.d.ts +7 -0
- package/dist/lib/api-route-store.js +100 -31
- package/dist/lib/auth/account-access.js +2 -1
- package/dist/lib/auth/auth.js +3 -2
- package/dist/lib/auth/device-auth.js +2 -1
- package/dist/lib/auth/http.d.ts +1 -0
- package/dist/lib/auth/http.js +1 -0
- package/dist/lib/codex-manager/account-manager-commands.js +1 -0
- package/dist/lib/codex-manager/api-login-menu.d.ts +1 -1
- package/dist/lib/codex-manager/api-login-menu.js +4 -6
- package/dist/lib/codex-manager/commands/locale.d.ts +12 -0
- package/dist/lib/codex-manager/commands/locale.js +25 -0
- package/dist/lib/codex-manager/commands/status.d.ts +5 -1
- package/dist/lib/codex-manager/commands/status.js +501 -13
- package/dist/lib/codex-manager/help.js +3 -1
- package/dist/lib/codex-manager.js +98 -23
- package/dist/lib/config.js +3 -0
- package/dist/lib/constants.d.ts +1 -0
- package/dist/lib/constants.js +1 -0
- package/dist/lib/locale.d.ts +7 -0
- package/dist/lib/locale.js +35 -0
- package/dist/lib/model-route-policy.d.ts +1 -0
- package/dist/lib/model-route-policy.js +2 -1
- package/dist/lib/quota-probe.js +2 -1
- package/dist/lib/request/proxy-fetch.d.ts +5 -0
- package/dist/lib/request/proxy-fetch.js +37 -0
- package/dist/lib/request/url-rewriting.js +17 -0
- package/dist/lib/runtime/account-model-catalog.d.ts +3 -0
- package/dist/lib/runtime/account-model-catalog.js +7 -2
- package/dist/lib/runtime/api-model-capabilities.d.ts +11 -2
- package/dist/lib/runtime/api-model-capabilities.js +155 -24
- package/dist/lib/runtime/app-bind.d.ts +7 -0
- package/dist/lib/runtime/app-bind.js +92 -1
- package/dist/lib/runtime/automatic-api-checks.d.ts +15 -0
- package/dist/lib/runtime/automatic-api-checks.js +188 -0
- package/dist/lib/runtime/model-discovery-status.d.ts +11 -1
- package/dist/lib/runtime/model-discovery-status.js +64 -49
- package/dist/lib/runtime/persisted-api-capabilities.d.ts +20 -0
- package/dist/lib/runtime/persisted-api-capabilities.js +387 -0
- package/dist/lib/runtime/responses-websocket.d.ts +48 -5
- package/dist/lib/runtime/responses-websocket.js +275 -39
- package/dist/lib/runtime/rotation-account-selection.d.ts +25 -5
- package/dist/lib/runtime/rotation-account-selection.js +94 -4
- package/dist/lib/runtime/rotation-proxy-state.d.ts +26 -0
- package/dist/lib/runtime/rotation-proxy-state.js +376 -4
- package/dist/lib/runtime/rotation-server-types.d.ts +6 -0
- package/dist/lib/runtime/rotation-token-refresh.d.ts +23 -1
- package/dist/lib/runtime/rotation-token-refresh.js +218 -27
- package/dist/lib/runtime-rotation-proxy.js +601 -105
- package/dist/lib/schemas.d.ts +5 -0
- package/dist/lib/schemas.js +1 -0
- package/dist/lib/storage/credential-sidecars.js +34 -2
- package/dist/lib/storage/file-lock.d.ts +2 -0
- package/dist/lib/storage/file-lock.js +44 -30
- package/dist/lib/usage/ledger.js +1 -0
- package/dist/lib/usage/redaction.js +1 -0
- package/dist/lib/usage/types.d.ts +1 -1
- package/dist/lib/web-dashboard-html.d.ts +1 -0
- package/dist/lib/web-dashboard-html.js +66 -0
- package/dist/lib/web-dashboard.d.ts +36 -0
- package/dist/lib/web-dashboard.js +322 -0
- package/package.json +3 -1
- package/scripts/codex-app-router.js +86 -14
- package/scripts/codex-routing.js +1 -0
- package/scripts/codex.js +150 -13
|
@@ -6,12 +6,15 @@ import { styleReportText as paint } from "../../ui/format.js";
|
|
|
6
6
|
import { inferenceAccountKey } from "../../runtime/inference-activity.js";
|
|
7
7
|
import { modelScopeId } from "../../runtime/workspace-model-scopes.js";
|
|
8
8
|
import { loadAccountPolicyStore, getAccountPolicyKey } from "../../account-policy.js";
|
|
9
|
+
import { getSchedulingStrategy, loadPluginConfig } from "../../config.js";
|
|
9
10
|
import { formatModelInventory } from "../../runtime/model-discovery-status.js";
|
|
10
11
|
import { AUTH_INVALIDATION_MARKER, formatAccountLabel, formatCooldown, formatWaitTime, formatWorkspaceLines, } from "../../accounts.js";
|
|
11
12
|
import { evaluateForecastAccounts, recommendForecastAccount, } from "../../forecast.js";
|
|
12
13
|
import { findQuotaCacheEntryForAccount, isQuotaCacheEntryExhausted, } from "../../quota-readiness.js";
|
|
14
|
+
import { formatWebsocketDisconnect } from "../../runtime/app-bind.js";
|
|
13
15
|
import { resolveAccountCurrentMarkers, resolveRuntimeCurrentAccount, } from "../../runtime/runtime-current-account.js";
|
|
14
16
|
import { isRateLimitedMarker } from "../rate-limit-markers.js";
|
|
17
|
+
import { localized, resolveOutputLocale } from "../../locale.js";
|
|
15
18
|
function isRestoreReason(value) {
|
|
16
19
|
return (value === "empty-storage" ||
|
|
17
20
|
value === "intentional-reset" ||
|
|
@@ -69,6 +72,187 @@ function formatRuntimeLastAccount(runtimeSnapshot) {
|
|
|
69
72
|
}
|
|
70
73
|
return null;
|
|
71
74
|
}
|
|
75
|
+
function boundedPercent(value) {
|
|
76
|
+
return Math.max(0, Math.min(100, Math.round(value)));
|
|
77
|
+
}
|
|
78
|
+
/** Render usage as a visual bar while retaining exact percentages for copy/paste. */
|
|
79
|
+
function formatQuotaBar(usedPercent, width = 20) {
|
|
80
|
+
const used = boundedPercent(usedPercent);
|
|
81
|
+
const filled = Math.round((used / 100) * width);
|
|
82
|
+
return `${"█".repeat(filled)}${"░".repeat(Math.max(0, width - filled))}`;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Human label for a quota window length. Derived from the reported
|
|
86
|
+
* `windowMinutes` (300→"5h"/"5小时", 10080→"7d"/"7天"); falls back to a
|
|
87
|
+
* neutral primary/secondary label when the duration is unknown.
|
|
88
|
+
*/
|
|
89
|
+
function formatQuotaWindowDuration(windowMinutes, locale) {
|
|
90
|
+
const zh = locale === "zh";
|
|
91
|
+
if (windowMinutes % 1440 === 0) {
|
|
92
|
+
const days = windowMinutes / 1440;
|
|
93
|
+
return zh ? `${days}天` : `${days}d`;
|
|
94
|
+
}
|
|
95
|
+
if (windowMinutes % 60 === 0) {
|
|
96
|
+
const hours = windowMinutes / 60;
|
|
97
|
+
return zh ? `${hours}小时` : `${hours}h`;
|
|
98
|
+
}
|
|
99
|
+
return zh ? `${windowMinutes}分钟` : `${windowMinutes}m`;
|
|
100
|
+
}
|
|
101
|
+
function formatQuotaWindowLabel(window, key, locale) {
|
|
102
|
+
const minutes = window.windowMinutes;
|
|
103
|
+
if (typeof minutes === "number" && Number.isFinite(minutes) && minutes > 0) {
|
|
104
|
+
return formatQuotaWindowDuration(minutes, locale);
|
|
105
|
+
}
|
|
106
|
+
return localized(locale, key === "primary" ? "primary" : "secondary", key === "primary" ? "主窗口" : "次窗口");
|
|
107
|
+
}
|
|
108
|
+
function formatQuotaWindow(key, window, locale, width = 20) {
|
|
109
|
+
const used = boundedPercent(window.usedPercent ?? 0);
|
|
110
|
+
const left = 100 - used;
|
|
111
|
+
const label = formatQuotaWindowLabel(window, key, locale);
|
|
112
|
+
if (locale === "zh") {
|
|
113
|
+
return `${label} [${formatQuotaBar(used, width)}] 已用 ${used}% · 剩余 ${left}%`;
|
|
114
|
+
}
|
|
115
|
+
return `${label} [${formatQuotaBar(used, width)}] used ${used}% · ${left}% left`;
|
|
116
|
+
}
|
|
117
|
+
function formatQuotaCell(key, window, locale) {
|
|
118
|
+
const used = boundedPercent(window.usedPercent ?? 0);
|
|
119
|
+
const label = formatQuotaWindowLabel(window, key, locale);
|
|
120
|
+
return locale === "zh"
|
|
121
|
+
? `${label} ${formatQuotaBar(used, 8)} ${used}%用/${100 - used}%余`
|
|
122
|
+
: `${label} ${formatQuotaBar(used, 8)} ${used}%/${100 - used}%`;
|
|
123
|
+
}
|
|
124
|
+
function formatQuotaSummary(quota, locale, width = 20) {
|
|
125
|
+
return ["primary", "secondary"].flatMap((key) => {
|
|
126
|
+
const used = quota[key]?.usedPercent;
|
|
127
|
+
return typeof used === "number" && Number.isFinite(used)
|
|
128
|
+
? [formatQuotaWindow(key, quota[key], locale, width)]
|
|
129
|
+
: [];
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
function translateMarker(marker) {
|
|
133
|
+
const direct = {
|
|
134
|
+
current: "当前",
|
|
135
|
+
selected: "已选中",
|
|
136
|
+
"in-use": "使用中",
|
|
137
|
+
"strict pin": "严格固定",
|
|
138
|
+
disabled: "已禁用",
|
|
139
|
+
"rate-limited": "限流中",
|
|
140
|
+
"quota-exhausted": "配额已用尽",
|
|
141
|
+
"token-invalid — re-login needed": "令牌无效——需要重新登录",
|
|
142
|
+
};
|
|
143
|
+
if (direct[marker])
|
|
144
|
+
return direct[marker];
|
|
145
|
+
if (marker.startsWith("cooldown:"))
|
|
146
|
+
return `冷却中:${marker.slice("cooldown:".length)}`;
|
|
147
|
+
return marker
|
|
148
|
+
.replace(/^account is disabled$/, "账号已禁用")
|
|
149
|
+
.replace(/^runtime policy blocked account$/, "运行时策略阻止了账号")
|
|
150
|
+
.replace(/^quota cache exhausted$/, "配额缓存已用尽")
|
|
151
|
+
.replace(/^live probe returned 429$/, "实时探测返回 429")
|
|
152
|
+
.replace(/^primary quota (.+) used$/, "主窗口配额已使用 $1")
|
|
153
|
+
.replace(/^secondary quota (.+) used$/, "次窗口配额已使用 $1")
|
|
154
|
+
.replace(/^hard auth failure: /, "认证失败:")
|
|
155
|
+
.replace(/^refresh warning: /, "刷新警告:")
|
|
156
|
+
.replace(/^runtime skip: /, "运行时跳过:")
|
|
157
|
+
.replace(/^cooldown remaining /, "冷却剩余 ")
|
|
158
|
+
.replace(/^rate limit resets in /, "限流将在 ")
|
|
159
|
+
.replace(/^quota resets in /, "配额将在 ");
|
|
160
|
+
}
|
|
161
|
+
function translateDisconnectLine(line) {
|
|
162
|
+
return line
|
|
163
|
+
.replace(/^Last WebSocket disconnect: /, "上次 WebSocket 断开:")
|
|
164
|
+
.replace(/; cause=/, ";原因=")
|
|
165
|
+
.replace(/; stage=/, ";阶段=")
|
|
166
|
+
.replace(/; closeCode=/, ";关闭码=")
|
|
167
|
+
.replace(/; route=/, ";路由=")
|
|
168
|
+
.replace(/; connectionAgeMs=/, ";连接时长(ms)=")
|
|
169
|
+
.replace(/; events=/, ";事件数=");
|
|
170
|
+
}
|
|
171
|
+
function translateReason(reason) {
|
|
172
|
+
if (reason === "token-invalid — re-login needed")
|
|
173
|
+
return "令牌无效——需要重新登录";
|
|
174
|
+
if (reason === "primary quota 0% used")
|
|
175
|
+
return "主窗口配额已使用 0%";
|
|
176
|
+
if (reason === "primary quota 97% used")
|
|
177
|
+
return "主窗口配额已使用 97%";
|
|
178
|
+
if (reason === "No recommendation available.")
|
|
179
|
+
return "暂无推荐账号。";
|
|
180
|
+
return translateMarker(reason)
|
|
181
|
+
.replace(/^Lowest risk ready account \(([^,]+), score (\d+)\)\.$/, (_match, level, score) => `最低风险可用账号(${translateRiskLevel(level)},分数 ${score})。`)
|
|
182
|
+
.replace(/^No account is immediately ready; pick shortest wait \((.+)\)\.$/, "当前没有立即可用账号;选择等待时间最短的账号($1)。")
|
|
183
|
+
.replace(/^All accounts are blocked or exhausted\..*$/, "所有账号都被阻止或配额已用尽。")
|
|
184
|
+
.replace(/^No healthy accounts are available\..*$/, "没有健康可用的账号。")
|
|
185
|
+
.replace(/^primary quota (\d+)% used$/, "主窗口配额已使用 $1%")
|
|
186
|
+
.replace(/^secondary quota (\d+)% used$/, "次窗口配额已使用 $1%");
|
|
187
|
+
}
|
|
188
|
+
function formatLocalizedSubscriptionStatus(value) {
|
|
189
|
+
const plan = {
|
|
190
|
+
"Unknown plan": "未知套餐",
|
|
191
|
+
Business: "商业版",
|
|
192
|
+
Enterprise: "企业版",
|
|
193
|
+
Edu: "教育版",
|
|
194
|
+
};
|
|
195
|
+
const freshness = value.freshness === "stale"
|
|
196
|
+
? "(数据已过期;请运行账号检查)"
|
|
197
|
+
: value.freshness === "missing" ? "(尚未观测)" : "";
|
|
198
|
+
const autoPrime = value.autoPrime === null ? "未知" : value.autoPrime ? "开启" : "关闭";
|
|
199
|
+
const priming = {
|
|
200
|
+
unknown: "首次使用状态未知",
|
|
201
|
+
"awaiting-first-use": "等待首次使用",
|
|
202
|
+
"timer-running": "重置计时运行中",
|
|
203
|
+
used: "已记录使用",
|
|
204
|
+
completed: "首次使用已完成;尚未确认计时器",
|
|
205
|
+
failed: "首次使用失败",
|
|
206
|
+
"not-applicable": "首次使用不适用",
|
|
207
|
+
};
|
|
208
|
+
const block = {
|
|
209
|
+
"sign-in required": "需要重新登录",
|
|
210
|
+
"account disabled": "账号已禁用",
|
|
211
|
+
paused: "已暂停",
|
|
212
|
+
drained: "已排空",
|
|
213
|
+
"cooling down": "冷却中",
|
|
214
|
+
"workspace unavailable": "工作区不可用",
|
|
215
|
+
};
|
|
216
|
+
const workspace = value.selectedWorkspace
|
|
217
|
+
.replace(/Token account/g, "令牌账号")
|
|
218
|
+
.replace(/Unnamed workspace/g, "未命名工作区")
|
|
219
|
+
.replace(/Invalid workspace selection/g, "工作区选择无效")
|
|
220
|
+
.replace(/Stored binding \(workspace unknown\)/g, "已存绑定(工作区未知)");
|
|
221
|
+
const planLabel = plan[value.planLabel] ?? value.planLabel;
|
|
222
|
+
const blocked = value.automaticBlock ? `;阻断:${block[value.automaticBlock] ?? value.automaticBlock}` : "";
|
|
223
|
+
return `${workspace}${freshness} · ${planLabel} · 自动预热 ${autoPrime} · ${priming[value.primingState] ?? value.primingState}${blocked}`;
|
|
224
|
+
}
|
|
225
|
+
function formatStorageHealthZh(state) {
|
|
226
|
+
const labels = {
|
|
227
|
+
healthy: "正常",
|
|
228
|
+
corrupt: "损坏",
|
|
229
|
+
empty: "空",
|
|
230
|
+
"intentional-reset": "已主动重置",
|
|
231
|
+
recoverable: "可恢复",
|
|
232
|
+
};
|
|
233
|
+
return labels[state] ?? state;
|
|
234
|
+
}
|
|
235
|
+
function translateAccountLabel(label) {
|
|
236
|
+
return label
|
|
237
|
+
.replace(/^Account /, "账号 ")
|
|
238
|
+
.replace(/Token account/g, "令牌账号")
|
|
239
|
+
.replace(/, id:/g, ",ID:")
|
|
240
|
+
.replace(/, /g, ",");
|
|
241
|
+
}
|
|
242
|
+
function translateRiskLevel(level) {
|
|
243
|
+
return { low: "低", medium: "中", high: "高" }[level] ?? level;
|
|
244
|
+
}
|
|
245
|
+
function formatModelInventoryZh(inventory) {
|
|
246
|
+
if (!inventory)
|
|
247
|
+
return ["模型:未检查;请运行 codex-multi-auth check。"];
|
|
248
|
+
const lines = [`模型发现(仅目录访问,不会发起推理探测):${inventory.entries.length} 个条目`];
|
|
249
|
+
for (const entry of inventory.entries) {
|
|
250
|
+
const state = !entry.enabled ? "已禁用" : entry.error ? "发现失败" : `${entry.models.length} 个可用`;
|
|
251
|
+
lines.push(` ${entry.label} [${entry.kind.toUpperCase()}]:${state}`);
|
|
252
|
+
lines.push(` 可用模型:${entry.models.join("、") || "无"}`);
|
|
253
|
+
}
|
|
254
|
+
return lines;
|
|
255
|
+
}
|
|
72
256
|
/** Expired observations must not impose pressure after their window has reset. */
|
|
73
257
|
function forecastQuotaWindow(window, updatedAt, now) {
|
|
74
258
|
const expiresAt = window.resetAtMs ?? (window.windowMinutes && window.windowMinutes > 0 ? updatedAt + window.windowMinutes * 60_000 : null);
|
|
@@ -83,14 +267,19 @@ export async function runStatusCommand(deps) {
|
|
|
83
267
|
const path = deps.getStoragePath();
|
|
84
268
|
const storageHealth = await deps.inspectStorageHealth?.();
|
|
85
269
|
const logInfo = deps.logInfo ?? console.log;
|
|
86
|
-
|
|
270
|
+
const locale = deps.locale ?? resolveOutputLocale(loadPluginConfig().locale);
|
|
271
|
+
if (apiConfigurationUnavailable && !deps.json && !deps.compact && locale !== "zh")
|
|
87
272
|
logInfo("API configuration unavailable; subscription status remains available.");
|
|
88
273
|
const modelInventory = await deps.loadModelInventory?.();
|
|
274
|
+
const appBindStatus = await deps.loadAppBindStatus?.() ?? null;
|
|
275
|
+
const disconnectLine = formatWebsocketDisconnect(appBindStatus?.lastWebsocketDisconnect);
|
|
276
|
+
if (!deps.json && !deps.compact && locale !== "zh" && disconnectLine)
|
|
277
|
+
logInfo(paint(disconnectLine, "warning"));
|
|
89
278
|
const resetCredits = await deps.loadResetCreditState?.().catch(() => null);
|
|
90
279
|
const resetSnapshot = (account) => { const target = resetTargetForStoredAccount(account); return target ? resetCredits?.snapshots[target.key] : undefined; };
|
|
91
280
|
let policiesUnavailable = false;
|
|
92
281
|
const accountPolicies = await (deps.loadAccountPolicies ?? loadAccountPolicyStore)({ strict: true }).catch(() => { policiesUnavailable = true; return { version: 1, accounts: {} }; });
|
|
93
|
-
if (!deps.json && deps.loadModelInventory)
|
|
282
|
+
if (!deps.json && !deps.compact && locale !== "zh" && deps.loadModelInventory)
|
|
94
283
|
for (const line of formatModelInventory(modelInventory ?? null))
|
|
95
284
|
logInfo(line);
|
|
96
285
|
if (!storage || (storage.accounts.length === 0 && apiRoutes.length === 0)) {
|
|
@@ -111,6 +300,7 @@ export async function runStatusCommand(deps) {
|
|
|
111
300
|
totalAccountCount: 0,
|
|
112
301
|
apiAccounts: [],
|
|
113
302
|
modelInventory: modelInventory ?? null,
|
|
303
|
+
lastWebsocketDisconnect: appBindStatus?.lastWebsocketDisconnect ?? null,
|
|
114
304
|
// Emit the same keys the populated branch does (as null) so a
|
|
115
305
|
// --json consumer sees one stable shape regardless of account count.
|
|
116
306
|
activeIndex: null,
|
|
@@ -118,10 +308,39 @@ export async function runStatusCommand(deps) {
|
|
|
118
308
|
recommendedIndex: null,
|
|
119
309
|
recommendationReason: null,
|
|
120
310
|
runtimeInUseIndex: null,
|
|
311
|
+
runtime: null,
|
|
121
312
|
accounts: [],
|
|
122
313
|
}, null, 2));
|
|
123
314
|
return 0;
|
|
124
315
|
}
|
|
316
|
+
if (deps.compact) {
|
|
317
|
+
logInfo(localized(locale, "Codex Multi-Auth status", "Codex Multi-Auth 状态"));
|
|
318
|
+
logInfo(localized(locale, "Accounts: 0 total", "账号:共 0 个"));
|
|
319
|
+
logInfo(`${localized(locale, "Storage", "存储")}: ${path}`);
|
|
320
|
+
if (effectiveState)
|
|
321
|
+
logInfo(`${localized(locale, "Storage health", "存储健康")}: ${effectiveState}`);
|
|
322
|
+
return 0;
|
|
323
|
+
}
|
|
324
|
+
if (locale === "zh") {
|
|
325
|
+
if (apiConfigurationUnavailable)
|
|
326
|
+
logInfo("警告:API 配置不可用;仍会显示订阅状态。");
|
|
327
|
+
if (deps.loadModelInventory) {
|
|
328
|
+
for (const line of formatModelInventoryZh(modelInventory ?? null))
|
|
329
|
+
logInfo(line);
|
|
330
|
+
}
|
|
331
|
+
const message = effectiveState === "intentional-reset"
|
|
332
|
+
? "未配置账号。存储已主动重置。"
|
|
333
|
+
: effectiveState === "recoverable"
|
|
334
|
+
? "未配置账号。存在可用的恢复文件。"
|
|
335
|
+
: effectiveState === "corrupt"
|
|
336
|
+
? "未配置账号。存储似乎已损坏。"
|
|
337
|
+
: "未配置账号。";
|
|
338
|
+
logInfo(message);
|
|
339
|
+
logInfo(`存储:${path}`);
|
|
340
|
+
if (effectiveState)
|
|
341
|
+
logInfo(`存储健康:${formatStorageHealthZh(effectiveState)}`);
|
|
342
|
+
return 0;
|
|
343
|
+
}
|
|
125
344
|
logInfo(effectiveState === "intentional-reset"
|
|
126
345
|
? "No accounts configured. Storage was intentionally reset."
|
|
127
346
|
: effectiveState === "recoverable"
|
|
@@ -161,7 +380,7 @@ export async function runStatusCommand(deps) {
|
|
|
161
380
|
liveQuota: forecastQuotas[index] ?? undefined,
|
|
162
381
|
})));
|
|
163
382
|
const recommendation = recommendForecastAccount(forecastResults);
|
|
164
|
-
if (!deps.json) {
|
|
383
|
+
if (!deps.json && !deps.compact && locale !== "zh") {
|
|
165
384
|
logInfo(paint(apiRoutes.length ? `Accounts (${storage.accounts.length + apiRoutes.length}: ${storage.accounts.length} subscription, ${apiRoutes.length} API/ZDR)` : `Accounts (${storage.accounts.length})`, "heading"));
|
|
166
385
|
logInfo(paint(`Storage: ${path}`, "muted"));
|
|
167
386
|
if (recommendation.recommendedIndex !== null) {
|
|
@@ -172,17 +391,26 @@ export async function runStatusCommand(deps) {
|
|
|
172
391
|
}
|
|
173
392
|
}
|
|
174
393
|
const appHelperStatus = deps.loadAppHelperStatus?.() ?? null;
|
|
175
|
-
const
|
|
176
|
-
deps.loadRuntimeObservabilitySnapshot?.() ?? Promise.resolve(null),
|
|
177
|
-
deps.loadAppBindStatus?.() ?? Promise.resolve(null),
|
|
178
|
-
]);
|
|
394
|
+
const runtimeSnapshot = await deps.loadRuntimeObservabilitySnapshot?.() ?? null;
|
|
179
395
|
const quotaPreferences = forecastQuotas.map(quota => subscriptionQuotaPreference(quota, now));
|
|
180
396
|
const automaticOrder = new Map();
|
|
181
397
|
if (appBindStatus?.nativeOpenai) {
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
candidates.
|
|
185
|
-
|
|
398
|
+
const schedulingStrategy = appBindStatus.schedulingStrategy ?? getSchedulingStrategy(loadPluginConfig());
|
|
399
|
+
let candidates = storage.accounts.map((account, index) => ({ account, index, quota: quotaPreferences[index] ?? subscriptionQuotaPreference(null, now), tier: accountPolicies.accounts[getAccountPolicyKey(account, index)]?.priority ?? 1 }));
|
|
400
|
+
const useSavedOrder = candidates.some(account => account.tier !== 1);
|
|
401
|
+
candidates = candidates.filter(a => forecastResults[a.index]?.availability === "ready" && !a.quota.exhausted);
|
|
402
|
+
candidates.sort((a, b) => {
|
|
403
|
+
const tierOrder = Number(usesSubscriptionReserve(a.quota)) - Number(usesSubscriptionReserve(b.quota)) || a.tier - b.tier;
|
|
404
|
+
if (tierOrder)
|
|
405
|
+
return tierOrder;
|
|
406
|
+
if (schedulingStrategy === "sequential") {
|
|
407
|
+
if (useSavedOrder)
|
|
408
|
+
return a.index - b.index;
|
|
409
|
+
const count = storage.accounts.length;
|
|
410
|
+
return (a.index - activeIndex + count) % count - (b.index - activeIndex + count) % count;
|
|
411
|
+
}
|
|
412
|
+
return compareSubscriptionQuota(a.quota, b.quota) || Number(b.index === activeIndex) - Number(a.index === activeIndex) || a.index - b.index;
|
|
413
|
+
});
|
|
186
414
|
// A switch pin is strict in native mode too: no other account is tried, so none gets an order.
|
|
187
415
|
const pinned = typeof storage.pinnedAccountIndex === "number" ? candidates.filter(a => a.index === storage.pinnedAccountIndex) : candidates;
|
|
188
416
|
pinned.forEach((a, index) => { automaticOrder.set(a.index, index + 1); });
|
|
@@ -213,6 +441,97 @@ export async function runStatusCommand(deps) {
|
|
|
213
441
|
appBindStatus,
|
|
214
442
|
appHelperStatus,
|
|
215
443
|
}, { now });
|
|
444
|
+
if (!deps.json && deps.compact) {
|
|
445
|
+
const compactRows = storage.accounts.map((account, index) => {
|
|
446
|
+
const markers = buildAccountMarkers(account, index, activeIndex, runtimeCurrent, now, quotaCache, storage.accounts, deps.formatRateLimitEntry);
|
|
447
|
+
const needsLogin = markers.some((marker) => /(re-login|sign-in|invalid)/i.test(marker));
|
|
448
|
+
const disabled = account.enabled === false;
|
|
449
|
+
const state = disabled
|
|
450
|
+
? localized(locale, "disabled", "已禁用")
|
|
451
|
+
: needsLogin
|
|
452
|
+
? localized(locale, "needs login", "需重新登录")
|
|
453
|
+
: markers.length > 0
|
|
454
|
+
? markers
|
|
455
|
+
.map((marker) => marker === "current" ? localized(locale, "current", "当前") : marker === "selected" ? localized(locale, "selected", "已选中") : marker === "in-use" ? localized(locale, "in use", "使用中") : marker)
|
|
456
|
+
.join(", ")
|
|
457
|
+
: localized(locale, "ready", "可用");
|
|
458
|
+
const subscription = subscriptionStatuses[index];
|
|
459
|
+
const plan = subscription?.planLabel === "Unknown plan"
|
|
460
|
+
? localized(locale, "unknown", "未知")
|
|
461
|
+
: subscription?.planLabel ?? localized(locale, "unknown", "未知");
|
|
462
|
+
const quota = forecastQuotas[index];
|
|
463
|
+
const quotaText = quota
|
|
464
|
+
? ["primary", "secondary"]
|
|
465
|
+
.map((key) => {
|
|
466
|
+
const used = quota[key].usedPercent;
|
|
467
|
+
return typeof used === "number" && Number.isFinite(used)
|
|
468
|
+
? formatQuotaCell(key, quota[key], locale)
|
|
469
|
+
: null;
|
|
470
|
+
})
|
|
471
|
+
.filter(Boolean)
|
|
472
|
+
.join(" / ") || "?"
|
|
473
|
+
: "?";
|
|
474
|
+
const last = inferenceTimes[inferenceAccountKey(account)];
|
|
475
|
+
const activity = typeof last === "number" && last > 0 ? formatWaitTime(Math.max(0, now - last)) : "—";
|
|
476
|
+
// Counts derive from the unlocalized marker set, not the rendered
|
|
477
|
+
// label: matching translated text miscounts states like a raw
|
|
478
|
+
// "disabled" marker embedded in a joined reason list.
|
|
479
|
+
const stateKind = disabled
|
|
480
|
+
? "disabled"
|
|
481
|
+
: needsLogin
|
|
482
|
+
? "login"
|
|
483
|
+
: markers.some((marker) => isRateLimitedMarker(marker) ||
|
|
484
|
+
marker === "quota-exhausted" ||
|
|
485
|
+
marker.startsWith("cooldown:"))
|
|
486
|
+
? "blocked"
|
|
487
|
+
: "ready";
|
|
488
|
+
return { index: index + 1, plan, state, stateKind, quota: quotaText, activity };
|
|
489
|
+
});
|
|
490
|
+
const apiRows = apiAccounts.map((account) => ({
|
|
491
|
+
index: `${account.kind}${account.index + 1}`,
|
|
492
|
+
plan: account.label || localized(locale, "API", "API"),
|
|
493
|
+
state: account.enabled
|
|
494
|
+
? localized(locale, "enabled", "已启用")
|
|
495
|
+
: localized(locale, "disabled", "已禁用"),
|
|
496
|
+
enabled: account.enabled,
|
|
497
|
+
quota: "—",
|
|
498
|
+
activity: typeof account.lastInferenceRequestAt === "number" && account.lastInferenceRequestAt > 0
|
|
499
|
+
? formatWaitTime(Math.max(0, now - account.lastInferenceRequestAt))
|
|
500
|
+
: "—",
|
|
501
|
+
}));
|
|
502
|
+
const needsLoginCount = compactRows.filter((row) => row.stateKind === "login").length;
|
|
503
|
+
const disabledCount = compactRows.filter((row) => row.stateKind === "disabled").length;
|
|
504
|
+
const blockedCount = compactRows.filter((row) => row.stateKind === "blocked").length;
|
|
505
|
+
const readyCount = compactRows.length - needsLoginCount - disabledCount - blockedCount;
|
|
506
|
+
const route = typeof storage.pinnedAccountIndex === "number"
|
|
507
|
+
? localized(locale, `pinned to account ${storage.pinnedAccountIndex + 1}`, `固定到账号 ${storage.pinnedAccountIndex + 1}`)
|
|
508
|
+
: activeIndex >= 0
|
|
509
|
+
? localized(locale, `automatic (active account ${activeIndex + 1})`, `自动轮换(当前账号 ${activeIndex + 1})`)
|
|
510
|
+
: localized(locale, "automatic", "自动轮换");
|
|
511
|
+
logInfo(localized(locale, "Codex Multi-Auth status", "Codex Multi-Auth 状态"));
|
|
512
|
+
logInfo(`${localized(locale, "Accounts", "账号")}: ${readyCount} ${localized(locale, "ready", "可用")} / ${needsLoginCount} ${localized(locale, "need login", "需登录")}${blockedCount > 0 ? ` / ${blockedCount} ${localized(locale, "blocked", "受限")}` : ""} / ${disabledCount} ${localized(locale, "disabled", "已禁用")} (${compactRows.length} ${localized(locale, "total", "总计")})`);
|
|
513
|
+
if (apiRows.length > 0) {
|
|
514
|
+
const apiDisabled = apiRows.filter((row) => !row.enabled).length;
|
|
515
|
+
logInfo(`${localized(locale, "API/ZDR accounts", "API/ZDR 账号")}: ${apiRows.length - apiDisabled} ${localized(locale, "enabled", "已启用")} / ${apiDisabled} ${localized(locale, "disabled", "已禁用")}`);
|
|
516
|
+
}
|
|
517
|
+
logInfo(`${localized(locale, "Routing", "路由")}: ${route}; ${localized(locale, "recommended", "推荐")} ${recommendation.recommendedIndex === null ? "—" : `#${recommendation.recommendedIndex + 1}`}`);
|
|
518
|
+
if (runtimeSnapshot) {
|
|
519
|
+
const metrics = runtimeSnapshot.runtimeMetrics;
|
|
520
|
+
logInfo(`${localized(locale, "Runtime", "运行时")}: ${runtimeSnapshot.responsesRequests} ${localized(locale, "requests", "请求")}, ${metrics.successfulRequests} ${localized(locale, "ok", "成功")}, ${metrics.failedRequests} ${localized(locale, "failed", "失败")}`);
|
|
521
|
+
}
|
|
522
|
+
logInfo("");
|
|
523
|
+
logInfo(localized(locale, "# Plan Quota State Last request", "# 套餐 配额 状态 最近请求"));
|
|
524
|
+
for (const row of [...compactRows, ...apiRows]) {
|
|
525
|
+
logInfo(`${row.index} ${row.plan.padEnd(10)} ${row.quota.padEnd(14)} ${row.state.padEnd(17)} ${row.activity}`);
|
|
526
|
+
}
|
|
527
|
+
if (needsLoginCount > 0) {
|
|
528
|
+
logInfo("");
|
|
529
|
+
logInfo(localized(locale, "Action: codex-multi-auth login --account <index>", "操作:codex-multi-auth login --account <序号>"));
|
|
530
|
+
}
|
|
531
|
+
logInfo(`${localized(locale, "Storage", "存储")}: ${path}`);
|
|
532
|
+
logInfo(localized(locale, "Use --details for the full diagnostic view; --json is stable machine-readable output.", "使用 --details 查看完整诊断;--json 输出稳定的机器可读格式。"));
|
|
533
|
+
return 0;
|
|
534
|
+
}
|
|
216
535
|
// cli-manager-03: machine-readable output for status/list. Build a single
|
|
217
536
|
// object from the same data the text path renders, then emit and return.
|
|
218
537
|
if (deps.json) {
|
|
@@ -233,6 +552,14 @@ export async function runStatusCommand(deps) {
|
|
|
233
552
|
forecastRiskScore: forecastResults[i]?.riskScore ?? null,
|
|
234
553
|
forecastRiskLevel: forecastResults[i]?.riskLevel ?? null,
|
|
235
554
|
forecastQuotaUpdatedAt: forecastQuotas[i]?.updatedAt ?? null,
|
|
555
|
+
quota: (() => {
|
|
556
|
+
const entry = forecastQuotas[i];
|
|
557
|
+
return entry ? {
|
|
558
|
+
updatedAt: entry.updatedAt,
|
|
559
|
+
primary: entry.primary,
|
|
560
|
+
secondary: entry.secondary,
|
|
561
|
+
} : null;
|
|
562
|
+
})(),
|
|
236
563
|
automaticOrder: automaticOrder.get(i) ?? null,
|
|
237
564
|
subscriptionReserve: usesSubscriptionReserve(quotaPreferences[i]),
|
|
238
565
|
quotaResetAt: quotaPreferences[i]?.resetAtMs ?? null,
|
|
@@ -260,15 +587,176 @@ export async function runStatusCommand(deps) {
|
|
|
260
587
|
recommendationReason: recommendation.reason,
|
|
261
588
|
runtimeInUseIndex: runtimeCurrent ? runtimeCurrent.index : null,
|
|
262
589
|
modelInventory: modelInventory ?? null,
|
|
590
|
+
lastWebsocketDisconnect: appBindStatus?.lastWebsocketDisconnect ?? null,
|
|
263
591
|
accounts,
|
|
264
592
|
lastRequestedWorkspace: runtimeSnapshot?.lastRequestedWorkspaceId ? {
|
|
265
593
|
id: runtimeSnapshot.lastRequestedWorkspaceId,
|
|
266
594
|
accountIndex: runtimeSnapshot.lastAccountIndex ?? null,
|
|
267
595
|
requestedAt: runtimeSnapshot.lastAccountUpdatedAt ?? null,
|
|
268
596
|
} : null,
|
|
597
|
+
runtime: runtimeSnapshot ? {
|
|
598
|
+
responsesRequests: runtimeSnapshot.responsesRequests,
|
|
599
|
+
authRefreshRequests: runtimeSnapshot.authRefreshRequests,
|
|
600
|
+
diagnosticProbeRequests: runtimeSnapshot.diagnosticProbeRequests,
|
|
601
|
+
successfulRequests: runtimeSnapshot.runtimeMetrics.successfulRequests,
|
|
602
|
+
failedRequests: runtimeSnapshot.runtimeMetrics.failedRequests,
|
|
603
|
+
} : null,
|
|
269
604
|
}, null, 2));
|
|
270
605
|
return 0;
|
|
271
606
|
}
|
|
607
|
+
// The Chinese detailed view is rendered as one localized block so no
|
|
608
|
+
// English diagnostic labels leak through when --locale zh is requested.
|
|
609
|
+
if (locale === "zh") {
|
|
610
|
+
if (apiConfigurationUnavailable)
|
|
611
|
+
logInfo("警告:API 配置不可用;仍会显示订阅状态。");
|
|
612
|
+
if (disconnectLine)
|
|
613
|
+
logInfo(translateDisconnectLine(disconnectLine));
|
|
614
|
+
if (deps.loadModelInventory) {
|
|
615
|
+
for (const line of formatModelInventoryZh(modelInventory ?? null))
|
|
616
|
+
logInfo(line);
|
|
617
|
+
}
|
|
618
|
+
const totalLabel = apiAccounts.length
|
|
619
|
+
? storage.accounts.length + apiAccounts.length + ":"
|
|
620
|
+
+ storage.accounts.length + " 个订阅账号," + apiAccounts.length + " 个 API/ZDR 账号"
|
|
621
|
+
: String(storage.accounts.length);
|
|
622
|
+
logInfo("账号(" + totalLabel + ")");
|
|
623
|
+
logInfo("存储:" + path);
|
|
624
|
+
logInfo("预测建议:" + (recommendation.recommendedIndex === null
|
|
625
|
+
? translateReason(recommendation.reason)
|
|
626
|
+
: "账号 " + (recommendation.recommendedIndex + 1) + "(" + translateReason(recommendation.reason) + ")"));
|
|
627
|
+
if (storageHealth)
|
|
628
|
+
logInfo("存储健康:" + formatStorageHealthZh(storageHealth.state));
|
|
629
|
+
if (runtimeSnapshot) {
|
|
630
|
+
const runtimeMetrics = runtimeSnapshot.runtimeMetrics;
|
|
631
|
+
logInfo("运行时:请求=" + runtimeSnapshot.responsesRequests
|
|
632
|
+
+ ",刷新=" + runtimeSnapshot.authRefreshRequests
|
|
633
|
+
+ ",探测=" + runtimeSnapshot.diagnosticProbeRequests
|
|
634
|
+
+ ",保留模型回退=" + (runtimeSnapshot.lunaReserveFallbacks ?? 0)
|
|
635
|
+
+ ",预算耗尽=" + runtimeMetrics.requestAttemptBudgetExhaustions);
|
|
636
|
+
const lastRuntimeAccount = formatRuntimeLastAccount(runtimeSnapshot);
|
|
637
|
+
if (lastRuntimeAccount) {
|
|
638
|
+
logInfo("最近运行账号:" + translateAccountLabel(lastRuntimeAccount));
|
|
639
|
+
if (typeof runtimeSnapshot.lastAccountUpdatedAt === "number" && runtimeSnapshot.lastAccountUpdatedAt > 0) {
|
|
640
|
+
logInfo("最近运行请求:" + formatWaitTime(Math.max(0, now - runtimeSnapshot.lastAccountUpdatedAt)) + " 前");
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
const requestedId = runtimeSnapshot.lastRequestedWorkspaceId;
|
|
644
|
+
if (requestedId) {
|
|
645
|
+
const index = resolveRuntimeCurrentAccount(storage, { runtimeSnapshot }, { now })?.index;
|
|
646
|
+
const workspace = index === undefined ? undefined : storage.accounts[index]?.workspaces?.find((w) => w.id === requestedId);
|
|
647
|
+
logInfo("最近请求工作区:" + (index === undefined ? "" : "账号 " + (index + 1) + " / ")
|
|
648
|
+
+ (workspace?.name?.trim() || "未命名工作区").replace(/Token account/g, "令牌账号") + "(ID:" + requestedId.slice(-6)
|
|
649
|
+
+ ";发送范围,计费套餐未验证)");
|
|
650
|
+
}
|
|
651
|
+
else {
|
|
652
|
+
logInfo("最近请求工作区:未记录");
|
|
653
|
+
}
|
|
654
|
+
const poolCooldown = typeof runtimeSnapshot.poolExhaustionCooldownUntil === "number" && runtimeSnapshot.poolExhaustionCooldownUntil > now
|
|
655
|
+
? formatWaitTime(runtimeSnapshot.poolExhaustionCooldownUntil - now) : null;
|
|
656
|
+
const serverCooldown = typeof runtimeSnapshot.serverBurstCooldownUntil === "number" && runtimeSnapshot.serverBurstCooldownUntil > now
|
|
657
|
+
? formatWaitTime(runtimeSnapshot.serverBurstCooldownUntil - now) : null;
|
|
658
|
+
if (poolCooldown || serverCooldown)
|
|
659
|
+
logInfo("冷却:账号池=" + (poolCooldown ?? "无") + ",服务突发=" + (serverCooldown ?? "无"));
|
|
660
|
+
if (runtimeSnapshot.currentRequestId)
|
|
661
|
+
logInfo("最近请求跟踪 ID:" + runtimeSnapshot.currentRequestId);
|
|
662
|
+
}
|
|
663
|
+
if (runtimeCurrent)
|
|
664
|
+
logInfo("当前运行账号:账号 " + (runtimeCurrent.index + 1) + "(来源:" + runtimeCurrent.source + ")");
|
|
665
|
+
if (appBindStatus?.nativeOpenai)
|
|
666
|
+
logInfo("自动顺序来自最近一次检查;请求模型、工作区和实时健康状态可能改变顺序。");
|
|
667
|
+
logInfo("下面的账号/工作区标签表示保存的设置,不代表最近一次路由的工作区。");
|
|
668
|
+
const pinnedAccountIndex = storage.pinnedAccountIndex;
|
|
669
|
+
if (typeof pinnedAccountIndex === "number") {
|
|
670
|
+
if (!Number.isInteger(pinnedAccountIndex) || pinnedAccountIndex < 0 || pinnedAccountIndex >= storage.accounts.length) {
|
|
671
|
+
logInfo("固定账号:索引 " + pinnedAccountIndex + " 无效;请运行 codex-multi-auth unpin");
|
|
672
|
+
}
|
|
673
|
+
else {
|
|
674
|
+
logInfo("固定账号:账号 " + (pinnedAccountIndex + 1) + "(严格固定,由 switch 设置)");
|
|
675
|
+
if (!appBindStatus?.nativeOpenai && runtimeCurrent && runtimeCurrent.index !== pinnedAccountIndex) {
|
|
676
|
+
logInfo("警告:当前运行账号是 " + (runtimeCurrent.index + 1) + ",但固定账号是 "
|
|
677
|
+
+ (pinnedAccountIndex + 1) + ";下一次请求时代理会采用固定账号。");
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
logInfo("");
|
|
682
|
+
for (let i = 0; i < storage.accounts.length; i += 1) {
|
|
683
|
+
const account = storage.accounts[i];
|
|
684
|
+
if (!account)
|
|
685
|
+
continue;
|
|
686
|
+
const label = translateAccountLabel(formatAccountLabel(account, i));
|
|
687
|
+
const markers = buildAccountMarkers(account, i, activeIndex, runtimeCurrent, now, quotaCache, storage.accounts, deps.formatRateLimitEntry);
|
|
688
|
+
if (storage.pinnedAccountIndex === i)
|
|
689
|
+
markers.push("strict pin");
|
|
690
|
+
const markerLabel = markers.length > 0 ? " [" + markers.map(translateMarker).join(",") + "]" : "";
|
|
691
|
+
const lastInferenceAt = inferenceTimes[inferenceAccountKey(account)];
|
|
692
|
+
const activity = typeof account.lastUsed === "number" && account.lastUsed > 0
|
|
693
|
+
? "账号活动于 " + formatWaitTime(Math.max(0, now - account.lastUsed)) + " 前"
|
|
694
|
+
: "尚无账号活动记录";
|
|
695
|
+
const lastUsed = typeof lastInferenceAt === "number" && lastInferenceAt > 0
|
|
696
|
+
? "最近推理请求于 " + formatWaitTime(Math.max(0, now - lastInferenceAt)) + " 前"
|
|
697
|
+
: "尚未记录推理请求;" + activity;
|
|
698
|
+
logInfo((i + 1) + ". " + label + markerLabel + " " + lastUsed);
|
|
699
|
+
logInfo(" 优先级:" + (accountPolicies.accounts[getAccountPolicyKey(account, i)]?.priority ?? 1));
|
|
700
|
+
const subscription = subscriptionStatuses[i];
|
|
701
|
+
if (subscription)
|
|
702
|
+
logInfo(" 订阅:" + formatLocalizedSubscriptionStatus(subscription));
|
|
703
|
+
if (appBindStatus?.nativeOpenai) {
|
|
704
|
+
const preference = quotaPreferences[i];
|
|
705
|
+
const order = automaticOrder.get(i);
|
|
706
|
+
const reset = preference?.resetAtMs
|
|
707
|
+
? ";限制窗口将在 " + formatWaitTime(Math.max(0, preference.resetAtMs - now)) + " 后重置"
|
|
708
|
+
: ";配额重置时间未知,请运行检查";
|
|
709
|
+
const reserve = usesSubscriptionReserve(preference) ? ";" + SUBSCRIPTION_RESERVE_PERCENT + "% 保留额度(最后使用)" : "";
|
|
710
|
+
const pinnedElsewhere = typeof storage.pinnedAccountIndex === "number" && storage.pinnedAccountIndex !== i;
|
|
711
|
+
const pinnedIndex = storage.pinnedAccountIndex;
|
|
712
|
+
logInfo(" 自动顺序:" + (order ? "#" + order : pinnedElsewhere && typeof pinnedIndex === "number" ? "无(账号 " + (pinnedIndex + 1) + " 已固定)" : "不可用") + reset + reserve);
|
|
713
|
+
}
|
|
714
|
+
const quota = forecastQuotas[i];
|
|
715
|
+
if (quota) {
|
|
716
|
+
const quotaLines = formatQuotaSummary(quota, "zh");
|
|
717
|
+
if (quotaLines.length > 0) {
|
|
718
|
+
logInfo(" 配额:");
|
|
719
|
+
for (const line of quotaLines)
|
|
720
|
+
logInfo(" " + line);
|
|
721
|
+
logInfo(" 缓存于 " + formatWaitTime(Math.max(0, now - quota.updatedAt)) + " 前");
|
|
722
|
+
}
|
|
723
|
+
else {
|
|
724
|
+
logInfo(" 配额:未知;请运行检查(风险评估仅使用其他信号)");
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
else {
|
|
728
|
+
logInfo(" 配额:未知;请运行检查(风险评估仅使用其他信号)");
|
|
729
|
+
}
|
|
730
|
+
const resets = resetSnapshot(account);
|
|
731
|
+
logInfo(" 订阅重置额度:" + (resets?.availableCount ?? "未知")
|
|
732
|
+
+ (resets ? "(缓存于 " + formatWaitTime(Math.max(0, now - resets.updatedAt)) + " 前)" : ";请运行检查"));
|
|
733
|
+
const forecast = forecastResults[i];
|
|
734
|
+
if (forecast)
|
|
735
|
+
logInfo(" 预测风险:" + forecast.riskScore + "/100(" + translateRiskLevel(forecast.riskLevel) + ";分数越低越好)");
|
|
736
|
+
const primaryReason = forecastResults[i]?.reasons[0];
|
|
737
|
+
if (primaryReason)
|
|
738
|
+
logInfo(" 原因:" + translateReason(primaryReason));
|
|
739
|
+
if ((account.workspaces?.length ?? 0) > 1) {
|
|
740
|
+
logInfo(" 工作区(保存的选择;不是实时路由):");
|
|
741
|
+
for (const workspaceLine of formatWorkspaceLines(account, " ", "保存的选择")) {
|
|
742
|
+
logInfo(workspaceLine.replace(/\(disabled\)/g, "(已禁用)").replace(/ id:/g, " ID:").replace(/\(unnamed\)/g, "(未命名)"));
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
if (apiAccounts.length > 0) {
|
|
747
|
+
logInfo("");
|
|
748
|
+
logInfo("API/ZDR 账号(" + apiAccounts.length + " 个;独立隐私池,低优先级数字先用):");
|
|
749
|
+
for (const account of apiAccounts) {
|
|
750
|
+
const last = account.lastInferenceRequestAt === null
|
|
751
|
+
? "尚未记录推理请求"
|
|
752
|
+
: "最近推理请求于 " + formatWaitTime(Math.max(0, now - account.lastInferenceRequestAt)) + " 前";
|
|
753
|
+
logInfo(account.kind.toUpperCase() + " 账号 " + (account.index + 1) + ":" + account.label
|
|
754
|
+
+ (account.enabled ? "" : "(已禁用)") + " " + last);
|
|
755
|
+
logInfo(" 优先级:" + account.priority + ";可见模型:" + account.visibleModelCount + " 个");
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
return 0;
|
|
759
|
+
}
|
|
272
760
|
if (runtimeSnapshot) {
|
|
273
761
|
const runtimeMetrics = runtimeSnapshot.runtimeMetrics;
|
|
274
762
|
const poolCooldown = typeof runtimeSnapshot.poolExhaustionCooldownUntil === "number" &&
|
|
@@ -345,7 +833,7 @@ export async function runStatusCommand(deps) {
|
|
|
345
833
|
if (appBindStatus?.nativeOpenai) {
|
|
346
834
|
const preference = quotaPreferences[i];
|
|
347
835
|
const order = automaticOrder.get(i);
|
|
348
|
-
const reset = preference?.resetAtMs ? `; limiting window resets in ${formatWaitTime(Math.max(0, preference.resetAtMs - now))}` : "; reset
|
|
836
|
+
const reset = preference?.resetAtMs ? `; limiting window resets in ${formatWaitTime(Math.max(0, preference.resetAtMs - now))}` : "; quota reset unknown; run check";
|
|
349
837
|
const reserve = usesSubscriptionReserve(preference) ? `; ${SUBSCRIPTION_RESERVE_PERCENT}% reserve (last resort)` : "";
|
|
350
838
|
const pinnedElsewhere = typeof storage.pinnedAccountIndex === "number" && storage.pinnedAccountIndex !== i;
|
|
351
839
|
logInfo(paint(` automatic order: ${order ? `#${order}` : pinnedElsewhere ? `none (account ${(storage.pinnedAccountIndex ?? 0) + 1} is pinned)` : "unavailable"}${reset}${reserve}`, reserve ? "warning" : "accent"));
|
|
@@ -354,7 +842,7 @@ export async function runStatusCommand(deps) {
|
|
|
354
842
|
if (quota) {
|
|
355
843
|
const windows = ["primary", "secondary"].flatMap(key => {
|
|
356
844
|
const used = quota[key].usedPercent;
|
|
357
|
-
return typeof used === "number" && Number.isFinite(used) ? [paint(
|
|
845
|
+
return typeof used === "number" && Number.isFinite(used) ? [paint(formatQuotaWindow(key, quota[key], "en"), used >= 95 ? "danger" : used >= 80 ? "warning" : "success")] : [];
|
|
358
846
|
});
|
|
359
847
|
logInfo(` quota: ${windows.join("; ")} (${paint(`cached quota ${formatWaitTime(Math.max(0, now - quota.updatedAt))} ago`, "muted")})`);
|
|
360
848
|
}
|
|
@@ -7,7 +7,8 @@ export function printUsage() {
|
|
|
7
7
|
"Start here:",
|
|
8
8
|
" codex-multi-auth login [--device-auth|--manual|--no-browser] [--org <org_id>] [--preserve-selection] [--account <index|email|account_id>]",
|
|
9
9
|
" codex-multi-auth login --api (API credentials and visible models)",
|
|
10
|
-
" codex-multi-auth status [--json] (list is the same command)",
|
|
10
|
+
" codex-multi-auth status [--json|--details] (list is the same command)",
|
|
11
|
+
" codex-multi-auth web [--port 4317] [--no-open] (local real-time pool dashboard)",
|
|
11
12
|
" codex-multi-auth check [accounts|resets|capabilities] [--prime] (default: all checks)",
|
|
12
13
|
" codex-multi-auth limits --json [--refresh] (structured quota windows; refresh is age-gated)",
|
|
13
14
|
"",
|
|
@@ -41,6 +42,7 @@ export function printUsage() {
|
|
|
41
42
|
" codex-multi-auth rotation reset-rate-limits [--all | --account <idx>] [--dry-run] [--json]",
|
|
42
43
|
" codex-multi-auth why-selected [--now|-n | --last|-l] [--json]",
|
|
43
44
|
" codex-multi-auth history [list|show <id>] [--json] (all local sessions, ignores /resume provider filtering)",
|
|
45
|
+
" codex-multi-auth config locale [auto|en|zh] (language; auto follows the system locale)",
|
|
44
46
|
"",
|
|
45
47
|
"Advanced:",
|
|
46
48
|
" codex-multi-auth report [--live] [--json] [--explain] [--model <model>] [--max-accounts <n>] [--max-probes <n>] [--cached-only] [--out <path>]",
|