fix(extensions): resolve GitHub API 415 error for source tarballs (#13319)

This commit is contained in:
Justin Poehnelt
2025-12-04 10:16:08 -07:00
committed by GitHub
parent 8b0a8f47c1
commit 205d0f456e
2 changed files with 276 additions and 9 deletions

View File

@@ -355,7 +355,17 @@ export async function downloadFromGitHubRelease(
}
try {
await downloadFile(archiveUrl, downloadedAssetPath);
// GitHub API requires different Accept headers for different types of downloads:
// 1. Binary Assets (e.g. release artifacts): Require 'application/octet-stream' to return the raw content.
// 2. Source Tarballs (e.g. /tarball/{ref}): Require 'application/vnd.github+json' (or similar) to return
// a 302 Redirect to the actual download location (codeload.github.com).
// Sending 'application/octet-stream' for tarballs results in a 415 Unsupported Media Type error.
const headers = {
...(asset
? { Accept: 'application/octet-stream' }
: { Accept: 'application/vnd.github+json' }),
};
await downloadFile(archiveUrl, downloadedAssetPath, { headers });
} catch (error) {
return {
failureReason: 'failed to download asset',
@@ -472,24 +482,42 @@ export function findReleaseAsset(assets: Asset[]): Asset | undefined {
return undefined;
}
export async function downloadFile(url: string, dest: string): Promise<void> {
const headers: {
'User-agent': string;
Accept: string;
Authorization?: string;
} = {
export interface DownloadOptions {
headers?: Record<string, string>;
}
export async function downloadFile(
url: string,
dest: string,
options?: DownloadOptions,
redirectCount: number = 0,
): Promise<void> {
const headers: Record<string, string> = {
'User-agent': 'gemini-cli',
Accept: 'application/octet-stream',
...options?.headers,
};
const token = getGitHubToken();
if (token) {
headers.Authorization = `token ${token}`;
headers['Authorization'] = `token ${token}`;
}
return new Promise((resolve, reject) => {
https
.get(url, { headers }, (res) => {
if (res.statusCode === 302 || res.statusCode === 301) {
downloadFile(res.headers.location!, dest).then(resolve).catch(reject);
if (redirectCount >= 10) {
return reject(new Error('Too many redirects'));
}
if (!res.headers.location) {
return reject(
new Error('Redirect response missing Location header'),
);
}
downloadFile(res.headers.location, dest, options, redirectCount + 1)
.then(resolve)
.catch(reject);
return;
}
if (res.statusCode !== 200) {