| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
|
|
| |
| export const CODEX_BASE_URL = 'https://chatgpt.com/backend-api'; |
|
|
| |
| export const JWT_CLAIM_PATH = 'https://api.openai.com/auth'; |
|
|
| const OPENAI_HEADERS = { |
| BETA: 'OpenAI-Beta', |
| ACCOUNT_ID: 'chatgpt-account-id', |
| ORIGINATOR: 'originator', |
| SESSION_ID: 'session_id', |
| CONVERSATION_ID: 'conversation_id', |
| } as const; |
|
|
| const OPENAI_HEADER_VALUES = { |
| BETA_RESPONSES: 'responses=experimental', |
| ORIGINATOR_CODEX: 'codex_cli_rs', |
| } as const; |
|
|
| |
| |
| |
|
|
| const MODEL_MAP: Record<string, string> = { |
| 'gpt-5.3-codex': 'gpt-5.3-codex', |
| 'gpt-5.1-codex': 'gpt-5.1-codex', |
| 'gpt-5.1-codex-low': 'gpt-5.1-codex', |
| 'gpt-5.1-codex-medium': 'gpt-5.1-codex', |
| 'gpt-5.1-codex-high': 'gpt-5.1-codex', |
| 'gpt-5.1-codex-mini': 'gpt-5.1-codex-mini', |
| 'gpt-5.1-codex-mini-medium': 'gpt-5.1-codex-mini', |
| 'gpt-5.1-codex-mini-high': 'gpt-5.1-codex-mini', |
| 'gpt-5.1': 'gpt-5.1', |
| 'gpt-5.1-low': 'gpt-5.1', |
| 'gpt-5.1-medium': 'gpt-5.1', |
| 'gpt-5.1-high': 'gpt-5.1', |
| 'gpt-5-codex': 'gpt-5-codex', |
| 'codex-mini-latest': 'codex-mini-latest', |
| 'gpt-5-codex-mini': 'codex-mini-latest', |
| 'gpt-5-codex-mini-medium': 'codex-mini-latest', |
| 'gpt-5-codex-mini-high': 'codex-mini-latest', |
| 'gpt-5': 'gpt-5', |
| 'gpt-5-mini': 'gpt-5', |
| 'gpt-5-nano': 'gpt-5', |
| }; |
|
|
| |
| |
| |
|
|
| |
| |
| |
| export function decodeJWT(token: string): Record<string, unknown> | null { |
| try { |
| const parts = token.split('.'); |
| if (parts.length !== 3) return null; |
| const decoded = Buffer.from(parts[1], 'base64').toString('utf-8'); |
| return JSON.parse(decoded); |
| } catch { |
| return null; |
| } |
| } |
|
|
| |
| |
| |
| export function createCodexHeaders( |
| init: RequestInit | undefined, |
| accountId: string, |
| accessToken: string, |
| ): Headers { |
| const headers = new Headers(init?.headers ?? {}); |
| headers.delete('x-api-key'); |
| headers.set('Authorization', `Bearer ${accessToken}`); |
| headers.set(OPENAI_HEADERS.ACCOUNT_ID, accountId); |
| headers.set(OPENAI_HEADERS.BETA, OPENAI_HEADER_VALUES.BETA_RESPONSES); |
| headers.set(OPENAI_HEADERS.ORIGINATOR, OPENAI_HEADER_VALUES.ORIGINATOR_CODEX); |
| headers.delete(OPENAI_HEADERS.CONVERSATION_ID); |
| headers.delete(OPENAI_HEADERS.SESSION_ID); |
| headers.set('accept', 'text/event-stream'); |
| return headers; |
| } |
|
|
| |
| |
| |
| |
| export function getNormalizedModel(modelId: string): string | undefined { |
| if (MODEL_MAP[modelId]) return MODEL_MAP[modelId]; |
| const lower = modelId.toLowerCase(); |
| const match = Object.keys(MODEL_MAP).find(k => k.toLowerCase() === lower); |
| return match ? MODEL_MAP[match] : undefined; |
| } |
|
|
| |
| |
| |
| export function getReasoningConfig(originalModel: string): { effort: string; summary: string } { |
| const normalized = originalModel?.toLowerCase() ?? ''; |
| const isCodexMini = normalized.includes('codex-mini') || normalized.includes('codex-mini-latest'); |
| const isCodex = normalized.includes('codex') && !isCodexMini; |
| const isLightweight = !isCodexMini && (normalized.includes('nano') || normalized.includes('mini')); |
|
|
| let effort = isCodexMini ? 'medium' : isLightweight ? 'minimal' : 'medium'; |
|
|
| if (isCodexMini) { |
| if (effort === 'minimal' || effort === 'low') effort = 'medium'; |
| if (effort !== 'high') effort = 'medium'; |
| } |
| if (isCodex && effort === 'minimal') effort = 'low'; |
|
|
| return { effort, summary: 'auto' }; |
| } |
|
|
| |
| |
| |
| |
| export async function handleErrorResponse(response: Response): Promise<Response> { |
| const raw = await response.text(); |
| let enriched = raw; |
| try { |
| const parsed = JSON.parse(raw); |
| const err = parsed?.error ?? {}; |
| const h = response.headers; |
| const primaryResetsAt = toInt(h.get('x-codex-primary-reset-at')); |
| const secondaryResetsAt = toInt(h.get('x-codex-secondary-reset-at')); |
| const resetsAt = err.resets_at ?? primaryResetsAt ?? secondaryResetsAt; |
| const mins = resetsAt ? Math.max(0, Math.round((resetsAt * 1000 - Date.now()) / 60000)) : undefined; |
| const code = (err.code ?? err.type ?? '').toString(); |
|
|
| let friendly_message: string | undefined; |
| if (/usage_limit_reached|usage_not_included|rate_limit_exceeded/i.test(code) || response.status === 429) { |
| const plan = err.plan_type ? ` (${String(err.plan_type).toLowerCase()} plan)` : ''; |
| const when = mins !== undefined ? ` Try again in ~${mins} min.` : ''; |
| friendly_message = `You have hit your ChatGPT usage limit${plan}.${when}`.trim(); |
| } |
|
|
| enriched = JSON.stringify({ |
| error: { |
| ...err, |
| message: err.message ?? friendly_message ?? 'Usage limit reached.', |
| friendly_message, |
| rate_limits: primaryResetsAt !== undefined || secondaryResetsAt !== undefined |
| ? { |
| primary: { used_percent: toNumber(h.get('x-codex-primary-used-percent')), resets_at: primaryResetsAt }, |
| secondary: { used_percent: toNumber(h.get('x-codex-secondary-used-percent')), resets_at: secondaryResetsAt }, |
| } |
| : undefined, |
| status: response.status, |
| }, |
| }); |
| } catch { |
| |
| } |
|
|
| const headers = new Headers(response.headers); |
| headers.set('content-type', 'application/json; charset=utf-8'); |
| return new Response(enriched, { |
| status: response.status, |
| statusText: response.statusText, |
| headers, |
| }); |
| } |
|
|
| function toNumber(v: string | null): number | undefined { |
| if (v == null) return undefined; |
| const n = Number(v); |
| return Number.isFinite(n) ? n : undefined; |
| } |
|
|
| function toInt(v: string | null): number | undefined { |
| if (v == null) return undefined; |
| const n = parseInt(v, 10); |
| return Number.isFinite(n) ? n : undefined; |
| } |
|
|