

















@@ -2,12 +2,14 @@ import { execFileSync, spawnSync } from "node:child_process";
22import { createPrivateKey, createSign } from "node:crypto";
33import { readFileSync } from "node:fs";
44import { pathToFileURL } from "node:url";
5+import { parseStrictIntegerOption } from "./lib/dev-tooling-safety.ts";
5667const APP_ID_ENV = "OPENCLAW_GH_READ_APP_ID";
78const KEY_FILE_ENV = "OPENCLAW_GH_READ_PRIVATE_KEY_FILE";
89const INSTALLATION_ID_ENV = "OPENCLAW_GH_READ_INSTALLATION_ID";
910const PERMISSIONS_ENV = "OPENCLAW_GH_READ_PERMISSIONS";
1011const API_VERSION = "2022-11-28";
12+const DEFAULT_GITHUB_FETCH_TIMEOUT_MS = 30_000;
1113const DEFAULT_READ_PERMISSION_KEYS = [
1214"actions",
1315"checks",
@@ -32,6 +34,11 @@ type AccessTokenResponse = {
3234token: string;
3335};
343637+type GitHubJsonOptions = {
38+fetchImpl?: typeof fetch;
39+timeoutMs?: number;
40+};
41+3542export function parseRepoArg(args: string[]): string | null {
3643for (let i = 0; i < args.length; i += 1) {
3744const arg = args[i];
@@ -91,6 +98,15 @@ export function buildReadPermissions(
9198return permissions;
9299}
93100101+export function resolveGitHubFetchTimeoutMs(raw = process.env.OPENCLAW_GH_READ_FETCH_TIMEOUT_MS) {
102+return parseStrictIntegerOption({
103+fallback: DEFAULT_GITHUB_FETCH_TIMEOUT_MS,
104+label: "OPENCLAW_GH_READ_FETCH_TIMEOUT_MS",
105+min: 1,
106+ raw,
107+});
108+}
109+94110function isMainModule() {
95111const entry = process.argv[1];
96112return entry ? import.meta.url === pathToFileURL(entry).href : false;
@@ -151,32 +167,65 @@ function createAppJwt(appId: string, privateKeyPem: string) {
151167return `${signingInput}.${base64UrlEncode(signature)}`;
152168}
153169154-async function githubJson<T>(
170+async function withGitHubFetchTimeout<T>(
171+label: string,
172+timeoutMs: number,
173+run: (signal: AbortSignal) => Promise<T>,
174+): Promise<T> {
175+const controller = new AbortController();
176+let timeout: ReturnType<typeof setTimeout> | undefined;
177+const timeoutPromise = new Promise<T>((_resolve, reject) => {
178+timeout = setTimeout(() => {
179+const error = new Error(`${label} exceeded timeout of ${timeoutMs}ms`);
180+reject(error);
181+controller.abort(error);
182+}, timeoutMs);
183+});
184+try {
185+return await Promise.race([run(controller.signal), timeoutPromise]);
186+} finally {
187+if (timeout) {
188+clearTimeout(timeout);
189+}
190+}
191+}
192+193+export async function githubJson<T>(
155194path: string,
156195bearerToken: string,
157196init?: {
158197method?: "GET" | "POST";
159198body?: unknown;
160199},
200+options: GitHubJsonOptions = {},
161201): Promise<T> {
162-const response = await fetch(`https://api.github.com${path}`, {
163-method: init?.method ?? "GET",
164-headers: {
165-Accept: "application/vnd.github+json",
166-Authorization: `Bearer ${bearerToken}`,
167-"Content-Type": "application/json",
168-"User-Agent": "openclaw-gh-read",
169-"X-GitHub-Api-Version": API_VERSION,
170-},
171-body: init?.body === undefined ? undefined : JSON.stringify(init.body),
172-});
202+const fetchImpl = options.fetchImpl ?? fetch;
203+const timeoutMs = options.timeoutMs ?? resolveGitHubFetchTimeoutMs();
204+return await withGitHubFetchTimeout(
205+`GitHub API ${init?.method ?? "GET"} ${path}`,
206+timeoutMs,
207+async (signal) => {
208+const response = await fetchImpl(`https://api.github.com${path}`, {
209+method: init?.method ?? "GET",
210+headers: {
211+Accept: "application/vnd.github+json",
212+Authorization: `Bearer ${bearerToken}`,
213+"Content-Type": "application/json",
214+"User-Agent": "openclaw-gh-read",
215+"X-GitHub-Api-Version": API_VERSION,
216+},
217+body: init?.body === undefined ? undefined : JSON.stringify(init.body),
218+ signal,
219+});
173220174-if (!response.ok) {
175-const text = await response.text();
176-fail(`${init?.method ?? "GET"} ${path} failed (${response.status}): ${text}`);
177-}
221+ if (!response.ok) {
222+ const text = await response.text();
223+ fail(`${init?.method ?? "GET"} ${path} failed (${response.status}): ${text}`);
224+ }
178225179-return (await response.json()) as T;
226+return (await response.json()) as T;
227+},
228+);
180229}
181230182231async function resolveInstallation(
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。