export type BotProfile = { type: "claude" | "codex"; model: string; token?: string; }; function isUsageExhausted(error: unknown): boolean { return /(?:usage|rate) limit|hit your limit|insufficient_quota|exceeded your current quota|too many requests|(?:api error|http error|http|status(?: code)?)[ :]+4(?:02|29)\b/i .test(String(error)); } export async function runProfiles< T, P extends Pick, >( profiles: P[], attempt: (profile: P) => Promise, ): Promise { for (const [index, profile] of profiles.entries()) { try { return await attempt(profile); } catch (error) { if (!isUsageExhausted(error) || index === profiles.length - 1) { throw error; } console.error(`${profile.type} usage exhausted; trying next profile`); } } throw new Error("profiles must contain at least one profile"); } export function sanitizedEnvironment( environment: Record, ): Record { const result: Record = {}; for ( const name of [ "PATH", "HOME", "LANG", "LANGUAGE", "LC_ALL", "TERM", "COLORTERM", "CI", "TMPDIR", "GITEA_TOKEN", "GITEA_API_URL", "GITEA_REPOSITORY", "GITHUB_WORKSPACE", "HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY", "NO_PROXY", "http_proxy", "https_proxy", "all_proxy", "no_proxy", "SSL_CERT_FILE", "SSL_CERT_DIR", "NODE_EXTRA_CA_CERTS", ] ) { if (environment[name] !== undefined) result[name] = environment[name]; } return result; } export function agentEnvironment( environment: Record, profile: Pick & { home: string }, token?: string, ): Record { const result = sanitizedEnvironment(environment); result.HOME = profile.home; if (profile.type === "codex") result.CODEX_HOME = `${profile.home}/.codex`; if (token) { result[ profile.type === "claude" ? "CLAUDE_CODE_OAUTH_TOKEN" : "OPENAI_API_KEY" ] = token; } return result; }