This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
export async function retryInvalidToken<
|
||||
T extends { status: { success: boolean }; error: string },
|
||||
>(attempt: () => Promise<T>, relogin: () => Promise<void>): Promise<T> {
|
||||
let result = await attempt();
|
||||
if (
|
||||
!result.status.success && result.error.includes("invalid_refresh_token")
|
||||
) {
|
||||
await relogin();
|
||||
result = await attempt();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { assertEquals } from "jsr:@std/assert@1";
|
||||
import { retryInvalidToken } from "./auth.ts";
|
||||
|
||||
Deno.test("retries once after an invalid refresh token", async () => {
|
||||
let attempts = 0;
|
||||
let relogins = 0;
|
||||
const result = await retryInvalidToken(
|
||||
() =>
|
||||
Promise.resolve({
|
||||
status: { success: false },
|
||||
error: attempts++ === 0 ? "invalid_refresh_token" : "still failed",
|
||||
}),
|
||||
() => {
|
||||
relogins++;
|
||||
return Promise.resolve();
|
||||
},
|
||||
);
|
||||
|
||||
assertEquals({ attempts, relogins, error: result.error }, {
|
||||
attempts: 2,
|
||||
relogins: 1,
|
||||
error: "still failed",
|
||||
});
|
||||
});
|
||||
|
||||
Deno.test("does not retry an unrelated failure", async () => {
|
||||
let attempts = 0;
|
||||
let relogins = 0;
|
||||
await retryInvalidToken(
|
||||
() => {
|
||||
attempts++;
|
||||
return Promise.resolve({
|
||||
status: { success: false },
|
||||
error: "rate limited",
|
||||
});
|
||||
},
|
||||
() => {
|
||||
relogins++;
|
||||
return Promise.resolve();
|
||||
},
|
||||
);
|
||||
|
||||
assertEquals({ attempts, relogins }, { attempts: 1, relogins: 0 });
|
||||
});
|
||||
@@ -3,6 +3,7 @@
|
||||
// GITEA_TOKEN; everything this script posts goes through the reviewer token,
|
||||
// so it appears as the bot account.
|
||||
import { TextLineStream } from "jsr:@std/streams@1/text-line-stream";
|
||||
import { retryInvalidToken } from "./auth.ts";
|
||||
|
||||
type GiteaUser = { login: string; email: string };
|
||||
type GiteaComment = { user: GiteaUser; created_at: string; body: string };
|
||||
@@ -252,7 +253,8 @@ async function runCodex(prompt: string): Promise<string> {
|
||||
.output();
|
||||
if (!loggedIn.success) await codexDeviceLogin();
|
||||
const file = await Deno.makeTempFile();
|
||||
const status = await new Deno.Command("codex", {
|
||||
const attempt = async () => {
|
||||
const codex = new Deno.Command("codex", {
|
||||
args: [
|
||||
"exec",
|
||||
"--model",
|
||||
@@ -265,8 +267,21 @@ async function runCodex(prompt: string): Promise<string> {
|
||||
env: agentEnv,
|
||||
clearEnv: true,
|
||||
stdout: "inherit",
|
||||
stderr: "inherit",
|
||||
}).spawn().status;
|
||||
stderr: "piped",
|
||||
}).spawn();
|
||||
const decoder = new TextDecoder();
|
||||
let error = "";
|
||||
for await (const chunk of codex.stderr) {
|
||||
await Deno.stderr.write(chunk);
|
||||
error += decoder.decode(chunk, { stream: true });
|
||||
}
|
||||
error += decoder.decode();
|
||||
return { status: await codex.status, error };
|
||||
};
|
||||
const { status } = await retryInvalidToken(attempt, async () => {
|
||||
await run("codex", ["logout"]);
|
||||
await codexDeviceLogin();
|
||||
});
|
||||
if (!status.success) throw new Error(`codex exited with ${status.code}`);
|
||||
return await Deno.readTextFile(file);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user