UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

116 lines (115 loc) 5.76 kB
import { i as getOrCreatePromise } from "./lazy-promise-DGqyc4Y4.js"; import { Et as array, Rn as string, Tn as object, wn as number } from "./schemas-zxit8y5H.js"; import { t as pruneMapToMaxSize } from "./map-size-CNcWiFKu.js"; import { a as resolveGitHubActionsRequest, s as BoardValidationError } from "./github-actions-capability-rN2d0K3V.js"; import { a as getActiveSecretsRuntimeConfigSnapshot } from "./runtime-state-C4aJ8Hzz.js"; import { c as prepareGitHubReadIdentity, n as GitHubIdentityError } from "./github-tool-identity-CsNyDjEx.js"; import { n as ControlUiGitHubError, o as fetchGitHubApi, p as readGitHubJsonResponse } from "./control-ui-github-api-CCpVJnCA.js"; import { r as BoardGatewayUnavailableError } from "./board-view-ticket-B5Cj1xmB.js"; import { r as requestCurrentGitHubOAuthRefresh } from "./github-oauth-lifecycle-DMB5peav.js"; //#region src/gateway/github-actions-read.ts const CACHE_TTL_MS = 3e4; const CACHE_LIMIT = 32; const MAX_CONCURRENT_READS = 32; const ACTIONS_MAX_RESPONSE_BYTES = 1048576; const integer = number().int().nonnegative().max(Number.MAX_SAFE_INTEGER); const shortText = string().max(256); const timestamp = string().max(40).datetime({ offset: true }); const runsSchema = object({ total_count: integer, workflow_runs: array(object({ id: integer, name: shortText.nullable(), display_title: string().max(1024), head_branch: shortText.nullable(), status: shortText.nullable(), conclusion: shortText.nullable(), html_url: string().max(2048).url(), run_started_at: timestamp.nullable(), created_at: timestamp, updated_at: timestamp, event: shortText, workflow_id: integer, run_attempt: integer })).max(30) }); const gatewayCaches = /* @__PURE__ */ new WeakMap(); function actionsFailure(error) { if (error instanceof BoardValidationError || error instanceof BoardGatewayUnavailableError || error instanceof GitHubIdentityError) return error; if (error instanceof ControlUiGitHubError) { if (error.statusCode === 429) return /* @__PURE__ */ new Error("GitHub Actions is rate limited; wait and retry."); if (error.statusCode === 401) return /* @__PURE__ */ new Error("GitHub Actions authentication expired; reconnect the agent's GitHub identity in Settings."); if (error.statusCode === 403 || error.statusCode === 404) return /* @__PURE__ */ new Error("GitHub Actions access denied or repository unavailable; check the repository and the selected agent identity's Actions read permission."); } return /* @__PURE__ */ new Error("GitHub Actions request failed or exceeded its response limit; retry with a smaller perPage or check GitHub availability."); } /** Pinning and reads share the same source-config scrub, refresh, and current credential check. */ async function prepareBoardGitHubIdentity(context, authority) { try { const config = context.getRuntimeConfig(); const identity = await prepareGitHubReadIdentity({ config, sourceConfig: getActiveSecretsRuntimeConfigSnapshot()?.sourceConfig ?? config, agentId: authority.boardSession.agentId, getCurrentConfig: () => context.getRuntimeConfig(), assertActive: authority.assertActive, refresh: () => requestCurrentGitHubOAuthRefresh(authority.boardSession.agentId) }); await identity.revalidate(); return identity; } catch (error) { if (error instanceof GitHubIdentityError || error instanceof BoardValidationError || error instanceof BoardGatewayUnavailableError) throw error; throw new GitHubIdentityError("unverified"); } } async function readBoardGitHubActions(params, context, authority) { const request = resolveGitHubActionsRequest(params); authority.assertActive(); const cache = gatewayCaches.get(context) ?? { active: 0, values: /* @__PURE__ */ new Map(), pending: /* @__PURE__ */ new Map() }; gatewayCaches.set(context, cache); if (cache.active >= MAX_CONCURRENT_READS) throw new Error("GitHub Actions reads are busy; retry shortly."); cache.active += 1; try { const identity = await prepareBoardGitHubIdentity(context, authority); identity.assertSelected(); const key = JSON.stringify([ authority.boardSession, identity.cacheScope, request.url ]); const cached = cache.values.get(key); if (cached && cached.expiresAt > Date.now()) return structuredClone(cached.value); cache.values.delete(key); const result = await getOrCreatePromise(cache.pending, key, async () => { const response = await fetchGitHubApi(request.url, fetch, identity.token, async () => { throw new BoardValidationError("invalid_operation", "GitHub Actions redirected the request; verify the repository/workflow, update the widget grant if needed, and retry."); }); const raw = await readGitHubJsonResponse(response, ACTIONS_MAX_RESPONSE_BYTES); const parsed = runsSchema.safeParse(raw); if (!parsed.success || parsed.data.workflow_runs.length > request.perPage || JSON.stringify(parsed.data).includes(identity.token)) throw new Error("Invalid Actions response"); for (const run of parsed.data.workflow_runs) { const url = new URL(run.html_url); if (url.origin !== "https://github.com" || url.username || url.password || url.search || url.hash || url.pathname.toLowerCase() !== `/${request.repository}/actions/runs/${run.id}`) throw new Error("Invalid Actions run URL"); } cache.values.set(key, { value: parsed.data, expiresAt: Date.now() + CACHE_TTL_MS }); pruneMapToMaxSize(cache.values, CACHE_LIMIT); return parsed.data; }, { evictOnSettled: true }); await identity.revalidate(); identity.assertSelected(); return structuredClone(result); } catch (error) { throw actionsFailure(error); } finally { cache.active -= 1; } } //#endregion export { prepareBoardGitHubIdentity, readBoardGitHubActions };