export interface BotProfile { type: "claude" | "codex" | "opencode"; model: string; token?: string; } export function profileFooter(profile: Pick): string { return `> Profile: \`${profile.type}\` · \`${profile.model}\``; } export function withProfile(body: string, profile: Pick): string { return `${body}\n\n${profileFooter(profile)}`; } export type FailureReason = "sign-in failed" | "usage exhausted" | "failed"; export type ProfileFailure = Pick & { reason: FailureReason; }; // Raised once an agent's credentials are rejected and any re-login is spent. export class AgentAuthError extends Error {} function isUsageExhausted(error: unknown): boolean { return /(?:usage|rate) limit|hit your (?:weekly )?limit|insufficient_quota|exceeded your current quota|too many requests|(?:api error|http error|http|status(?: code)?)[ :]+4(?:02|29)\b/iu.test( String(error), ); } function failureReason(error: unknown): FailureReason { if (error instanceof AgentAuthError) return "sign-in failed"; return isUsageExhausted(error) ? "usage exhausted" : "failed"; } export function failureFooter(failures: ProfileFailure[]): string { if (failures.length === 0) return "> Profile: none"; return `> Tried: ${failures .map(({ type, model, reason }) => `\`${type}\` · \`${model}\` (${reason})`) .join(", ")}`; } // Tries the profiles from `index` on, moving on only after a sign-in failure // or exhausted usage, and records every profile it tried. async function attemptFrom>( profiles: P[], attempt: (profile: P) => Promise, failures: ProfileFailure[], index: number, ): Promise { const profile = profiles[index]; if (profile === undefined) { throw new Error("profiles must contain at least one profile"); } try { return await attempt(profile); } catch (error) { const reason = failureReason(error); failures.push({ type: profile.type, model: profile.model, reason }); if (reason === "failed" || index === profiles.length - 1) throw error; console.error(`${profile.type} ${reason}; trying next profile`); return attemptFrom(profiles, attempt, failures, index + 1); } } export function runProfiles>( profiles: P[], attempt: (profile: P) => Promise, failures: ProfileFailure[] = [], ): Promise { return attemptFrom(profiles, attempt, failures, 0); } 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", ]) { const value = environment[name]; if (value !== undefined) result[name] = value; } 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 !== undefined && token !== "") { const tokenName = { claude: "CLAUDE_CODE_OAUTH_TOKEN", codex: "OPENAI_API_KEY", opencode: "OPENROUTER_API_KEY", }[profile.type]; result[tokenName] = token; } return result; }