149 lines · 5.3 KB · TypeScript
RawDownload
/** * Codex subscription usage. The `account/rateLimits/read` response and the * `account/rateLimits/updated` notification carry the same snapshot shape, so * one mapper serves the status probe and the turn-driven update; both emit * windows with the same ids so they merge onto the same rows. * * @module provider/Layers/codexUsageLimits */import type {  ProviderUsageLimitsUpdate,  ServerProviderResetCredits,  ServerProviderUsageLimits,  ServerProviderUsageWindow,} from "@t3tools/contracts";import * as DateTime from "effect/DateTime";import * as Option from "effect/Option";import type * as CodexErrors from "effect-codex-app-server/errors"; import { clampPercent, makeUsageLimits } from "../providerUsageLimits.ts"; interface CodexRateLimitWindow {  readonly usedPercent: number;  readonly resetsAt?: number | null;  readonly windowDurationMins?: number | null;} /** Structural view of the generated `RateLimitSnapshot`; both messages satisfy it. */export interface CodexRateLimitSnapshot {  readonly planType?: string | null;  readonly primary?: CodexRateLimitWindow | null;  readonly secondary?: CodexRateLimitWindow | null;} /** Structural view of the read response's `rateLimitResetCredits`. */export interface CodexResetCreditsSummary {  readonly availableCount: number;  readonly credits?: ReadonlyArray<{    readonly status: string;    readonly expiresAt?: number | null;  }> | null;} const SESSION_MINS = 5 * 60;const WEEK_MINS = 7 * 24 * 60;const MONTH_MINS = 30 * 24 * 60; function isoFromEpochSeconds(value: number | null | undefined): string | undefined {  if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return undefined;  const dt = DateTime.make(value * 1000);  return Option.isSome(dt) ? DateTime.formatIso(dt.value) : undefined;} function kindForDuration(mins: number): ServerProviderUsageWindow["kind"] {  if (mins >= MONTH_MINS) return "monthly";  if (mins >= WEEK_MINS) return "weekly";  return "session";} function labelForKind(kind: ServerProviderUsageWindow["kind"]): string {  return kind === "session" ? "Session" : kind === "weekly" ? "Weekly" : "Monthly";} /** * `primary` / `secondary` are positions, not durations. Codex usually sends * `windowDurationMins`; when it does not, paid plans expose the 5-hour and * weekly pair and Free/Go expose one monthly allowance. */export function codexRateLimitsToWindows(  snapshot: CodexRateLimitSnapshot,): ReadonlyArray<ServerProviderUsageWindow> {  const isMonthlyPlan = snapshot.planType === "free" || snapshot.planType === "go";  const positions = [    ["primary", snapshot.primary, isMonthlyPlan ? MONTH_MINS : SESSION_MINS],    ["secondary", snapshot.secondary, WEEK_MINS],  ] as const;  const windows: ServerProviderUsageWindow[] = [];  for (const [id, window, fallbackMins] of positions) {    if (!window || !Number.isFinite(window.usedPercent)) continue;    const windowDurationMins =      typeof window.windowDurationMins === "number" ? window.windowDurationMins : fallbackMins;    const kind = kindForDuration(windowDurationMins);    const resetsAt = isoFromEpochSeconds(window.resetsAt);    windows.push({      id,      kind,      label: labelForKind(kind),      usedPercent: clampPercent(window.usedPercent),      windowDurationMins,      ...(resetsAt ? { resetsAt } : {}),    });  }  return windows;} export function codexResetCreditsToContract(  summary: CodexResetCreditsSummary | null | undefined,): ServerProviderResetCredits | undefined {  if (!summary) return undefined;  const expiries = (summary.credits ?? [])    .filter((credit) => credit.status === "available")    .map((credit) => credit.expiresAt)    .filter((value): value is number => typeof value === "number");  const nextExpiresAt =    expiries.length > 0 ? isoFromEpochSeconds(Math.min(...expiries)) : undefined;  return {    availableCount: Math.max(0, summary.availableCount),    ...(nextExpiresAt ? { nextExpiresAt } : {}),  };} export function codexRateLimitsToLimits(input: {  readonly snapshot: CodexRateLimitSnapshot;  readonly resetCredits?: CodexResetCreditsSummary | null | undefined;  readonly checkedAt: string;}): ServerProviderUsageLimits {  const resetCredits = codexResetCreditsToContract(input.resetCredits);  return {    ...makeUsageLimits({      checkedAt: input.checkedAt,      windows: codexRateLimitsToWindows(input.snapshot),    }),    ...(resetCredits ? { resetCredits } : {}),  };} export function codexRateLimitsToUpdate(  snapshot: CodexRateLimitSnapshot,): ProviderUsageLimitsUpdate | undefined {  const windows = codexRateLimitsToWindows(snapshot);  return windows.length > 0 ? { windows } : undefined;} /** * A bounded, client-safe reason for a failed `account/rateLimits/read`. The * raw error is for the log; only the category and, for a JSON-RPC failure, * the code reach the Limits view. */export function codexRateLimitsFailureMessage(error: CodexErrors.CodexAppServerError): string {  switch (error._tag) {    case "CodexAppServerRequestError":      return `Codex could not read usage (JSON-RPC ${error.code}).`;    case "CodexAppServerSpawnError":      return "Codex could not be started to read usage.";    case "CodexAppServerProcessExitedError":      return "Codex exited before it could report usage.";    default:      return "Codex did not answer the usage request.";  }}