Files
oh-my-pi/packages/ai/src/providers/openai-codex/response-handler.ts
T
can1357 1b9d9d0851 refactor(catalog)!: split model catalog from pi-ai
Move bundled models, model cache/manager, thinking metadata, effort helpers,
provider descriptors/discovery, wire constants, and model identity utilities
into the new @oh-my-pi/pi-catalog package.

Update pi-ai to keep provider runtime/auth concerns, move catalog provider
metadata into CATALOG_PROVIDERS, and migrate coding-agent, agent, stats, docs,
and tests to import catalog values from pi-catalog.

Split coding-agent model registry helpers into discovery, roles, and models
config modules while preserving registry orchestration.

BREAKING CHANGE: @oh-my-pi/pi-ai no longer exports catalog subpaths such as
/models, /model-cache, /model-manager, /model-thinking, /effort,
/provider-models*, discovery helpers, and provider wire constants; use the
matching @oh-my-pi/pi-catalog subpaths instead.
2026-06-10 04:06:57 +02:00

82 lines
2.7 KiB
TypeScript

import { toNumber } from "@oh-my-pi/pi-catalog/utils";
export type CodexRateLimit = {
used_percent?: number;
window_minutes?: number;
resets_at?: number;
};
export type CodexRateLimits = {
primary?: CodexRateLimit;
secondary?: CodexRateLimit;
};
export type CodexErrorInfo = {
message: string;
status: number;
friendlyMessage?: string;
rateLimits?: CodexRateLimits;
raw?: string;
};
export async function parseCodexError(response: Response): Promise<CodexErrorInfo> {
const raw = await response.text();
let message = raw || response.statusText || "Request failed";
let friendlyMessage: string | undefined;
let rateLimits: CodexRateLimits | undefined;
try {
const parsed = JSON.parse(raw) as { error?: Record<string, unknown> };
const err = parsed?.error ?? {};
const headers = response.headers;
const primary = {
used_percent: toNumber(headers.get("x-codex-primary-used-percent")),
window_minutes: toInt(headers.get("x-codex-primary-window-minutes")),
resets_at: toInt(headers.get("x-codex-primary-reset-at")),
};
const secondary = {
used_percent: toNumber(headers.get("x-codex-secondary-used-percent")),
window_minutes: toInt(headers.get("x-codex-secondary-window-minutes")),
resets_at: toInt(headers.get("x-codex-secondary-reset-at")),
};
rateLimits =
primary.used_percent !== undefined || secondary.used_percent !== undefined
? { primary, secondary }
: undefined;
const code = String((err as { code?: string; type?: string }).code ?? (err as { type?: string }).type ?? "");
const resetsAt = (err as { resets_at?: number }).resets_at ?? primary.resets_at ?? secondary.resets_at;
const mins = resetsAt ? Math.max(0, Math.round((resetsAt * 1000 - Date.now()) / 60000)) : undefined;
if (/usage_limit_reached|usage_not_included/i.test(code)) {
const planType = (err as { plan_type?: string }).plan_type;
const plan = planType ? ` (${String(planType).toLowerCase()} plan)` : "";
const when = mins !== undefined ? ` Try again in ~${mins} min.` : "";
friendlyMessage = `You have hit your ChatGPT usage limit${plan}.${when}`.trim();
} else if (/rate_limit_exceeded/i.test(code) || response.status === 429) {
const when = mins !== undefined ? ` Try again in ~${mins} min.` : "";
friendlyMessage = `ChatGPT rate limit exceeded.${when}`.trim();
}
const errMessage = (err as { message?: string }).message;
message = errMessage || friendlyMessage || message;
} catch {
// raw body not JSON
}
return {
message,
status: response.status,
friendlyMessage,
rateLimits,
raw: raw,
};
}
function toInt(v: string | null): number | undefined {
if (v == null) return undefined;
const n = parseInt(v, 10);
return Number.isFinite(n) ? n : undefined;
}