Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
296 changes: 293 additions & 3 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4156,9 +4156,299 @@ while (attempted.size < Math.max(1, accountCount)) {
})}`,
);

return lines.join("\n");
},
}),
return lines.join("\n");
},
}),
"codex-limits": tool({
description: "Show live 5-hour and weekly Codex usage limits for all accounts.",
args: {},
async execute() {
const ui = resolveUiRuntime();
const storage = await loadAccounts();
if (!storage || storage.accounts.length === 0) {
if (ui.v2Enabled) {
return [
...formatUiHeader(ui, "Codex limits"),
"",
formatUiItem(ui, "No accounts configured.", "warning"),
formatUiItem(ui, "Run: opencode auth login", "accent"),
].join("\n");
}
return "No Codex accounts configured. Run: opencode auth login";
}

type UsageWindow = {
used_percent?: number;
limit_window_seconds?: number;
reset_at?: number;
reset_after_seconds?: number;
} | null;

type LimitWindow = {
usedPercent?: number;
windowMinutes?: number;
resetAtMs?: number;
};

type UsageRateLimit = {
primary_window?: UsageWindow;
secondary_window?: UsageWindow;
} | null;

type UsageCredits = {
has_credits?: boolean;
unlimited?: boolean;
balance?: string | null;
} | null;

type UsagePayload = {
plan_type?: string;
rate_limit?: UsageRateLimit;
code_review_rate_limit?: UsageRateLimit;
additional_rate_limits?: Array<{
limit_name?: string;
metered_feature?: string;
rate_limit?: UsageRateLimit;
}> | null;
credits?: UsageCredits;
};

const formatWindowLabel = (windowMinutes: number | undefined): string => {
if (!windowMinutes || !Number.isFinite(windowMinutes) || windowMinutes <= 0) {
return "quota";
}
if (windowMinutes % 1440 === 0) return `${windowMinutes / 1440}d`;
if (windowMinutes % 60 === 0) return `${windowMinutes / 60}h`;
return `${windowMinutes}m`;
};

const formatReset = (resetAtMs: number | undefined): string | undefined => {
if (!resetAtMs || !Number.isFinite(resetAtMs) || resetAtMs <= 0) return undefined;
const date = new Date(resetAtMs);
if (!Number.isFinite(date.getTime())) return undefined;

const now = new Date();
const sameDay =
now.getFullYear() === date.getFullYear() &&
now.getMonth() === date.getMonth() &&
now.getDate() === date.getDate();
const time = date.toLocaleTimeString(undefined, {
hour: "2-digit",
minute: "2-digit",
hour12: false,
});
if (sameDay) return time;
const day = date.toLocaleDateString(undefined, { month: "short", day: "2-digit" });
return `${time} on ${day}`;
};

const mapWindow = (window: UsageWindow): LimitWindow => {
if (!window) return {};
return {
usedPercent:
typeof window.used_percent === "number" && Number.isFinite(window.used_percent)
? window.used_percent
: undefined,
windowMinutes:
typeof window.limit_window_seconds === "number" && Number.isFinite(window.limit_window_seconds)
? Math.max(1, Math.ceil(window.limit_window_seconds / 60))
: undefined,
resetAtMs:
typeof window.reset_at === "number" && window.reset_at > 0
? window.reset_at * 1000
: typeof window.reset_after_seconds === "number" && window.reset_after_seconds > 0
? Date.now() + window.reset_after_seconds * 1000
: undefined,
};
};

const formatLimitTitle = (windowMinutes: number | undefined, fallback = "quota"): string => {
if (windowMinutes === 300) return "5h limit";
if (windowMinutes === 10080) return "Weekly limit";
if (fallback !== "quota") return fallback;
return `${formatWindowLabel(windowMinutes)} limit`;
};

const formatLimitSummary = (window: LimitWindow): string => {
const used = window.usedPercent;
const left =
typeof used === "number" && Number.isFinite(used)
? Math.max(0, Math.min(100, Math.round(100 - used)))
: undefined;
const reset = formatReset(window.resetAtMs);
if (left !== undefined && reset) return `${left}% left (resets ${reset})`;
if (left !== undefined) return `${left}% left`;
if (reset) return `resets ${reset}`;
return "unavailable";
};

const formatCredits = (credits: UsageCredits): string | undefined => {
if (!credits) return undefined;
if (credits.unlimited) return "unlimited";
if (typeof credits.balance === "string" && credits.balance.trim()) {
return credits.balance.trim();
}
if (credits.has_credits) return "available";
return undefined;
};

const formatExtraName = (name: string | undefined): string => {
if (!name) return "Additional limit";
if (name === "code_review_rate_limit") return "Code review";
return name.replace(/[_-]+/g, " ").replace(/\b\w/g, (match) => match.toUpperCase());
};

const fetchUsage = async (params: {
accountId: string;
accessToken: string;
organizationId: string | undefined;
}): Promise<UsagePayload> => {
const headers = createCodexHeaders(undefined, params.accountId, params.accessToken, {
organizationId: params.organizationId,
});
headers.set("accept", "application/json");

const response = await fetch(`${CODEX_BASE_URL}/wham/usage`, {
method: "GET",
headers,
});
if (!response.ok) {
const bodyText = await response.text().catch(() => "");
throw new Error(bodyText || `HTTP ${response.status}`);
Comment on lines +4315 to +4317
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Raw API Error Body Surfaced to User Without Redaction

bodyText is the raw response body from the remote /wham/usage endpoint. it's thrown directly as the error message and then shown to the user via message.slice(0, 160) in the catch block. on windows this is also a token-leakage risk: if the api ever echoes back request headers or partial credential details in a 4xx body, they'd be written to screen output and potentially captured by logging middleware or AV scanners that hook stdout.

prefer discarding the raw body and surfacing only the status code, or at minimum strip anything that looks like a bearer token before throwing:

Suggested change
if (!response.ok) {
const bodyText = await response.text().catch(() => "");
throw new Error(bodyText || `HTTP ${response.status}`);
if (!response.ok) {
const bodyText = await response.text().catch(() => "");
const safe = bodyText.replace(/Bearer\s+\S+/gi, "[redacted]").slice(0, 200);
throw new Error(safe || `HTTP ${response.status}`);
}

Rule Used: What: Every code change must explain how it defend... (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: index.ts
Line: 4315-4317

Comment:
**Raw API Error Body Surfaced to User Without Redaction**

`bodyText` is the raw response body from the remote `/wham/usage` endpoint. it's thrown directly as the error message and then shown to the user via `message.slice(0, 160)` in the catch block. on windows this is also a token-leakage risk: if the api ever echoes back request headers or partial credential details in a 4xx body, they'd be written to screen output and potentially captured by logging middleware or AV scanners that hook stdout.

prefer discarding the raw body and surfacing only the status code, or at minimum strip anything that looks like a bearer token before throwing:

```suggestion
						if (!response.ok) {
							const bodyText = await response.text().catch(() => "");
							const safe = bodyText.replace(/Bearer\s+\S+/gi, "[redacted]").slice(0, 200);
							throw new Error(safe || `HTTP ${response.status}`);
						}
```

**Rule Used:** What: Every code change must explain how it defend... ([source](https://app.greptile.com/review/custom-context?memory=637a42e6-7a78-40d6-9ef8-6a45e02e73b6))

How can I resolve this? If you propose a fix, please make it concise.

}
return (await response.json()) as UsagePayload;
};
Comment thread
ndycode marked this conversation as resolved.

// Deduplicate accounts by refreshToken (same credential = same limits)
const seenTokens = new Set<string>();
const uniqueIndices: number[] = [];
for (let i = 0; i < storage.accounts.length; i++) {
const acct = storage.accounts[i];
if (!acct) continue;
if (seenTokens.has(acct.refreshToken)) continue;
seenTokens.add(acct.refreshToken);
uniqueIndices.push(i);
Comment thread
ndycode marked this conversation as resolved.
}
Comment thread
ndycode marked this conversation as resolved.

const lines: string[] = ui.v2Enabled
? [...formatUiHeader(ui, "Codex limits"), ""]
: [`Codex limits (${uniqueIndices.length} account${uniqueIndices.length === 1 ? "" : "s"}):`, ""];
const activeIndex = resolveActiveIndex(storage, "codex");
let storageChanged = false;

for (const i of uniqueIndices) {
const account = storage.accounts[i];
if (!account) continue;
const label = formatCommandAccountLabel(account, i);
const activeSuffix = i === activeIndex ? (ui.v2Enabled ? ` ${formatUiBadge(ui, "active", "accent")}` : " [active]") : "";
Comment thread
ndycode marked this conversation as resolved.
Comment thread
ndycode marked this conversation as resolved.

try {
let accessToken = account.accessToken;
if (
typeof accessToken !== "string" ||
!accessToken ||
typeof account.expiresAt !== "number" ||
account.expiresAt <= Date.now() + 30_000
) {
const refreshResult = await queuedRefresh(account.refreshToken);
if (refreshResult.type !== "success") {
throw new Error(refreshResult.message ?? refreshResult.reason);
}
account.refreshToken = refreshResult.refresh;
account.accessToken = refreshResult.access;
account.expiresAt = refreshResult.expires;
accessToken = refreshResult.access;
Comment thread
ndycode marked this conversation as resolved.
storageChanged = true;
}
Comment thread
ndycode marked this conversation as resolved.

const accountId = account.accountId ?? extractAccountId(accessToken);
if (!accountId) {
throw new Error("Missing account id");
}

const payload = await fetchUsage({
accountId,
accessToken,
organizationId: account.organizationId,
});

const primary = mapWindow(payload.rate_limit?.primary_window ?? null);
const secondary = mapWindow(payload.rate_limit?.secondary_window ?? null);
const codeReviewRateLimit =
payload.code_review_rate_limit ??
payload.additional_rate_limits?.find((entry) => entry.limit_name === "code_review_rate_limit")?.rate_limit ??
null;
const codeReview = mapWindow(codeReviewRateLimit?.primary_window ?? null);
const credits = formatCredits(payload.credits ?? null);
Comment thread
ndycode marked this conversation as resolved.
const additionalLimits = (payload.additional_rate_limits ?? []).filter(
(entry) => entry.limit_name !== "code_review_rate_limit",
);

if (ui.v2Enabled) {
lines.push(formatUiItem(ui, `${label}${activeSuffix}`));
lines.push(` ${formatUiKeyValue(ui, formatLimitTitle(primary.windowMinutes), formatLimitSummary(primary), "muted")}`);
lines.push(` ${formatUiKeyValue(ui, formatLimitTitle(secondary.windowMinutes), formatLimitSummary(secondary), "muted")}`);
if (codeReview.windowMinutes || typeof codeReview.usedPercent === "number" || codeReview.resetAtMs) {
lines.push(` ${formatUiKeyValue(ui, "Code review", formatLimitSummary(codeReview), "muted")}`);
}
for (const limit of additionalLimits) {
const extraWindow = mapWindow(limit.rate_limit?.primary_window ?? null);
lines.push(` ${formatUiKeyValue(ui, formatExtraName(limit.limit_name ?? limit.metered_feature), formatLimitSummary(extraWindow), "muted")}`);
}
if (payload.plan_type) {
lines.push(` ${formatUiKeyValue(ui, "Plan", payload.plan_type, "muted")}`);
}
if (credits) {
lines.push(` ${formatUiKeyValue(ui, "Credits", credits, "muted")}`);
}
} else {
lines.push(`${label}${activeSuffix}:`);
lines.push(` ${formatLimitTitle(primary.windowMinutes)}: ${formatLimitSummary(primary)}`);
lines.push(` ${formatLimitTitle(secondary.windowMinutes)}: ${formatLimitSummary(secondary)}`);
if (codeReview.windowMinutes || typeof codeReview.usedPercent === "number" || codeReview.resetAtMs) {
lines.push(` Code review: ${formatLimitSummary(codeReview)}`);
}
for (const limit of additionalLimits) {
const extraWindow = mapWindow(limit.rate_limit?.primary_window ?? null);
lines.push(` ${formatExtraName(limit.limit_name ?? limit.metered_feature)}: ${formatLimitSummary(extraWindow)}`);
}
if (payload.plan_type) {
lines.push(` Plan: ${payload.plan_type}`);
}
if (credits) {
lines.push(` Credits: ${credits}`);
}
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
if (ui.v2Enabled) {
lines.push(formatUiItem(ui, `${label}${activeSuffix}`));
lines.push(` ${formatUiKeyValue(ui, "Error", message.slice(0, 160), "danger")}`);
} else {
lines.push(`${label}${activeSuffix}:`);
lines.push(` Error: ${message.slice(0, 160)}`);
Comment thread
ndycode marked this conversation as resolved.
}
}

lines.push("");
}

if (storageChanged) {
await saveAccounts(storage);
if (cachedAccountManager) {
const reloadedManager = await AccountManager.loadFromDisk();
cachedAccountManager = reloadedManager;
accountManagerPromise = Promise.resolve(reloadedManager);
}
}

while (lines.length > 0 && lines[lines.length - 1] === "") {
lines.pop();
}

return lines.join("\n");
},
}),
"codex-metrics": tool({
description: "Show runtime request metrics for this plugin process.",
args: {},
Expand Down
Loading