UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

139 lines (138 loc) • 6.45 kB
import { r as createRetainedCache } from "./git-read-cache-DiYjgftZ.mjs"; import { n as gitHubPublicApi } from "./github-public-api-D6N-IT-v.mjs"; import { i as sessionPullRequestRepositoryApiUrl, n as parsePullListItem, r as fetchSessionPullRequestCheckDetails } from "./control-ui-session-prs-CTOLpTaP.mjs"; //#region src/gateway/control-ui-session-pr-check-details.ts const checkDetailsCache = createRetainedCache(); let activeCheckDetails = 0; const CHECK_DETAILS_CACHE_MS = 3e4; const MAX_CHECK_DETAIL_REQUESTS = 4; /** The only details admission path: clients select a PR already resolved for this session. */ async function loadControlUiSessionPullRequestChecks(params, deps) { const { owner, repo, number, headSha } = params; const target = { owner, repo, number, headSha }; const unavailable = (error) => ({ ...target, checks: [], status: "unavailable", rateLimited: false, error }); deps.assertCurrent(); const credential = gitHubPublicApi.resolveGitHubApiCredentialScope(); const assertCredentialCurrent = () => { if (gitHubPublicApi.resolveGitHubApiCredentialScope().cacheScope !== credential.cacheScope) throw new gitHubPublicApi.ControlUiGitHubError(409, "GitHub identity changed; reopen CI details"); }; const assertCurrent = () => { deps.assertCurrent(); assertCredentialCurrent(); }; const matchesTarget = (candidate) => candidate.owner.toLowerCase() === owner.toLowerCase() && candidate.repo.toLowerCase() === repo.toLowerCase() && candidate.number === number && candidate.headSha === headSha; const { loadPullRequests } = deps; const snapshot = await loadPullRequests({ sessionKey: params.sessionKey, agentId: params.agentId }, { fetchImpl: deps.fetchImpl }); assertCurrent(); const pull = snapshot.pullRequests.find(matchesTarget); if (!pull || pull.state !== "open" && pull.state !== "draft") return unavailable("The session pull request or head changed; reopen CI details"); const key = JSON.stringify([ deps.sessionScope, owner.toLowerCase(), repo.toLowerCase(), number, headSha, credential.cacheScope ]); let entry = checkDetailsCache.get(key); if (!entry || entry.expiresAt <= Date.now()) { if (activeCheckDetails >= MAX_CHECK_DETAIL_REQUESTS) return { ...unavailable("CI details are busy; retry shortly"), retryAfterMs: 5e3 }; const previous = entry?.lastGood; const pending = { expiresAt: Infinity, promise: Promise.resolve(unavailable("CI details are loading")), lastGood: previous }; activeCheckDetails += 1; const fetchImpl = deps.fetchImpl ?? fetch; const load = async () => { const deadline = Date.now() + 25e3; const signal = AbortSignal.timeout(25e3); let requests = 0; const request = async (url, maxBytes) => { assertCredentialCurrent(); if (++requests > 64 || Date.now() > deadline) throw new gitHubPublicApi.ControlUiGitHubError(502, "CI details exceeded the request budget; open the job on GitHub"); return gitHubPublicApi.withOptionalGitHubAuth(credential.token, async (token) => gitHubPublicApi.readGitHubJsonResponse(await gitHubPublicApi.fetchGitHubApi(url, fetchImpl, token, async () => { throw new gitHubPublicApi.ControlUiGitHubError(409, "GitHub repository changed; reopen CI details"); }, void 0, void 0, signal), maxBytes)); }; const assertHead = async () => { const value = parsePullListItem(await request(`${sessionPullRequestRepositoryApiUrl(target)}/pulls/${number}`)); if (!value || value.owner.toLowerCase() !== owner.toLowerCase() || value.repo.toLowerCase() !== repo.toLowerCase() || value.number !== number || value.headSha?.toLowerCase() !== headSha || value.state !== "open" && value.state !== "draft") throw new gitHubPublicApi.ControlUiGitHubError(409, "The pull request head changed; reopen CI details"); }; try { await assertHead(); const details = await fetchSessionPullRequestCheckDetails(target, request); if (!(details.error instanceof gitHubPublicApi.ControlUiGitHubError && details.error.statusCode === 429)) await assertHead(); const result = { ...target, checks: details.checks, status: details.error ? "stale" : "ready", rateLimited: false }; if (Buffer.byteLength(JSON.stringify(result), "utf8") > 524288) throw new gitHubPublicApi.ControlUiGitHubError(502, "CI details exceeded the response limit; open the job on GitHub"); if (details.error) { const formatted = gitHubPublicApi.formatControlUiGitHubPreviewError(details.error); result.error = formatted.message; result.rateLimited = details.error instanceof gitHubPublicApi.ControlUiGitHubError && details.error.statusCode === 429; result.retryAfterMs = formatted.retryAfterMs; } pending.lastGood = result; return result; } catch (error) { const formatted = gitHubPublicApi.formatControlUiGitHubPreviewError(error); const changed = error instanceof gitHubPublicApi.ControlUiGitHubError && error.statusCode === 409; const rateLimited = error instanceof gitHubPublicApi.ControlUiGitHubError && error.statusCode === 429; const retain = !changed && previous && error instanceof gitHubPublicApi.ControlUiGitHubError && (error.retryable || error.statusCode === 502); if (!retain) pending.lastGood = void 0; return { ...target, checks: retain ? previous.checks : [], status: retain ? "stale" : "unavailable", rateLimited, error: changed ? error.message : formatted.message, retryAfterMs: formatted.retryAfterMs }; } }; pending.promise = load().then((result) => { pending.expiresAt = Date.now() + Math.max(CHECK_DETAILS_CACHE_MS, result.retryAfterMs ?? 0); return result; }).finally(() => { activeCheckDetails -= 1; }); checkDetailsCache.set(key, pending); entry = pending; } const result = await entry.promise; assertCurrent(); const current = await loadPullRequests({ sessionKey: params.sessionKey, agentId: params.agentId }, { fetchImpl: deps.fetchImpl }); assertCurrent(); if (!current.pullRequests.some((candidate) => matchesTarget(candidate) && (candidate.state === "open" || candidate.state === "draft"))) return unavailable("The session pull request or head changed; reopen CI details"); return structuredClone({ ...result, ...result.retryAfterMs === void 0 ? {} : { retryAfterMs: Math.max(0, entry.expiresAt - Date.now()) } }); } //#endregion export { loadControlUiSessionPullRequestChecks };