refactor(infra): split provider usage
This commit is contained in:
102
src/infra/provider-usage.fetch.codex.ts
Normal file
102
src/infra/provider-usage.fetch.codex.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { fetchJson } from "./provider-usage.fetch.shared.js";
|
||||
import { clampPercent, PROVIDER_LABELS } from "./provider-usage.shared.js";
|
||||
import type {
|
||||
ProviderUsageSnapshot,
|
||||
UsageWindow,
|
||||
} from "./provider-usage.types.js";
|
||||
|
||||
type CodexUsageResponse = {
|
||||
rate_limit?: {
|
||||
primary_window?: {
|
||||
limit_window_seconds?: number;
|
||||
used_percent?: number;
|
||||
reset_at?: number;
|
||||
};
|
||||
secondary_window?: {
|
||||
limit_window_seconds?: number;
|
||||
used_percent?: number;
|
||||
reset_at?: number;
|
||||
};
|
||||
};
|
||||
plan_type?: string;
|
||||
credits?: { balance?: number | string | null };
|
||||
};
|
||||
|
||||
export async function fetchCodexUsage(
|
||||
token: string,
|
||||
accountId: string | undefined,
|
||||
timeoutMs: number,
|
||||
fetchFn: typeof fetch,
|
||||
): Promise<ProviderUsageSnapshot> {
|
||||
const headers: Record<string, string> = {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"User-Agent": "CodexBar",
|
||||
Accept: "application/json",
|
||||
};
|
||||
if (accountId) headers["ChatGPT-Account-Id"] = accountId;
|
||||
|
||||
const res = await fetchJson(
|
||||
"https://chatgpt.com/backend-api/wham/usage",
|
||||
{ method: "GET", headers },
|
||||
timeoutMs,
|
||||
fetchFn,
|
||||
);
|
||||
|
||||
if (res.status === 401 || res.status === 403) {
|
||||
return {
|
||||
provider: "openai-codex",
|
||||
displayName: PROVIDER_LABELS["openai-codex"],
|
||||
windows: [],
|
||||
error: "Token expired",
|
||||
};
|
||||
}
|
||||
|
||||
if (!res.ok) {
|
||||
return {
|
||||
provider: "openai-codex",
|
||||
displayName: PROVIDER_LABELS["openai-codex"],
|
||||
windows: [],
|
||||
error: `HTTP ${res.status}`,
|
||||
};
|
||||
}
|
||||
|
||||
const data = (await res.json()) as CodexUsageResponse;
|
||||
const windows: UsageWindow[] = [];
|
||||
|
||||
if (data.rate_limit?.primary_window) {
|
||||
const pw = data.rate_limit.primary_window;
|
||||
const windowHours = Math.round((pw.limit_window_seconds || 10800) / 3600);
|
||||
windows.push({
|
||||
label: `${windowHours}h`,
|
||||
usedPercent: clampPercent(pw.used_percent || 0),
|
||||
resetAt: pw.reset_at ? pw.reset_at * 1000 : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
if (data.rate_limit?.secondary_window) {
|
||||
const sw = data.rate_limit.secondary_window;
|
||||
const windowHours = Math.round((sw.limit_window_seconds || 86400) / 3600);
|
||||
const label = windowHours >= 24 ? "Day" : `${windowHours}h`;
|
||||
windows.push({
|
||||
label,
|
||||
usedPercent: clampPercent(sw.used_percent || 0),
|
||||
resetAt: sw.reset_at ? sw.reset_at * 1000 : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
let plan = data.plan_type;
|
||||
if (data.credits?.balance !== undefined && data.credits.balance !== null) {
|
||||
const balance =
|
||||
typeof data.credits.balance === "number"
|
||||
? data.credits.balance
|
||||
: parseFloat(data.credits.balance) || 0;
|
||||
plan = plan ? `${plan} ($${balance.toFixed(2)})` : `$${balance.toFixed(2)}`;
|
||||
}
|
||||
|
||||
return {
|
||||
provider: "openai-codex",
|
||||
displayName: PROVIDER_LABELS["openai-codex"],
|
||||
windows,
|
||||
plan,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user