From 4b06f45b2c1645f0ee600e6ed770a63c6539e65b Mon Sep 17 00:00:00 2001 From: Evan Reichard Date: Sun, 3 May 2026 11:56:46 -0400 Subject: [PATCH] chore: format usage --- usage/claude.ts | 550 ++++++++++++++++++++----------------- usage/openai-codex.ts | 621 +++++++++++++++++++++++------------------- usage/shared.ts | 16 +- usage/zai.ts | 411 ++++++++++++++-------------- 4 files changed, 853 insertions(+), 745 deletions(-) diff --git a/usage/claude.ts b/usage/claude.ts index 43022fe..e13a216 100644 --- a/usage/claude.ts +++ b/usage/claude.ts @@ -1,13 +1,13 @@ import type { - CredentialRankingStrategy, - UsageAmount, - UsageFetchContext, - UsageFetchParams, - UsageLimit, - UsageProvider, - UsageReport, - UsageStatus, - UsageWindow, + CredentialRankingStrategy, + UsageAmount, + UsageFetchContext, + UsageFetchParams, + UsageLimit, + UsageProvider, + UsageReport, + UsageStatus, + UsageWindow, } from "../usage"; import { isRecord, toNumber } from "../utils"; @@ -18,320 +18,358 @@ const MAX_RETRIES = 3; const BASE_RETRY_DELAY_MS = 500; const CLAUDE_HEADERS = { - accept: "application/json, text/plain, */*", - "accept-encoding": "gzip, compress, deflate, br", - "anthropic-beta": - "claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,context-management-2025-06-27,prompt-caching-scope-2026-01-05", - "content-type": "application/json", - "user-agent": "claude-cli/2.1.63 (external, cli)", - connection: "keep-alive", + accept: "application/json, text/plain, */*", + "accept-encoding": "gzip, compress, deflate, br", + "anthropic-beta": + "claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,context-management-2025-06-27,prompt-caching-scope-2026-01-05", + "content-type": "application/json", + "user-agent": "claude-cli/2.1.63 (external, cli)", + connection: "keep-alive", } as const; function normalizeClaudeBaseUrl(baseUrl?: string): string { - if (!baseUrl?.trim()) return DEFAULT_ENDPOINT; - const trimmed = baseUrl.trim().replace(/\/+$/, ""); - const lower = trimmed.toLowerCase(); - if (lower.endsWith("/api/oauth")) return trimmed; - let url: URL; - try { - url = new URL(trimmed); - } catch { - return DEFAULT_ENDPOINT; - } - let path = url.pathname.replace(/\/+$/, ""); - if (path === "/") path = ""; - if (path.toLowerCase().endsWith("/v1")) { - path = path.slice(0, -3); - } - if (!path) return `${url.origin}/api/oauth`; - return `${url.origin}${path}/api/oauth`; + if (!baseUrl?.trim()) return DEFAULT_ENDPOINT; + const trimmed = baseUrl.trim().replace(/\/+$/, ""); + const lower = trimmed.toLowerCase(); + if (lower.endsWith("/api/oauth")) return trimmed; + let url: URL; + try { + url = new URL(trimmed); + } catch { + return DEFAULT_ENDPOINT; + } + let path = url.pathname.replace(/\/+$/, ""); + if (path === "/") path = ""; + if (path.toLowerCase().endsWith("/v1")) { + path = path.slice(0, -3); + } + if (!path) return `${url.origin}/api/oauth`; + return `${url.origin}${path}/api/oauth`; } interface ClaudeUsageBucket { - utilization?: number; - resets_at?: string; + utilization?: number; + resets_at?: string; } interface ParsedUsageBucket { - utilization?: number; - resetsAt?: number; + utilization?: number; + resetsAt?: number; } interface ClaudeUsageResponse { - five_hour?: ClaudeUsageBucket | null; - seven_day?: ClaudeUsageBucket | null; - seven_day_opus?: ClaudeUsageBucket | null; - seven_day_sonnet?: ClaudeUsageBucket | null; + five_hour?: ClaudeUsageBucket | null; + seven_day?: ClaudeUsageBucket | null; + seven_day_opus?: ClaudeUsageBucket | null; + seven_day_sonnet?: ClaudeUsageBucket | null; } type ClaudeUsagePayload = { - payload: ClaudeUsageResponse; - orgId?: string; + payload: ClaudeUsageResponse; + orgId?: string; }; function parseIsoTime(value: string | undefined): number | undefined { - if (!value) return undefined; - const parsed = Date.parse(value); - return Number.isFinite(parsed) ? parsed : undefined; + if (!value) return undefined; + const parsed = Date.parse(value); + return Number.isFinite(parsed) ? parsed : undefined; } function parseBucket(bucket: unknown): ParsedUsageBucket | undefined { - if (!isRecord(bucket)) return undefined; - const utilization = toNumber(bucket.utilization); - const resetsAt = parseIsoTime(typeof bucket.resets_at === "string" ? bucket.resets_at : undefined); - if (utilization === undefined && resetsAt === undefined) { - return undefined; - } - return { utilization, resetsAt }; + if (!isRecord(bucket)) return undefined; + const utilization = toNumber(bucket.utilization); + const resetsAt = parseIsoTime( + typeof bucket.resets_at === "string" ? bucket.resets_at : undefined, + ); + if (utilization === undefined && resetsAt === undefined) { + return undefined; + } + return { utilization, resetsAt }; } -function getPayloadString(payload: Record, key: string): string | undefined { - const value = payload[key]; - return typeof value === "string" && value.trim() ? value.trim() : undefined; +function getPayloadString( + payload: Record, + key: string, +): string | undefined { + const value = payload[key]; + return typeof value === "string" && value.trim() ? value.trim() : undefined; } -function extractUsageIdentity(payload: ClaudeUsageResponse, orgId?: string): { accountId?: string; email?: string } { - if (!isRecord(payload)) return { accountId: orgId }; - const accountId = - getPayloadString(payload, "account_id") ?? - getPayloadString(payload, "accountId") ?? - getPayloadString(payload, "user_id") ?? - getPayloadString(payload, "userId") ?? - getPayloadString(payload, "org_id") ?? - getPayloadString(payload, "orgId") ?? - orgId; - const email = - getPayloadString(payload, "email") ?? - getPayloadString(payload, "user_email") ?? - getPayloadString(payload, "userEmail"); - return { accountId, email }; +function extractUsageIdentity( + payload: ClaudeUsageResponse, + orgId?: string, +): { accountId?: string; email?: string } { + if (!isRecord(payload)) return { accountId: orgId }; + const accountId = + getPayloadString(payload, "account_id") ?? + getPayloadString(payload, "accountId") ?? + getPayloadString(payload, "user_id") ?? + getPayloadString(payload, "userId") ?? + getPayloadString(payload, "org_id") ?? + getPayloadString(payload, "orgId") ?? + orgId; + const email = + getPayloadString(payload, "email") ?? + getPayloadString(payload, "user_email") ?? + getPayloadString(payload, "userEmail"); + return { accountId, email }; } function hasUsageData(payload: ClaudeUsageResponse): boolean { - return Boolean(payload.five_hour || payload.seven_day || payload.seven_day_opus || payload.seven_day_sonnet); + return Boolean( + payload.five_hour || + payload.seven_day || + payload.seven_day_opus || + payload.seven_day_sonnet, + ); } async function fetchUsagePayload( - url: string, - headers: Record, - ctx: UsageFetchContext, - signal?: AbortSignal, + url: string, + headers: Record, + ctx: UsageFetchContext, + signal?: AbortSignal, ): Promise { - let lastPayload: ClaudeUsageResponse | null = null; - let lastOrgId: string | undefined; - for (let attempt = 0; attempt < MAX_RETRIES; attempt++) { - try { - const response = await ctx.fetch(url, { headers, signal }); - if (!response.ok) { - ctx.logger?.warn("Claude usage fetch failed", { status: response.status, statusText: response.statusText }); - return null; - } - const payload = (await response.json()) as ClaudeUsageResponse; - lastPayload = payload; - const orgId = response.headers.get("anthropic-organization-id")?.trim() || undefined; - lastOrgId = orgId ?? lastOrgId; - if (payload && isRecord(payload) && hasUsageData(payload)) { - return { payload, orgId }; - } - } catch (error) { - ctx.logger?.warn("Claude usage fetch error", { error: String(error) }); - return null; - } + let lastPayload: ClaudeUsageResponse | null = null; + let lastOrgId: string | undefined; + for (let attempt = 0; attempt < MAX_RETRIES; attempt++) { + try { + const response = await ctx.fetch(url, { headers, signal }); + if (!response.ok) { + ctx.logger?.warn("Claude usage fetch failed", { + status: response.status, + statusText: response.statusText, + }); + return null; + } + const payload = (await response.json()) as ClaudeUsageResponse; + lastPayload = payload; + const orgId = + response.headers.get("anthropic-organization-id")?.trim() || undefined; + lastOrgId = orgId ?? lastOrgId; + if (payload && isRecord(payload) && hasUsageData(payload)) { + return { payload, orgId }; + } + } catch (error) { + ctx.logger?.warn("Claude usage fetch error", { error: String(error) }); + return null; + } - if (attempt < MAX_RETRIES - 1) { - await new Promise(resolve => setTimeout(resolve, BASE_RETRY_DELAY_MS * 2 ** attempt)); - } - } + if (attempt < MAX_RETRIES - 1) { + await new Promise((resolve) => + setTimeout(resolve, BASE_RETRY_DELAY_MS * 2 ** attempt), + ); + } + } - return lastPayload ? { payload: lastPayload, orgId: lastOrgId } : null; + return lastPayload ? { payload: lastPayload, orgId: lastOrgId } : null; } interface ClaudeProfile { - account?: { - uuid?: string; - email?: string; - }; + account?: { + uuid?: string; + email?: string; + }; } async function fetchProfile( - baseUrl: string, - headers: Record, - ctx: UsageFetchContext, - signal?: AbortSignal, + baseUrl: string, + headers: Record, + ctx: UsageFetchContext, + signal?: AbortSignal, ): Promise { - const url = `${baseUrl}/profile`; - try { - const response = await ctx.fetch(url, { headers, signal }); - if (!response.ok) return null; - return (await response.json()) as ClaudeProfile; - } catch { - return null; - } + const url = `${baseUrl}/profile`; + try { + const response = await ctx.fetch(url, { headers, signal }); + if (!response.ok) return null; + return (await response.json()) as ClaudeProfile; + } catch { + return null; + } } async function resolveEmail( - params: UsageFetchParams, - ctx: UsageFetchContext, - baseUrl: string, - headers: Record, + params: UsageFetchParams, + ctx: UsageFetchContext, + baseUrl: string, + headers: Record, ): Promise { - if (params.credential.email) return params.credential.email; + if (params.credential.email) return params.credential.email; - const profile = await fetchProfile(baseUrl, headers, ctx, params.signal); - return profile?.account?.email; + const profile = await fetchProfile(baseUrl, headers, ctx, params.signal); + return profile?.account?.email; } -function buildUsageAmount(utilization: number | undefined): UsageAmount | undefined { - if (utilization === undefined) return undefined; - const clamped = Math.min(Math.max(utilization, 0), 100); - const usedFraction = clamped / 100; - return { - used: clamped, - limit: 100, - remaining: Math.max(0, 100 - clamped), - usedFraction, - remainingFraction: Math.max(0, 1 - usedFraction), - unit: "percent", - }; +function buildUsageAmount( + utilization: number | undefined, +): UsageAmount | undefined { + if (utilization === undefined) return undefined; + const clamped = Math.min(Math.max(utilization, 0), 100); + const usedFraction = clamped / 100; + return { + used: clamped, + limit: 100, + remaining: Math.max(0, 100 - clamped), + usedFraction, + remainingFraction: Math.max(0, 1 - usedFraction), + unit: "percent", + }; } -function buildUsageStatus(usedFraction: number | undefined): UsageStatus | undefined { - if (usedFraction === undefined) return undefined; - if (usedFraction >= 1) return "exhausted"; - if (usedFraction >= 0.9) return "warning"; - return "ok"; +function buildUsageStatus( + usedFraction: number | undefined, +): UsageStatus | undefined { + if (usedFraction === undefined) return undefined; + if (usedFraction >= 1) return "exhausted"; + if (usedFraction >= 0.9) return "warning"; + return "ok"; } function buildUsageLimit(args: { - id: string; - label: string; - windowId: string; - windowLabel: string; - durationMs: number; - bucket: ParsedUsageBucket | undefined; - provider: "anthropic"; - tier?: string; - shared?: boolean; + id: string; + label: string; + windowId: string; + windowLabel: string; + durationMs: number; + bucket: ParsedUsageBucket | undefined; + provider: "anthropic"; + tier?: string; + shared?: boolean; }): UsageLimit | null { - if (!args.bucket) return null; - const amount = buildUsageAmount(args.bucket.utilization); - if (!amount) return null; - const window: UsageWindow = { - id: args.windowId, - label: args.windowLabel, - durationMs: args.durationMs, - ...(args.bucket.resetsAt !== undefined ? { resetsAt: args.bucket.resetsAt } : {}), - }; - return { - id: args.id, - label: args.label, - scope: { - provider: args.provider, - windowId: args.windowId, - tier: args.tier, - shared: args.shared, - }, - window, - amount, - status: buildUsageStatus(amount.usedFraction), - }; + if (!args.bucket) return null; + const amount = buildUsageAmount(args.bucket.utilization); + if (!amount) return null; + const window: UsageWindow = { + id: args.windowId, + label: args.windowLabel, + durationMs: args.durationMs, + ...(args.bucket.resetsAt !== undefined + ? { resetsAt: args.bucket.resetsAt } + : {}), + }; + return { + id: args.id, + label: args.label, + scope: { + provider: args.provider, + windowId: args.windowId, + tier: args.tier, + shared: args.shared, + }, + window, + amount, + status: buildUsageStatus(amount.usedFraction), + }; } -async function fetchClaudeUsage(params: UsageFetchParams, ctx: UsageFetchContext): Promise { - if (params.provider !== "anthropic") return null; - const credential = params.credential; - if (credential.type !== "oauth" || !credential.accessToken) return null; +async function fetchClaudeUsage( + params: UsageFetchParams, + ctx: UsageFetchContext, +): Promise { + if (params.provider !== "anthropic") return null; + const credential = params.credential; + if (credential.type !== "oauth" || !credential.accessToken) return null; - const baseUrl = normalizeClaudeBaseUrl(params.baseUrl); - const url = `${baseUrl}/usage`; - const headers: Record = { - ...CLAUDE_HEADERS, - authorization: `Bearer ${credential.accessToken}`, - }; + const baseUrl = normalizeClaudeBaseUrl(params.baseUrl); + const url = `${baseUrl}/usage`; + const headers: Record = { + ...CLAUDE_HEADERS, + authorization: `Bearer ${credential.accessToken}`, + }; - const payloadResult = await fetchUsagePayload(url, headers, ctx, params.signal); - if (!payloadResult || !isRecord(payloadResult.payload)) return null; - const { payload, orgId } = payloadResult; + const payloadResult = await fetchUsagePayload( + url, + headers, + ctx, + params.signal, + ); + if (!payloadResult || !isRecord(payloadResult.payload)) return null; + const { payload, orgId } = payloadResult; - const fiveHour = parseBucket(payload.five_hour); - const sevenDay = parseBucket(payload.seven_day); - const sevenDayOpus = parseBucket(payload.seven_day_opus); - const sevenDaySonnet = parseBucket(payload.seven_day_sonnet); + const fiveHour = parseBucket(payload.five_hour); + const sevenDay = parseBucket(payload.seven_day); + const sevenDayOpus = parseBucket(payload.seven_day_opus); + const sevenDaySonnet = parseBucket(payload.seven_day_sonnet); - const limits = [ - buildUsageLimit({ - id: "anthropic:5h", - label: "Claude 5 Hour", - windowId: "5h", - windowLabel: "5 Hour", - durationMs: FIVE_HOURS_MS, - bucket: fiveHour, - provider: "anthropic", - shared: true, - }), - buildUsageLimit({ - id: "anthropic:7d", - label: "Claude 7 Day", - windowId: "7d", - windowLabel: "7 Day", - durationMs: SEVEN_DAYS_MS, - bucket: sevenDay, - provider: "anthropic", - shared: true, - }), - buildUsageLimit({ - id: "anthropic:7d:opus", - label: "Claude 7 Day (Opus)", - windowId: "7d", - windowLabel: "7 Day", - durationMs: SEVEN_DAYS_MS, - bucket: sevenDayOpus, - provider: "anthropic", - tier: "opus", - }), - buildUsageLimit({ - id: "anthropic:7d:sonnet", - label: "Claude 7 Day (Sonnet)", - windowId: "7d", - windowLabel: "7 Day", - durationMs: SEVEN_DAYS_MS, - bucket: sevenDaySonnet, - provider: "anthropic", - tier: "sonnet", - }), - ].filter((limit): limit is UsageLimit => limit !== null); + const limits = [ + buildUsageLimit({ + id: "anthropic:5h", + label: "Claude 5 Hour", + windowId: "5h", + windowLabel: "5 Hour", + durationMs: FIVE_HOURS_MS, + bucket: fiveHour, + provider: "anthropic", + shared: true, + }), + buildUsageLimit({ + id: "anthropic:7d", + label: "Claude 7 Day", + windowId: "7d", + windowLabel: "7 Day", + durationMs: SEVEN_DAYS_MS, + bucket: sevenDay, + provider: "anthropic", + shared: true, + }), + buildUsageLimit({ + id: "anthropic:7d:opus", + label: "Claude 7 Day (Opus)", + windowId: "7d", + windowLabel: "7 Day", + durationMs: SEVEN_DAYS_MS, + bucket: sevenDayOpus, + provider: "anthropic", + tier: "opus", + }), + buildUsageLimit({ + id: "anthropic:7d:sonnet", + label: "Claude 7 Day (Sonnet)", + windowId: "7d", + windowLabel: "7 Day", + durationMs: SEVEN_DAYS_MS, + bucket: sevenDaySonnet, + provider: "anthropic", + tier: "sonnet", + }), + ].filter((limit): limit is UsageLimit => limit !== null); - if (limits.length === 0) return null; - const identity = extractUsageIdentity(payload, orgId); - const accountId = identity.accountId ?? credential.accountId; - const email = identity.email ?? (await resolveEmail(params, ctx, baseUrl, headers)); + if (limits.length === 0) return null; + const identity = extractUsageIdentity(payload, orgId); + const accountId = identity.accountId ?? credential.accountId; + const email = + identity.email ?? (await resolveEmail(params, ctx, baseUrl, headers)); - const report: UsageReport = { - provider: params.provider, - fetchedAt: Date.now(), - limits, - metadata: { - accountId, - email, - endpoint: url, - }, - raw: payload, - }; + const report: UsageReport = { + provider: params.provider, + fetchedAt: Date.now(), + limits, + metadata: { + accountId, + email, + endpoint: url, + }, + raw: payload, + }; - return report; + return report; } export const claudeUsageProvider: UsageProvider = { - id: "anthropic", - fetchUsage: fetchClaudeUsage, - supports: params => params.provider === "anthropic" && params.credential.type === "oauth", + id: "anthropic", + fetchUsage: fetchClaudeUsage, + supports: (params) => + params.provider === "anthropic" && params.credential.type === "oauth", }; export const claudeRankingStrategy: CredentialRankingStrategy = { - findWindowLimits(report) { - const primary = report.limits.find(l => l.id === "anthropic:5h"); - const secondary = report.limits.find(l => l.id === "anthropic:7d"); - return { primary, secondary }; - }, - windowDefaults: { primaryMs: 5 * 60 * 60 * 1000, secondaryMs: 7 * 24 * 60 * 60 * 1000 }, + findWindowLimits(report) { + const primary = report.limits.find((l) => l.id === "anthropic:5h"); + const secondary = report.limits.find((l) => l.id === "anthropic:7d"); + return { primary, secondary }; + }, + windowDefaults: { + primaryMs: 5 * 60 * 60 * 1000, + secondaryMs: 7 * 24 * 60 * 60 * 1000, + }, }; diff --git a/usage/openai-codex.ts b/usage/openai-codex.ts index a42cdc9..52a8de1 100644 --- a/usage/openai-codex.ts +++ b/usage/openai-codex.ts @@ -1,14 +1,14 @@ import { Buffer } from "node:buffer"; import { CODEX_BASE_URL } from "../providers/openai-codex/constants"; import type { - CredentialRankingStrategy, - UsageAmount, - UsageFetchContext, - UsageFetchParams, - UsageLimit, - UsageProvider, - UsageReport, - UsageWindow, + CredentialRankingStrategy, + UsageAmount, + UsageFetchContext, + UsageFetchParams, + UsageLimit, + UsageProvider, + UsageReport, + UsageWindow, } from "../usage"; import { isRecord } from "../utils"; import { toNumber } from "./shared"; @@ -18,360 +18,415 @@ const JWT_AUTH_CLAIM = "https://api.openai.com/auth"; const JWT_PROFILE_CLAIM = "https://api.openai.com/profile"; interface CodexUsageWindowPayload { - used_percent?: number; - limit_window_seconds?: number; - reset_after_seconds?: number; - reset_at?: number; + used_percent?: number; + limit_window_seconds?: number; + reset_after_seconds?: number; + reset_at?: number; } interface CodexUsageRateLimitPayload { - allowed?: boolean; - limit_reached?: boolean; - primary_window?: CodexUsageWindowPayload | null; - secondary_window?: CodexUsageWindowPayload | null; + allowed?: boolean; + limit_reached?: boolean; + primary_window?: CodexUsageWindowPayload | null; + secondary_window?: CodexUsageWindowPayload | null; } interface CodexUsagePayload { - plan_type?: string; - rate_limit?: CodexUsageRateLimitPayload | null; + plan_type?: string; + rate_limit?: CodexUsageRateLimitPayload | null; } interface ParsedUsageWindow { - usedPercent?: number; - limitWindowSeconds?: number; - resetAfterSeconds?: number; - resetAt?: number; + usedPercent?: number; + limitWindowSeconds?: number; + resetAfterSeconds?: number; + resetAt?: number; } interface ParsedUsage { - planType?: string; - allowed?: boolean; - limitReached?: boolean; - primary?: ParsedUsageWindow; - secondary?: ParsedUsageWindow; - raw: CodexUsagePayload; + planType?: string; + allowed?: boolean; + limitReached?: boolean; + primary?: ParsedUsageWindow; + secondary?: ParsedUsageWindow; + raw: CodexUsagePayload; } interface JwtPayload { - [JWT_AUTH_CLAIM]?: { - chatgpt_account_id?: string; - }; - [JWT_PROFILE_CLAIM]?: { - email?: string; - }; + [JWT_AUTH_CLAIM]?: { + chatgpt_account_id?: string; + }; + [JWT_PROFILE_CLAIM]?: { + email?: string; + }; } const toBoolean = (value: unknown): boolean | undefined => { - if (typeof value === "boolean") return value; - return undefined; + if (typeof value === "boolean") return value; + return undefined; }; function base64UrlDecode(input: string): string { - const base64 = input.replace(/-/g, "+").replace(/_/g, "/"); - const padLen = (4 - (base64.length % 4)) % 4; - const padded = base64 + "=".repeat(padLen); - return Buffer.from(padded, "base64").toString("utf8"); + const base64 = input.replace(/-/g, "+").replace(/_/g, "/"); + const padLen = (4 - (base64.length % 4)) % 4; + const padded = base64 + "=".repeat(padLen); + return Buffer.from(padded, "base64").toString("utf8"); } function parseJwt(token: string): JwtPayload | null { - const parts = token.split("."); - if (parts.length !== 3) return null; - try { - const payloadJson = base64UrlDecode(parts[1]); - return JSON.parse(payloadJson) as JwtPayload; - } catch { - return null; - } + const parts = token.split("."); + if (parts.length !== 3) return null; + try { + const payloadJson = base64UrlDecode(parts[1]); + return JSON.parse(payloadJson) as JwtPayload; + } catch { + return null; + } } function normalizeEmail(email: string | undefined): string | undefined { - if (!email) return undefined; - const normalized = email.trim().toLowerCase(); - return normalized || undefined; + if (!email) return undefined; + const normalized = email.trim().toLowerCase(); + return normalized || undefined; } function extractAccountId(token: string | undefined): string | undefined { - if (!token) return undefined; - const payload = parseJwt(token); - return payload?.[JWT_AUTH_CLAIM]?.chatgpt_account_id ?? undefined; + if (!token) return undefined; + const payload = parseJwt(token); + return payload?.[JWT_AUTH_CLAIM]?.chatgpt_account_id ?? undefined; } function extractEmail(token: string | undefined): string | undefined { - if (!token) return undefined; - const payload = parseJwt(token); - return normalizeEmail(payload?.[JWT_PROFILE_CLAIM]?.email); + if (!token) return undefined; + const payload = parseJwt(token); + return normalizeEmail(payload?.[JWT_PROFILE_CLAIM]?.email); } function parseUsageWindow(payload: unknown): ParsedUsageWindow | undefined { - if (!isRecord(payload)) return undefined; - const usedPercent = toNumber(payload.used_percent); - const limitWindowSeconds = toNumber(payload.limit_window_seconds); - const resetAfterSeconds = toNumber(payload.reset_after_seconds); - const resetAt = toNumber(payload.reset_at); - if ( - usedPercent === undefined && - limitWindowSeconds === undefined && - resetAfterSeconds === undefined && - resetAt === undefined - ) { - return undefined; - } - return { - usedPercent, - limitWindowSeconds, - resetAfterSeconds, - resetAt, - }; + if (!isRecord(payload)) return undefined; + const usedPercent = toNumber(payload.used_percent); + const limitWindowSeconds = toNumber(payload.limit_window_seconds); + const resetAfterSeconds = toNumber(payload.reset_after_seconds); + const resetAt = toNumber(payload.reset_at); + if ( + usedPercent === undefined && + limitWindowSeconds === undefined && + resetAfterSeconds === undefined && + resetAt === undefined + ) { + return undefined; + } + return { + usedPercent, + limitWindowSeconds, + resetAfterSeconds, + resetAt, + }; } function parseUsagePayload(payload: unknown): ParsedUsage | null { - if (!isRecord(payload)) return null; - const planType = typeof payload.plan_type === "string" ? payload.plan_type : undefined; - const rateLimit = isRecord(payload.rate_limit) ? payload.rate_limit : undefined; - if (!rateLimit) return null; - const parsed: ParsedUsage = { - planType, - allowed: toBoolean(rateLimit.allowed), - limitReached: toBoolean(rateLimit.limit_reached), - primary: parseUsageWindow(rateLimit.primary_window), - secondary: parseUsageWindow(rateLimit.secondary_window), - raw: payload as CodexUsagePayload, - }; - if (!parsed.primary && !parsed.secondary && parsed.allowed === undefined && parsed.limitReached === undefined) { - return null; - } - return parsed; + if (!isRecord(payload)) return null; + const planType = + typeof payload.plan_type === "string" ? payload.plan_type : undefined; + const rateLimit = isRecord(payload.rate_limit) + ? payload.rate_limit + : undefined; + if (!rateLimit) return null; + const parsed: ParsedUsage = { + planType, + allowed: toBoolean(rateLimit.allowed), + limitReached: toBoolean(rateLimit.limit_reached), + primary: parseUsageWindow(rateLimit.primary_window), + secondary: parseUsageWindow(rateLimit.secondary_window), + raw: payload as CodexUsagePayload, + }; + if ( + !parsed.primary && + !parsed.secondary && + parsed.allowed === undefined && + parsed.limitReached === undefined + ) { + return null; + } + return parsed; } function normalizeCodexBaseUrl(baseUrl?: string): string { - const fallback = CODEX_BASE_URL; - const trimmed = baseUrl?.trim() ? baseUrl.trim() : fallback; - const base = trimmed.replace(/\/+$/, ""); - const lower = base.toLowerCase(); - if ( - (lower.startsWith("https://chatgpt.com") || lower.startsWith("https://chat.openai.com")) && - !lower.includes("/backend-api") - ) { - return `${base}/backend-api`; - } - return base; + const fallback = CODEX_BASE_URL; + const trimmed = baseUrl?.trim() ? baseUrl.trim() : fallback; + const base = trimmed.replace(/\/+$/, ""); + const lower = base.toLowerCase(); + if ( + (lower.startsWith("https://chatgpt.com") || + lower.startsWith("https://chat.openai.com")) && + !lower.includes("/backend-api") + ) { + return `${base}/backend-api`; + } + return base; } function buildCodexUsageUrl(baseUrl: string): string { - const normalized = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`; - return `${normalized}${CODEX_USAGE_PATH}`; + const normalized = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`; + return `${normalized}${CODEX_USAGE_PATH}`; } function formatWindowLabel(value: number, unit: "hour" | "day"): string { - const rounded = Math.round(value); - const suffix = rounded === 1 ? unit : `${unit}s`; - return `${rounded} ${suffix}`; + const rounded = Math.round(value); + const suffix = rounded === 1 ? unit : `${unit}s`; + return `${rounded} ${suffix}`; } function buildWindowLabel(seconds: number): { id: string; label: string } { - const daySeconds = 86_400; - if (seconds >= daySeconds) { - const days = Math.round(seconds / daySeconds); - return { id: `${days}d`, label: formatWindowLabel(days, "day") }; - } - const hours = Math.max(1, Math.round(seconds / 3600)); - return { id: `${hours}h`, label: formatWindowLabel(hours, "hour") }; + const daySeconds = 86_400; + if (seconds >= daySeconds) { + const days = Math.round(seconds / daySeconds); + return { id: `${days}d`, label: formatWindowLabel(days, "day") }; + } + const hours = Math.max(1, Math.round(seconds / 3600)); + return { id: `${hours}h`, label: formatWindowLabel(hours, "hour") }; } -function resolveResetTime(window: ParsedUsageWindow, nowMs: number): number | undefined { - const resetAt = window.resetAt; - if (resetAt !== undefined) { - const resetAtMs = resetAt > 1_000_000_000_000 ? resetAt : resetAt * 1000; - if (Number.isFinite(resetAtMs)) return resetAtMs; - } - if (window.resetAfterSeconds !== undefined) { - return nowMs + window.resetAfterSeconds * 1000; - } - return undefined; +function resolveResetTime( + window: ParsedUsageWindow, + nowMs: number, +): number | undefined { + const resetAt = window.resetAt; + if (resetAt !== undefined) { + const resetAtMs = resetAt > 1_000_000_000_000 ? resetAt : resetAt * 1000; + if (Number.isFinite(resetAtMs)) return resetAtMs; + } + if (window.resetAfterSeconds !== undefined) { + return nowMs + window.resetAfterSeconds * 1000; + } + return undefined; } -function buildUsageWindow(window: ParsedUsageWindow, key: string, nowMs: number): UsageWindow { - const resetsAt = resolveResetTime(window, nowMs); - if (window.limitWindowSeconds !== undefined) { - const { id, label } = buildWindowLabel(window.limitWindowSeconds); - const durationMs = window.limitWindowSeconds * 1000; - return { id, label, durationMs, ...(resetsAt !== undefined ? { resetsAt } : {}) }; - } - const fallbackLabel = key === "primary" ? "Primary window" : "Secondary window"; - return { id: key, label: fallbackLabel, ...(resetsAt !== undefined ? { resetsAt } : {}) }; +function buildUsageWindow( + window: ParsedUsageWindow, + key: string, + nowMs: number, +): UsageWindow { + const resetsAt = resolveResetTime(window, nowMs); + if (window.limitWindowSeconds !== undefined) { + const { id, label } = buildWindowLabel(window.limitWindowSeconds); + const durationMs = window.limitWindowSeconds * 1000; + return { + id, + label, + durationMs, + ...(resetsAt !== undefined ? { resetsAt } : {}), + }; + } + const fallbackLabel = + key === "primary" ? "Primary window" : "Secondary window"; + return { + id: key, + label: fallbackLabel, + ...(resetsAt !== undefined ? { resetsAt } : {}), + }; } function buildUsageAmount(window: ParsedUsageWindow): UsageAmount { - const usedPercent = window.usedPercent; - if (usedPercent === undefined) { - return { unit: "percent" }; - } - const clamped = Math.min(Math.max(usedPercent, 0), 100); - const usedFraction = clamped / 100; - return { - used: clamped, - limit: 100, - remaining: Math.max(0, 100 - clamped), - usedFraction, - remainingFraction: Math.max(0, 1 - usedFraction), - unit: "percent", - }; + const usedPercent = window.usedPercent; + if (usedPercent === undefined) { + return { unit: "percent" }; + } + const clamped = Math.min(Math.max(usedPercent, 0), 100); + const usedFraction = clamped / 100; + return { + used: clamped, + limit: 100, + remaining: Math.max(0, 100 - clamped), + usedFraction, + remainingFraction: Math.max(0, 1 - usedFraction), + unit: "percent", + }; } -function buildUsageStatus(usedFraction?: number, limitReached?: boolean): UsageLimit["status"] { - if (limitReached) return "exhausted"; - if (usedFraction === undefined) return "unknown"; - if (usedFraction >= 1) return "exhausted"; - if (usedFraction >= 0.9) return "warning"; - return "ok"; +function buildUsageStatus( + usedFraction?: number, + limitReached?: boolean, +): UsageLimit["status"] { + if (limitReached) return "exhausted"; + if (usedFraction === undefined) return "unknown"; + if (usedFraction >= 1) return "exhausted"; + if (usedFraction >= 0.9) return "warning"; + return "ok"; } function buildUsageLimit(args: { - key: "primary" | "secondary"; - window: ParsedUsageWindow; - accountId?: string; - planType?: string; - limitReached?: boolean; - nowMs: number; + key: "primary" | "secondary"; + window: ParsedUsageWindow; + accountId?: string; + planType?: string; + limitReached?: boolean; + nowMs: number; }): UsageLimit { - const usageWindow = buildUsageWindow(args.window, args.key, args.nowMs); - const amount = buildUsageAmount(args.window); - return { - id: `openai-codex:${args.key}`, - label: usageWindow.label, - scope: { - provider: "openai-codex", - accountId: args.accountId, - tier: args.planType, - windowId: usageWindow.id, - shared: true, - }, - window: usageWindow, - amount, - status: buildUsageStatus(amount.usedFraction, args.limitReached), - }; + const usageWindow = buildUsageWindow(args.window, args.key, args.nowMs); + const amount = buildUsageAmount(args.window); + return { + id: `openai-codex:${args.key}`, + label: usageWindow.label, + scope: { + provider: "openai-codex", + accountId: args.accountId, + tier: args.planType, + windowId: usageWindow.id, + shared: true, + }, + window: usageWindow, + amount, + status: buildUsageStatus(amount.usedFraction, args.limitReached), + }; } export const openaiCodexUsageProvider: UsageProvider = { - id: "openai-codex", - supports(params: UsageFetchParams): boolean { - return params.provider === "openai-codex" && params.credential.type === "oauth"; - }, - async fetchUsage(params: UsageFetchParams, ctx: UsageFetchContext): Promise { - if (params.provider !== "openai-codex") return null; - const { credential } = params; - if (credential.type !== "oauth") return null; + id: "openai-codex", + supports(params: UsageFetchParams): boolean { + return ( + params.provider === "openai-codex" && params.credential.type === "oauth" + ); + }, + async fetchUsage( + params: UsageFetchParams, + ctx: UsageFetchContext, + ): Promise { + if (params.provider !== "openai-codex") return null; + const { credential } = params; + if (credential.type !== "oauth") return null; - const accessToken = credential.accessToken; - if (!accessToken) return null; + const accessToken = credential.accessToken; + if (!accessToken) return null; - const nowMs = Date.now(); - if (credential.expiresAt !== undefined && credential.expiresAt <= nowMs) { - ctx.logger?.warn("Codex usage token expired", { provider: params.provider }); - return null; - } + const nowMs = Date.now(); + if (credential.expiresAt !== undefined && credential.expiresAt <= nowMs) { + ctx.logger?.warn("Codex usage token expired", { + provider: params.provider, + }); + return null; + } - const baseUrl = normalizeCodexBaseUrl(params.baseUrl); - const accountId = credential.accountId ?? extractAccountId(accessToken); - const email = normalizeEmail(credential.email ?? extractEmail(accessToken)); + const baseUrl = normalizeCodexBaseUrl(params.baseUrl); + const accountId = credential.accountId ?? extractAccountId(accessToken); + const email = normalizeEmail(credential.email ?? extractEmail(accessToken)); - const headers: Record = { - Authorization: `Bearer ${accessToken}`, - "User-Agent": "OpenCode-Status-Plugin/1.0", - }; - if (accountId) { - headers["ChatGPT-Account-Id"] = accountId; - } + const headers: Record = { + Authorization: `Bearer ${accessToken}`, + "User-Agent": "OpenCode-Status-Plugin/1.0", + }; + if (accountId) { + headers["ChatGPT-Account-Id"] = accountId; + } - const url = buildCodexUsageUrl(baseUrl); - let payload: unknown; - try { - const response = await ctx.fetch(url, { headers, signal: params.signal }); - if (!response.ok) { - ctx.logger?.warn("Codex usage request failed", { status: response.status, provider: params.provider }); - return null; - } - payload = await response.json(); - } catch (error) { - ctx.logger?.warn("Codex usage request error", { provider: params.provider, error: String(error) }); - return null; - } + const url = buildCodexUsageUrl(baseUrl); + let payload: unknown; + try { + const response = await ctx.fetch(url, { headers, signal: params.signal }); + if (!response.ok) { + ctx.logger?.warn("Codex usage request failed", { + status: response.status, + provider: params.provider, + }); + return null; + } + payload = await response.json(); + } catch (error) { + ctx.logger?.warn("Codex usage request error", { + provider: params.provider, + error: String(error), + }); + return null; + } - const parsed = parseUsagePayload(payload); - const planType = - parsed?.planType ?? - (isRecord(payload) && typeof payload.plan_type === "string" ? payload.plan_type : undefined); + const parsed = parseUsagePayload(payload); + const planType = + parsed?.planType ?? + (isRecord(payload) && typeof payload.plan_type === "string" + ? payload.plan_type + : undefined); - const limits: UsageLimit[] = []; - if (parsed?.primary) { - limits.push( - buildUsageLimit({ - key: "primary", - window: parsed.primary, - accountId, - planType, - limitReached: parsed.limitReached, - nowMs, - }), - ); - } - if (parsed?.secondary) { - limits.push( - buildUsageLimit({ - key: "secondary", - window: parsed.secondary, - accountId, - planType, - limitReached: parsed.limitReached, - nowMs, - }), - ); - } + const limits: UsageLimit[] = []; + if (parsed?.primary) { + limits.push( + buildUsageLimit({ + key: "primary", + window: parsed.primary, + accountId, + planType, + limitReached: parsed.limitReached, + nowMs, + }), + ); + } + if (parsed?.secondary) { + limits.push( + buildUsageLimit({ + key: "secondary", + window: parsed.secondary, + accountId, + planType, + limitReached: parsed.limitReached, + nowMs, + }), + ); + } - const report: UsageReport = { - provider: "openai-codex", - fetchedAt: nowMs, - limits, - metadata: { - planType, - allowed: parsed?.allowed, - limitReached: parsed?.limitReached, - email, - accountId, - }, - raw: parsed?.raw ?? payload, - }; + const report: UsageReport = { + provider: "openai-codex", + fetchedAt: nowMs, + limits, + metadata: { + planType, + allowed: parsed?.allowed, + limitReached: parsed?.limitReached, + email, + accountId, + }, + raw: parsed?.raw ?? payload, + }; - return report; - }, + return report; + }, }; const FIVE_HOUR_MS = 5 * 60 * 60 * 1000; export const codexRankingStrategy: CredentialRankingStrategy = { - findWindowLimits(report) { - const findLimit = (key: "primary" | "secondary"): UsageLimit | undefined => { - const direct = report.limits.find(l => l.id === `openai-codex:${key}`); - if (direct) return direct; - const byId = report.limits.find(l => l.id.toLowerCase().includes(key)); - if (byId) return byId; - const windowId = key === "secondary" ? "7d" : "1h"; - return report.limits.find(l => l.scope.windowId?.toLowerCase() === windowId); - }; - return { primary: findLimit("primary"), secondary: findLimit("secondary") }; - }, - windowDefaults: { primaryMs: 60 * 60 * 1000, secondaryMs: 7 * 24 * 60 * 60 * 1000 }, - hasPriorityBoost(primary) { - if (!primary) return false; - const windowId = primary.scope.windowId?.toLowerCase(); - const durationMs = primary.window?.durationMs; - const isFiveHourWindow = - windowId === "5h" || - (typeof durationMs === "number" && - Number.isFinite(durationMs) && - Math.abs(durationMs - FIVE_HOUR_MS) <= 60_000); - if (!isFiveHourWindow) return false; - const usedFraction = primary.amount.usedFraction; - return typeof usedFraction === "number" && Number.isFinite(usedFraction) && usedFraction === 0; - }, + findWindowLimits(report) { + const findLimit = ( + key: "primary" | "secondary", + ): UsageLimit | undefined => { + const direct = report.limits.find((l) => l.id === `openai-codex:${key}`); + if (direct) return direct; + const byId = report.limits.find((l) => l.id.toLowerCase().includes(key)); + if (byId) return byId; + const windowId = key === "secondary" ? "7d" : "1h"; + return report.limits.find( + (l) => l.scope.windowId?.toLowerCase() === windowId, + ); + }; + return { primary: findLimit("primary"), secondary: findLimit("secondary") }; + }, + windowDefaults: { + primaryMs: 60 * 60 * 1000, + secondaryMs: 7 * 24 * 60 * 60 * 1000, + }, + hasPriorityBoost(primary) { + if (!primary) return false; + const windowId = primary.scope.windowId?.toLowerCase(); + const durationMs = primary.window?.durationMs; + const isFiveHourWindow = + windowId === "5h" || + (typeof durationMs === "number" && + Number.isFinite(durationMs) && + Math.abs(durationMs - FIVE_HOUR_MS) <= 60_000); + if (!isFiveHourWindow) return false; + const usedFraction = primary.amount.usedFraction; + return ( + typeof usedFraction === "number" && + Number.isFinite(usedFraction) && + usedFraction === 0 + ); + }, }; diff --git a/usage/shared.ts b/usage/shared.ts index f4a23e7..9de2379 100644 --- a/usage/shared.ts +++ b/usage/shared.ts @@ -1,10 +1,10 @@ export const toNumber = (value: unknown): number | undefined => { - if (typeof value === "number" && Number.isFinite(value)) return value; - if (typeof value === "string") { - const trimmed = value.trim(); - if (!trimmed) return undefined; - const parsed = Number(trimmed); - if (Number.isFinite(parsed)) return parsed; - } - return undefined; + if (typeof value === "number" && Number.isFinite(value)) return value; + if (typeof value === "string") { + const trimmed = value.trim(); + if (!trimmed) return undefined; + const parsed = Number(trimmed); + if (Number.isFinite(parsed)) return parsed; + } + return undefined; }; diff --git a/usage/zai.ts b/usage/zai.ts index a2fdf6d..c0ae049 100644 --- a/usage/zai.ts +++ b/usage/zai.ts @@ -1,12 +1,12 @@ import type { - UsageAmount, - UsageFetchContext, - UsageFetchParams, - UsageLimit, - UsageProvider, - UsageReport, - UsageStatus, - UsageWindow, + UsageAmount, + UsageFetchContext, + UsageFetchParams, + UsageLimit, + UsageProvider, + UsageReport, + UsageStatus, + UsageWindow, } from "../usage"; import { isRecord, toNumber } from "../utils"; @@ -16,232 +16,247 @@ const MODEL_USAGE_PATH = "/api/monitor/usage/model-usage"; const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000; function normalizeZaiBaseUrl(baseUrl?: string): string { - if (!baseUrl?.trim()) return DEFAULT_ENDPOINT; - try { - return new URL(baseUrl.trim()).origin; - } catch { - return DEFAULT_ENDPOINT; - } + if (!baseUrl?.trim()) return DEFAULT_ENDPOINT; + try { + return new URL(baseUrl.trim()).origin; + } catch { + return DEFAULT_ENDPOINT; + } } interface ZaiUsageLimitItem { - type?: string; - usage?: number; - currentValue?: number; - percentage?: number; - remaining?: number; - nextResetTime?: number; + type?: string; + usage?: number; + currentValue?: number; + percentage?: number; + remaining?: number; + nextResetTime?: number; } interface ZaiQuotaPayload { - success?: boolean; - code?: number; - msg?: string; - data?: { - limits?: ZaiUsageLimitItem[]; - }; + success?: boolean; + code?: number; + msg?: string; + data?: { + limits?: ZaiUsageLimitItem[]; + }; } function parseMillis(value: unknown): number | undefined { - const parsed = toNumber(value); - if (parsed === undefined) return undefined; - return parsed > 1_000_000_000_000 ? parsed : parsed * 1000; + const parsed = toNumber(value); + if (parsed === undefined) return undefined; + return parsed > 1_000_000_000_000 ? parsed : parsed * 1000; } function parseLimitItem(value: unknown): ZaiUsageLimitItem | null { - if (!isRecord(value)) return null; - const type = typeof value.type === "string" ? value.type : undefined; - if (!type) return null; - return { - type, - usage: toNumber(value.usage), - currentValue: toNumber(value.currentValue), - percentage: toNumber(value.percentage), - remaining: toNumber(value.remaining), - nextResetTime: parseMillis(value.nextResetTime), - }; + if (!isRecord(value)) return null; + const type = typeof value.type === "string" ? value.type : undefined; + if (!type) return null; + return { + type, + usage: toNumber(value.usage), + currentValue: toNumber(value.currentValue), + percentage: toNumber(value.percentage), + remaining: toNumber(value.remaining), + nextResetTime: parseMillis(value.nextResetTime), + }; } function buildUsageAmount(args: { - used: number | undefined; - limit: number | undefined; - remaining: number | undefined; - unit: UsageAmount["unit"]; - percentage?: number; + used: number | undefined; + limit: number | undefined; + remaining: number | undefined; + unit: UsageAmount["unit"]; + percentage?: number; }): UsageAmount { - const usedFraction = - args.percentage !== undefined - ? Math.min(Math.max(args.percentage / 100, 0), 1) - : args.used !== undefined && args.limit !== undefined && args.limit > 0 - ? Math.min(args.used / args.limit, 1) - : undefined; - const remainingFraction = usedFraction !== undefined ? Math.max(1 - usedFraction, 0) : undefined; - return { - used: args.used, - limit: args.limit, - remaining: args.remaining, - usedFraction, - remainingFraction, - unit: args.unit, - }; + const usedFraction = + args.percentage !== undefined + ? Math.min(Math.max(args.percentage / 100, 0), 1) + : args.used !== undefined && args.limit !== undefined && args.limit > 0 + ? Math.min(args.used / args.limit, 1) + : undefined; + const remainingFraction = + usedFraction !== undefined ? Math.max(1 - usedFraction, 0) : undefined; + return { + used: args.used, + limit: args.limit, + remaining: args.remaining, + usedFraction, + remainingFraction, + unit: args.unit, + }; } -function getUsageStatus(usedFraction: number | undefined): UsageStatus | undefined { - if (usedFraction === undefined) return undefined; - if (usedFraction >= 1) return "exhausted"; - if (usedFraction >= 0.9) return "warning"; - return "ok"; +function getUsageStatus( + usedFraction: number | undefined, +): UsageStatus | undefined { + if (usedFraction === undefined) return undefined; + if (usedFraction >= 1) return "exhausted"; + if (usedFraction >= 0.9) return "warning"; + return "ok"; } function formatDate(value: Date): string { - const pad = (input: number) => String(input).padStart(2, "0"); - return `${value.getFullYear()}-${pad(value.getMonth() + 1)}-${pad(value.getDate())}+${pad(value.getHours())}:${pad( - value.getMinutes(), - )}:${pad(value.getSeconds())}`; + const pad = (input: number) => String(input).padStart(2, "0"); + return `${value.getFullYear()}-${pad(value.getMonth() + 1)}-${pad(value.getDate())}+${pad(value.getHours())}:${pad( + value.getMinutes(), + )}:${pad(value.getSeconds())}`; } function buildModelUsageUrl(baseUrl: string, now: Date): string { - const start = new Date(now.getTime() - SEVEN_DAYS_MS); - const startTime = formatDate(start); - const endTime = formatDate(now); - return `${baseUrl}${MODEL_USAGE_PATH}?startTime=${encodeURIComponent(startTime)}&endTime=${encodeURIComponent(endTime)}`; + const start = new Date(now.getTime() - SEVEN_DAYS_MS); + const startTime = formatDate(start); + const endTime = formatDate(now); + return `${baseUrl}${MODEL_USAGE_PATH}?startTime=${encodeURIComponent(startTime)}&endTime=${encodeURIComponent(endTime)}`; } -async function fetchZaiUsage(params: UsageFetchParams, ctx: UsageFetchContext): Promise { - if (params.provider !== "zai") return null; - const credential = params.credential; - if (credential.type !== "api_key" || !credential.apiKey) return null; +async function fetchZaiUsage( + params: UsageFetchParams, + ctx: UsageFetchContext, +): Promise { + if (params.provider !== "zai") return null; + const credential = params.credential; + if (credential.type !== "api_key" || !credential.apiKey) return null; - const baseUrl = normalizeZaiBaseUrl(params.baseUrl); - const url = `${baseUrl}${QUOTA_PATH}`; - const headers: Record = { - Authorization: credential.apiKey, - "Content-Type": "application/json", - "User-Agent": "OpenCode-Status-Plugin/1.0", - }; + const baseUrl = normalizeZaiBaseUrl(params.baseUrl); + const url = `${baseUrl}${QUOTA_PATH}`; + const headers: Record = { + Authorization: credential.apiKey, + "Content-Type": "application/json", + "User-Agent": "OpenCode-Status-Plugin/1.0", + }; - let payload: ZaiQuotaPayload | null = null; - try { - const response = await ctx.fetch(url, { - headers, - signal: params.signal, - }); - if (!response.ok) { - ctx.logger?.warn("ZAI usage fetch failed", { status: response.status, statusText: response.statusText }); - return null; - } - payload = (await response.json()) as ZaiQuotaPayload; - } catch (error) { - ctx.logger?.warn("ZAI usage fetch error", { error: String(error) }); - return null; - } + let payload: ZaiQuotaPayload | null = null; + try { + const response = await ctx.fetch(url, { + headers, + signal: params.signal, + }); + if (!response.ok) { + ctx.logger?.warn("ZAI usage fetch failed", { + status: response.status, + statusText: response.statusText, + }); + return null; + } + payload = (await response.json()) as ZaiQuotaPayload; + } catch (error) { + ctx.logger?.warn("ZAI usage fetch error", { error: String(error) }); + return null; + } - if (!payload) return null; - if (payload.success !== true) { - ctx.logger?.warn("ZAI usage response invalid", { code: payload.code, message: payload.msg }); - return null; - } + if (!payload) return null; + if (payload.success !== true) { + ctx.logger?.warn("ZAI usage response invalid", { + code: payload.code, + message: payload.msg, + }); + return null; + } - const limitsPayload = Array.isArray(payload.data?.limits) ? payload.data?.limits : []; - const limits: UsageLimit[] = []; + const limitsPayload = Array.isArray(payload.data?.limits) + ? payload.data?.limits + : []; + const limits: UsageLimit[] = []; - for (const rawLimit of limitsPayload) { - const parsed = parseLimitItem(rawLimit); - if (!parsed) continue; - if (parsed.type === "TOKENS_LIMIT") { - const amount = buildUsageAmount({ - used: parsed.currentValue, - limit: parsed.usage, - remaining: parsed.remaining, - percentage: parsed.percentage, - unit: "tokens", - }); - const window: UsageWindow = { - id: "quota", - label: "Quota", - durationMs: SEVEN_DAYS_MS, - resetsAt: parsed.nextResetTime, - }; - limits.push({ - id: "zai:tokens", - label: "ZAI Token Quota", - scope: { - provider: params.provider, - windowId: window?.id ?? "quota", - shared: true, - }, - window, - amount, - status: getUsageStatus(amount.usedFraction), - }); - } - if (parsed.type === "TIME_LIMIT") { - const window: UsageWindow = { - id: "quota", - label: "Quota", - durationMs: SEVEN_DAYS_MS, - resetsAt: parsed.nextResetTime, - }; - const amount = buildUsageAmount({ - used: parsed.currentValue, - limit: parsed.usage, - remaining: parsed.remaining, - percentage: parsed.percentage, - unit: "requests", - }); - limits.push({ - id: "zai:requests", - label: "ZAI Request Quota", - scope: { - provider: params.provider, - windowId: "quota", - shared: true, - }, - window, - amount, - status: getUsageStatus(amount.usedFraction), - }); - } - } + for (const rawLimit of limitsPayload) { + const parsed = parseLimitItem(rawLimit); + if (!parsed) continue; + if (parsed.type === "TOKENS_LIMIT") { + const amount = buildUsageAmount({ + used: parsed.currentValue, + limit: parsed.usage, + remaining: parsed.remaining, + percentage: parsed.percentage, + unit: "tokens", + }); + const window: UsageWindow = { + id: "quota", + label: "Quota", + durationMs: SEVEN_DAYS_MS, + resetsAt: parsed.nextResetTime, + }; + limits.push({ + id: "zai:tokens", + label: "ZAI Token Quota", + scope: { + provider: params.provider, + windowId: window?.id ?? "quota", + shared: true, + }, + window, + amount, + status: getUsageStatus(amount.usedFraction), + }); + } + if (parsed.type === "TIME_LIMIT") { + const window: UsageWindow = { + id: "quota", + label: "Quota", + durationMs: SEVEN_DAYS_MS, + resetsAt: parsed.nextResetTime, + }; + const amount = buildUsageAmount({ + used: parsed.currentValue, + limit: parsed.usage, + remaining: parsed.remaining, + percentage: parsed.percentage, + unit: "requests", + }); + limits.push({ + id: "zai:requests", + label: "ZAI Request Quota", + scope: { + provider: params.provider, + windowId: "quota", + shared: true, + }, + window, + amount, + status: getUsageStatus(amount.usedFraction), + }); + } + } - if (limits.length === 0) return null; + if (limits.length === 0) return null; - const report: UsageReport = { - provider: params.provider, - fetchedAt: Date.now(), - limits, - metadata: { - endpoint: url, - accountId: credential.accountId, - email: credential.email, - }, - raw: payload, - }; + const report: UsageReport = { + provider: params.provider, + fetchedAt: Date.now(), + limits, + metadata: { + endpoint: url, + accountId: credential.accountId, + email: credential.email, + }, + raw: payload, + }; - const modelUsageUrl = buildModelUsageUrl(baseUrl, new Date()); - try { - const response = await ctx.fetch(modelUsageUrl, { - headers, - signal: params.signal, - }); - if (response.ok) { - const modelUsagePayload = (await response.json()) as unknown; - if (isRecord(modelUsagePayload)) { - report.metadata = { - ...report.metadata, - modelUsage: modelUsagePayload, - }; - } - } - } catch (error) { - ctx.logger?.debug("ZAI model usage fetch failed", { error: String(error) }); - } + const modelUsageUrl = buildModelUsageUrl(baseUrl, new Date()); + try { + const response = await ctx.fetch(modelUsageUrl, { + headers, + signal: params.signal, + }); + if (response.ok) { + const modelUsagePayload = (await response.json()) as unknown; + if (isRecord(modelUsagePayload)) { + report.metadata = { + ...report.metadata, + modelUsage: modelUsagePayload, + }; + } + } + } catch (error) { + ctx.logger?.debug("ZAI model usage fetch failed", { error: String(error) }); + } - return report; + return report; } export const zaiUsageProvider: UsageProvider = { - id: "zai", - fetchUsage: fetchZaiUsage, - supports: params => params.provider === "zai" && params.credential.type === "api_key", + id: "zai", + fetchUsage: fetchZaiUsage, + supports: (params) => + params.provider === "zai" && params.credential.type === "api_key", };