Make bot review setup self-contained #11
@@ -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,
|
// GITEA_TOKEN; everything this script posts goes through the reviewer token,
|
||||||
// so it appears as the bot account.
|
// so it appears as the bot account.
|
||||||
import { TextLineStream } from "jsr:@std/streams@1/text-line-stream";
|
import { TextLineStream } from "jsr:@std/streams@1/text-line-stream";
|
||||||
|
import { retryInvalidToken } from "./auth.ts";
|
||||||
|
|
||||||
type GiteaUser = { login: string; email: string };
|
type GiteaUser = { login: string; email: string };
|
||||||
type GiteaComment = { user: GiteaUser; created_at: string; body: string };
|
type GiteaComment = { user: GiteaUser; created_at: string; body: string };
|
||||||
@@ -252,7 +253,8 @@ async function runCodex(prompt: string): Promise<string> {
|
|||||||
.output();
|
.output();
|
||||||
if (!loggedIn.success) await codexDeviceLogin();
|
if (!loggedIn.success) await codexDeviceLogin();
|
||||||
const file = await Deno.makeTempFile();
|
const file = await Deno.makeTempFile();
|
||||||
const status = await new Deno.Command("codex", {
|
const attempt = async () => {
|
||||||
|
const codex = new Deno.Command("codex", {
|
||||||
args: [
|
args: [
|
||||||
"exec",
|
"exec",
|
||||||
"--model",
|
"--model",
|
||||||
@@ -265,8 +267,21 @@ async function runCodex(prompt: string): Promise<string> {
|
|||||||
env: agentEnv,
|
env: agentEnv,
|
||||||
clearEnv: true,
|
clearEnv: true,
|
||||||
stdout: "inherit",
|
stdout: "inherit",
|
||||||
stderr: "inherit",
|
stderr: "piped",
|
||||||
}).spawn().status;
|
}).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}`);
|
if (!status.success) throw new Error(`codex exited with ${status.code}`);
|
||||||
return await Deno.readTextFile(file);
|
return await Deno.readTextFile(file);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user