gemini retries

This commit is contained in:
Shawn Morreau
2025-11-26 15:14:18 -05:00
parent 4ff547f673
commit eaa35168ea
3 changed files with 49 additions and 20 deletions
+2 -2
View File
@@ -144,12 +144,12 @@ const messageHandlers = {
export const gemini = agent({ export const gemini = agent({
name: "gemini", name: "gemini",
install: async () => { install: async (githubInstallationToken?: string) => {
return await installFromGithub({ return await installFromGithub({
owner: "google-gemini", owner: "google-gemini",
repo: "gemini-cli", repo: "gemini-cli",
tag: "v0.16.0",
assetName: "gemini.js", assetName: "gemini.js",
...(githubInstallationToken && { githubInstallationToken }),
}); });
}, },
run: async ({ payload, apiKey, mcpServers, githubInstallationToken, cliPath }) => { run: async ({ payload, apiKey, mcpServers, githubInstallationToken, cliPath }) => {
+41 -17
View File
@@ -62,9 +62,9 @@ export interface InstallFromCurlParams {
export interface InstallFromGithubParams { export interface InstallFromGithubParams {
owner: string; owner: string;
repo: string; repo: string;
tag?: string;
assetName?: string; assetName?: string;
executablePath?: string; executablePath?: string;
githubInstallationToken?: string;
} }
/** /**
@@ -180,6 +180,36 @@ export async function installFromNpmTarball({
return cliPath; return cliPath;
} }
/**
* Fetch with retry logic if Retry-After header is present
*/
async function fetchWithRetry(
url: string,
headers: Record<string, string>,
errorMessage: string
): Promise<Response> {
const response = await fetch(url, { headers });
if (!response.ok) {
const retryAfter = response.headers.get("Retry-After") || response.headers.get("retry-after");
if (retryAfter) {
const waitSeconds = parseInt(retryAfter, 10);
if (!Number.isNaN(waitSeconds) && waitSeconds > 0) {
log.info(`Rate limited, waiting ${waitSeconds} seconds before retry...`);
await new Promise((resolve) => setTimeout(resolve, waitSeconds * 1000));
const retryResponse = await fetch(url, { headers });
if (!retryResponse.ok) {
throw new Error(
`${errorMessage}: ${retryResponse.status} ${retryResponse.statusText} (retry failed)`
);
}
return retryResponse;
}
}
throw new Error(`${errorMessage}: ${response.status} ${response.statusText}`);
}
return response;
}
/** /**
* Install a CLI tool from GitHub releases * Install a CLI tool from GitHub releases
* Downloads the latest release asset from GitHub and returns the path to the executable * Downloads the latest release asset from GitHub and returns the path to the executable
@@ -188,24 +218,23 @@ export async function installFromNpmTarball({
export async function installFromGithub({ export async function installFromGithub({
owner, owner,
repo, repo,
tag,
assetName, assetName,
executablePath, executablePath,
githubInstallationToken,
}: InstallFromGithubParams): Promise<string> { }: InstallFromGithubParams): Promise<string> {
log.info(`📦 Installing ${owner}/${repo} from GitHub releases...`); log.info(`📦 Installing ${owner}/${repo} from GitHub releases...`);
// fetch release from GitHub API (specific tag or latest) // fetch release from GitHub API (latest)
const releaseUrl = tag const releaseUrl = `https://api.github.com/repos/${owner}/${repo}/releases/latest`;
? `https://api.github.com/repos/${owner}/${repo}/releases/tags/${tag}`
: `https://api.github.com/repos/${owner}/${repo}/releases/latest`;
log.info(`Fetching release from ${releaseUrl}...`); log.info(`Fetching release from ${releaseUrl}...`);
const releaseResponse = await fetch(releaseUrl);
if (!releaseResponse.ok) { const headers: Record<string, string> = {};
throw new Error( if (githubInstallationToken) {
`Failed to fetch release: ${releaseResponse.status} ${releaseResponse.statusText}` headers.Authorization = `Bearer ${githubInstallationToken}`;
);
} }
const releaseResponse = await fetchWithRetry(releaseUrl, headers, "Failed to fetch release");
const releaseData = (await releaseResponse.json()) as { const releaseData = (await releaseResponse.json()) as {
tag_name: string; tag_name: string;
assets: Array<{ assets: Array<{
@@ -234,12 +263,7 @@ export async function installFromGithub({
const downloadPath = join(tempDir, fileName); const downloadPath = join(tempDir, fileName);
// download the asset // download the asset
const assetResponse = await fetch(assetUrl); const assetResponse = await fetchWithRetry(assetUrl, headers, "Failed to download asset");
if (!assetResponse.ok) {
throw new Error(
`Failed to download asset: ${assetResponse.status} ${assetResponse.statusText}`
);
}
if (!assetResponse.body) throw new Error("Response body is null"); if (!assetResponse.body) throw new Error("Response body is null");
const fileStream = createWriteStream(downloadPath); const fileStream = createWriteStream(downloadPath);
+6 -1
View File
@@ -300,7 +300,12 @@ function setupMcpServers(ctx: MainContext): void {
} }
async function installAgentCli(ctx: MainContext): Promise<void> { async function installAgentCli(ctx: MainContext): Promise<void> {
ctx.cliPath = await ctx.agent.install(); // gemini is the only agent that needs githubInstallationToken for install
if (ctx.agentName === "gemini") {
ctx.cliPath = await ctx.agent.install(ctx.githubInstallationToken);
} else {
ctx.cliPath = await ctx.agent.install();
}
} }
function validateApiKey(ctx: MainContext): void { function validateApiKey(ctx: MainContext): void {