kimi-code / packages /agent-core-v2 /src /app /plugin /github-resolver.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
68d7816 verified
Raw
History Blame Contribute Delete
4.31 kB
import { Error2, ErrorCodes } from '#/errors';
import type { GithubRef } from './source';
import type { PluginGithubRef } from './types';
export interface GithubSourceInput {
readonly kind: 'github';
readonly owner: string;
readonly repo: string;
readonly ref?: GithubRef;
}
export interface GithubSourceResolution {
readonly tarballUrl: string;
readonly displayVersion: string;
readonly ref: PluginGithubRef;
}
export async function resolveGithubCommitSha(
owner: string,
repo: string,
ref: string,
): Promise<string> {
const encodedRef = encodeCodeloadRefPath(ref);
const url = `https://github.com/${owner}/${repo}/commits/${encodedRef}.atom`;
const resp = await fetch(url, {
headers: { accept: 'application/atom+xml', range: 'bytes=0-4095' },
signal: AbortSignal.timeout(10_000),
});
if (!resp.ok) {
throw new Error2(
ErrorCodes.PLUGIN_LOAD_FAILED,
`Could not resolve ${owner}/${repo}@${ref}: HTTP ${resp.status} ${resp.statusText}.`,
{ details: { owner, repo, ref, status: resp.status } },
);
}
const feed = await resp.text();
const sha = /Grit::Commit\/([0-9a-f]{40})/i.exec(feed)?.[1];
if (sha === undefined) {
throw new Error2(
ErrorCodes.PLUGIN_LOAD_FAILED,
`Could not resolve ${owner}/${repo}@${ref} to a commit SHA.`,
{ details: { owner, repo, ref } },
);
}
return sha.toLowerCase();
}
export async function resolveGithubSource(
input: GithubSourceInput,
): Promise<GithubSourceResolution> {
const { owner, repo } = input;
if (input.ref !== undefined) {
return {
tarballUrl: codeloadUrl(owner, repo, input.ref),
displayVersion: input.ref.value,
ref: { kind: input.ref.kind, value: input.ref.value },
};
}
const latestTag = await tryResolveLatestReleaseTag(owner, repo);
if (latestTag !== undefined) {
return {
tarballUrl: codeloadUrl(owner, repo, { kind: 'tag', value: latestTag }),
displayVersion: latestTag,
ref: { kind: 'tag', value: latestTag },
};
}
const headProbe = await fetch(`https://codeload.github.com/${owner}/${repo}/zip/HEAD`, {
method: 'HEAD',
signal: AbortSignal.timeout(10_000),
});
if (headProbe.status === 404) {
throw new Error2(
ErrorCodes.PLUGIN_LOAD_FAILED,
`Repository \`${owner}/${repo}\` not found or not accessible.`,
{ details: { owner, repo } },
);
}
if (!headProbe.ok) {
throw new Error2(
ErrorCodes.PLUGIN_LOAD_FAILED,
`Could not access \`${owner}/${repo}\`: HTTP ${headProbe.status} ${headProbe.statusText}.`,
{ details: { owner, repo, status: headProbe.status } },
);
}
return {
tarballUrl: `https://codeload.github.com/${owner}/${repo}/zip/HEAD`,
displayVersion: 'HEAD',
ref: { kind: 'branch', value: 'HEAD' },
};
}
async function tryResolveLatestReleaseTag(owner: string, repo: string): Promise<string | undefined> {
const url = `https://github.com/${owner}/${repo}/releases/latest`;
const resp = await fetch(url, {
redirect: 'manual',
signal: AbortSignal.timeout(10_000),
});
if (resp.status === 404) return undefined;
if (resp.status !== 301 && resp.status !== 302) {
throw new Error2(
ErrorCodes.PLUGIN_LOAD_FAILED,
`Could not look up latest release of \`${owner}/${repo}\`: ` +
`HTTP ${resp.status} ${resp.statusText} (${url}). ` +
`Pin a specific ref with \`/tree/<branch|tag|sha>\` to bypass release lookup.`,
{ details: { owner, repo, status: resp.status, url } },
);
}
const location = resp.headers.get('location');
if (location === null) return undefined;
const match = /\/releases\/tag\/([^/?#]+)/.exec(location);
if (match === null) return undefined;
try {
return decodeURIComponent(match[1]!);
} catch {
return match[1];
}
}
function codeloadUrl(owner: string, repo: string, ref: GithubRef): string {
const base = `https://codeload.github.com/${owner}/${repo}/zip`;
const encoded = encodeCodeloadRefPath(ref.value);
if (ref.kind === 'sha') return `${base}/${encoded}`;
if (ref.kind === 'tag') return `${base}/refs/tags/${encoded}`;
return `${base}/${encoded}`;
}
function encodeCodeloadRefPath(value: string): string {
return value.split('/').map(encodeURIComponent).join('/');
}