Harden usage dashboard and codex auth
This commit is contained in:
@@ -108,7 +108,10 @@ async function fetchUsageForToken(
|
|||||||
|
|
||||||
if (res.status === 401) {
|
if (res.status === 401) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
{ account: accountIndex != null ? accountIndex + 1 : '?', tokenKey: key },
|
{
|
||||||
|
account: accountIndex != null ? accountIndex + 1 : '?',
|
||||||
|
tokenKey: key,
|
||||||
|
},
|
||||||
'Claude usage API: token expired or invalid (401)',
|
'Claude usage API: token expired or invalid (401)',
|
||||||
);
|
);
|
||||||
return null;
|
return null;
|
||||||
@@ -116,7 +119,11 @@ async function fetchUsageForToken(
|
|||||||
if (res.status === 429) {
|
if (res.status === 429) {
|
||||||
const staleMs = cached ? Date.now() - cached.fetchedAt : 0;
|
const staleMs = cached ? Date.now() - cached.fetchedAt : 0;
|
||||||
logger.warn(
|
logger.warn(
|
||||||
{ account: accountIndex != null ? accountIndex + 1 : '?', tokenKey: key, staleMinutes: Math.round(staleMs / 60_000) },
|
{
|
||||||
|
account: accountIndex != null ? accountIndex + 1 : '?',
|
||||||
|
tokenKey: key,
|
||||||
|
staleMinutes: Math.round(staleMs / 60_000),
|
||||||
|
},
|
||||||
'Claude usage API: rate limited (429), returning cached data',
|
'Claude usage API: rate limited (429), returning cached data',
|
||||||
);
|
);
|
||||||
// Record attempt time so we don't retry for MIN_FETCH_INTERVAL_MS
|
// Record attempt time so we don't retry for MIN_FETCH_INTERVAL_MS
|
||||||
@@ -128,7 +135,11 @@ async function fetchUsageForToken(
|
|||||||
}
|
}
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
{ account: accountIndex != null ? accountIndex + 1 : '?', status: res.status, tokenKey: key },
|
{
|
||||||
|
account: accountIndex != null ? accountIndex + 1 : '?',
|
||||||
|
status: res.status,
|
||||||
|
tokenKey: key,
|
||||||
|
},
|
||||||
`Claude usage API: unexpected status ${res.status}`,
|
`Claude usage API: unexpected status ${res.status}`,
|
||||||
);
|
);
|
||||||
if (cached) {
|
if (cached) {
|
||||||
@@ -153,7 +164,11 @@ async function fetchUsageForToken(
|
|||||||
saveUsageDiskCache();
|
saveUsageDiskCache();
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
{ tokenKey: key, h5: result.five_hour?.utilization, d7: result.seven_day?.utilization },
|
{
|
||||||
|
tokenKey: key,
|
||||||
|
h5: result.five_hour?.utilization,
|
||||||
|
d7: result.seven_day?.utilization,
|
||||||
|
},
|
||||||
'Claude usage API: fetched successfully',
|
'Claude usage API: fetched successfully',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -344,7 +344,12 @@ export function updateCodexAccountUsage(
|
|||||||
saveCodexState();
|
saveCodexState();
|
||||||
|
|
||||||
// Auto-rotate away from 7d-exhausted current account to avoid API billing
|
// Auto-rotate away from 7d-exhausted current account to avoid API billing
|
||||||
if (idx === currentIndex && d7Pct != null && d7Pct >= 100 && accounts.length > 1) {
|
if (
|
||||||
|
idx === currentIndex &&
|
||||||
|
d7Pct != null &&
|
||||||
|
d7Pct >= 100 &&
|
||||||
|
accounts.length > 1
|
||||||
|
) {
|
||||||
const nextIdx = findNextCodexAvailable(idx);
|
const nextIdx = findNextCodexAvailable(idx);
|
||||||
if (nextIdx !== null && nextIdx !== idx) {
|
if (nextIdx !== null && nextIdx !== idx) {
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|||||||
@@ -33,15 +33,15 @@ export function stripInternalTags(text: string): string {
|
|||||||
* Matched strings are replaced with `[REDACTED]`.
|
* Matched strings are replaced with `[REDACTED]`.
|
||||||
*/
|
*/
|
||||||
const SECRET_PATTERNS: RegExp[] = [
|
const SECRET_PATTERNS: RegExp[] = [
|
||||||
/sk-ant-[A-Za-z0-9_-]{20,}/g, // Anthropic
|
/sk-ant-[A-Za-z0-9_-]{20,}/g, // Anthropic
|
||||||
/sk-[A-Za-z0-9_-]{20,}/g, // OpenAI
|
/sk-[A-Za-z0-9_-]{20,}/g, // OpenAI
|
||||||
/gsk_[A-Za-z0-9_-]{20,}/g, // Groq
|
/gsk_[A-Za-z0-9_-]{20,}/g, // Groq
|
||||||
/xai-[A-Za-z0-9_-]{20,}/g, // xAI
|
/xai-[A-Za-z0-9_-]{20,}/g, // xAI
|
||||||
/ghp_[A-Za-z0-9_]{36,}/g, // GitHub PAT classic
|
/ghp_[A-Za-z0-9_]{36,}/g, // GitHub PAT classic
|
||||||
/github_pat_[A-Za-z0-9_]{20,}/g, // GitHub PAT fine-grained
|
/github_pat_[A-Za-z0-9_]{20,}/g, // GitHub PAT fine-grained
|
||||||
/glpat-[A-Za-z0-9_-]{20,}/g, // GitLab PAT
|
/glpat-[A-Za-z0-9_-]{20,}/g, // GitLab PAT
|
||||||
/AKIA[A-Z0-9]{16}/g, // AWS Access Key
|
/AKIA[A-Z0-9]{16}/g, // AWS Access Key
|
||||||
/Bearer\s+eyJ[A-Za-z0-9_-]{40,}/g, // Bearer JWT
|
/Bearer\s+eyJ[A-Za-z0-9_-]{40,}/g, // Bearer JWT
|
||||||
];
|
];
|
||||||
|
|
||||||
function redactSecrets(text: string): string {
|
function redactSecrets(text: string): string {
|
||||||
|
|||||||
@@ -558,8 +558,9 @@ async function buildUsageContent(): Promise<string> {
|
|||||||
|
|
||||||
// Codex usage: read from Codex service's own status snapshot.
|
// Codex usage: read from Codex service's own status snapshot.
|
||||||
// Each service owns its usage data — no cross-service auth access needed.
|
// Each service owns its usage data — no cross-service auth access needed.
|
||||||
const codexSnapshot = readStatusSnapshots(STATUS_SNAPSHOT_MAX_AGE_MS)
|
const codexSnapshot = readStatusSnapshots(STATUS_SNAPSHOT_MAX_AGE_MS).find(
|
||||||
.find(s => s.agentType === 'codex');
|
(s) => s.agentType === 'codex',
|
||||||
|
);
|
||||||
rows.push(...extractCodexUsageRows(codexSnapshot, USAGE_SNAPSHOT_MAX_AGE_MS));
|
rows.push(...extractCodexUsageRows(codexSnapshot, USAGE_SNAPSHOT_MAX_AGE_MS));
|
||||||
|
|
||||||
if (rows.length > 0) {
|
if (rows.length > 0) {
|
||||||
@@ -720,7 +721,13 @@ function applyCodexUsageToAccount(
|
|||||||
: undefined;
|
: undefined;
|
||||||
updateCodexAccountUsage(pct, resetStr, accountIndex, d7Pct, resetD7Str);
|
updateCodexAccountUsage(pct, resetStr, accountIndex, d7Pct, resetD7Str);
|
||||||
logger.info(
|
logger.info(
|
||||||
{ account: accountIndex + 1, bucket: effective.limitId, h5: pct, d7: d7Pct, reset: resetStr },
|
{
|
||||||
|
account: accountIndex + 1,
|
||||||
|
bucket: effective.limitId,
|
||||||
|
h5: pct,
|
||||||
|
d7: d7Pct,
|
||||||
|
reset: resetStr,
|
||||||
|
},
|
||||||
`Codex account #${accountIndex + 1} usage: 5h=${pct}% 7d=${d7Pct}%`,
|
`Codex account #${accountIndex + 1} usage: 5h=${pct}% 7d=${d7Pct}%`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -895,7 +902,9 @@ export async function startUnifiedDashboard(
|
|||||||
} else {
|
} else {
|
||||||
// Codex service: fetch own usage and expose via status snapshot.
|
// Codex service: fetch own usage and expose via status snapshot.
|
||||||
// Active account every usageUpdateInterval; full scan on startup + hourly.
|
// Active account every usageUpdateInterval; full scan on startup + hourly.
|
||||||
void refreshAllCodexAccountUsage().then(() => void refreshActiveCodexUsage());
|
void refreshAllCodexAccountUsage().then(
|
||||||
|
() => void refreshActiveCodexUsage(),
|
||||||
|
);
|
||||||
setInterval(() => void refreshActiveCodexUsage(), opts.usageUpdateInterval);
|
setInterval(() => void refreshActiveCodexUsage(), opts.usageUpdateInterval);
|
||||||
setInterval(
|
setInterval(
|
||||||
() => void refreshAllCodexAccountUsage(),
|
() => void refreshAllCodexAccountUsage(),
|
||||||
|
|||||||
Reference in New Issue
Block a user