Turn on upper-snake-string-unions and convert profile types, failure reasons and anchor sides at their boundaries

This commit is contained in:
Danny Kim
2026-10-19 19:46:31 +09:00
parent 71150900ff
commit c783935936
13 changed files with 144 additions and 113 deletions
+33 -13
View File
@@ -1,6 +1,6 @@
// The agent and model a profile runs, which every report names.
export interface ProfileIdentity {
type: "claude" | "codex" | "opencode";
type: "CLAUDE" | "CODEX" | "OPENCODE";
model: string;
}
@@ -16,7 +16,19 @@ export function withProfile(body: string, profile: ProfileIdentity): string {
return `${body}\n\n${profileFooter(profile)}`;
}
export type FailureReason = "sign-in failed" | "usage exhausted" | "failed";
export type FailureReason = "SIGN_IN_FAILED" | "USAGE_EXHAUSTED" | "FAILED";
// The words a failure reason reads as in logs and posted comments.
function toMessage(reason: FailureReason): string {
switch (reason) {
case "SIGN_IN_FAILED":
return "sign-in failed";
case "USAGE_EXHAUSTED":
return "usage exhausted";
case "FAILED":
return "failed";
}
}
export interface ProfileFailure extends ProfileIdentity {
reason: FailureReason;
@@ -44,9 +56,9 @@ function isUsageExhausted(error: unknown): boolean {
function failureReason(error: unknown): FailureReason {
if (error instanceof AgentAuthError) {
return "sign-in failed";
return "SIGN_IN_FAILED";
}
return isUsageExhausted(error) ? "usage exhausted" : "failed";
return isUsageExhausted(error) ? "USAGE_EXHAUSTED" : "FAILED";
}
export function failureFooter(failures: ProfileFailure[]): string {
@@ -54,7 +66,7 @@ export function failureFooter(failures: ProfileFailure[]): string {
return "> Profile: none";
}
return `> Tried: ${failures
.map(({ type, model, reason }) => `\`${type}\` · \`${model}\` (${reason})`)
.map(({ type, model, reason }) => `\`${type}\` · \`${model}\` (${toMessage(reason)})`)
.join(", ")}`;
}
@@ -71,10 +83,10 @@ export async function runProfiles<T, P extends ProfileIdentity>(
} catch (error) {
const reason = failureReason(error);
failures.push({ type: profile.type, model: profile.model, reason });
if (reason === "failed" || index === profiles.length - 1) {
if (reason === "FAILED" || index === profiles.length - 1) {
throw error;
}
console.error(`${profile.type} ${reason}; trying next profile`);
console.error(`${profile.type} ${toMessage(reason)}; trying next profile`);
}
}
throw new Error("profiles must contain at least one profile");
@@ -120,6 +132,18 @@ export function sanitizedEnvironment(
return result;
}
// The environment variable each agent CLI reads its credential from.
function tokenVariable(type: ProfileIdentity["type"]): string {
switch (type) {
case "CLAUDE":
return "CLAUDE_CODE_OAUTH_TOKEN";
case "CODEX":
return "OPENAI_API_KEY";
case "OPENCODE":
return "OPENROUTER_API_KEY";
}
}
export function agentEnvironment(
environment: ReadonlyMap<string, string>,
profile: ProfileHome,
@@ -127,15 +151,11 @@ export function agentEnvironment(
): Map<string, string> {
const result = sanitizedEnvironment(environment);
result.set("HOME", profile.home);
if (profile.type === "codex") {
if (profile.type === "CODEX") {
result.set("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];
const tokenName = tokenVariable(profile.type);
result.set(tokenName, token);
}
return result;