UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

150 lines (149 loc) • 6.25 kB
import { c as isRecord } from "./record-coerce-TIsquoVR.mjs"; import { i as fetchWithSsrFGuard } from "./fetch-guard-CIAMeSj8.mjs"; import { t as ErrorCodes } from "./gateway-error-details-DIefe7Zy.mjs"; import { f as errorShape } from "./error-codes-DI20vDEB.mjs"; import { n as detectMime } from "./mime-CGDYrRzL.mjs"; import "./string-coerce-runtime-B1b8UNzb.mjs"; import "./ssrf-runtime-DDaKW8yK.mjs"; import "./media-mime-DLFR6fqC.mjs"; import { t as dispatchGatewayMethod } from "./gateway-method-runtime-B291LJmm.mjs"; import "./gateway-runtime-C4L8u4H3.mjs"; import { a as parseGitHubLinkParams, i as parseGitHubItemTarget, r as githubTargetUrl } from "./targets-fjDDX6wC.mjs"; import { n as githubPreviewView, p as readBoundedResponse, r as ControlUiGitHubError, s as discardResponse, u as formatControlUiGitHubPreviewError } from "./view-model-By2aRfvd.mjs"; //#region extensions/github/src/image.ts const IMAGE_HOSTS = [ "github.com", "user-images.githubusercontent.com", "private-user-images.githubusercontent.com", "github-production-user-asset-6210df.s3.amazonaws.com" ]; const ATTACHMENT_PATH = /^\/user-attachments\/assets\/[a-f\d]{8}(?:-[a-f\d]{4}){3}-[a-f\d]{12}$/i; const RASTER_TYPES = /* @__PURE__ */ new Set([ "image/png", "image/jpeg", "image/gif", "image/webp" ]); const MAX_IMAGE_BYTES = 2097152; function isImageUrl(url, initial) { return url.protocol === "https:" && !url.username && !url.password && !url.port && IMAGE_HOSTS.includes(url.hostname) && (url.hostname !== "github.com" || ATTACHMENT_PATH.test(url.pathname)) && (!initial || !url.hostname.endsWith(".s3.amazonaws.com")); } function parseGitHubImageParams(params) { if (!isRecord(params) || typeof params.url !== "string" || params.url.length > 8192) return; try { const url = new URL(params.url); return isImageUrl(url, true) ? url.href : void 0; } catch { return; } } async function loadGitHubImage(url) { const { response, release } = await fetchWithSsrFGuard({ url, requireHttps: true, timeoutMs: 15e3, maxRedirects: 3, capture: false, policy: { hostnameAllowlist: IMAGE_HOSTS }, resolveDispatcherPolicy: (target) => { if (!isImageUrl(target, false)) throw new Error("GitHub image redirect is not permitted"); }, init: { credentials: "omit", headers: { Accept: [...RASTER_TYPES].join(",") } } }); try { if (!response.ok) throw new Error("GitHub image is unavailable"); const body = await readBoundedResponse(response, MAX_IMAGE_BYTES); const mime = await detectMime({ buffer: body }); if (!mime || !RASTER_TYPES.has(mime)) throw new Error("GitHub attachment is not a supported image"); return { url, dataUrl: `data:${mime};base64,${body.toString("base64")}` }; } finally { await discardResponse(response); await release(); } } //#endregion //#region extensions/github/src/preview-contract.ts /** Validate the untyped dispatch payload before projecting it into a generic reader. */ function isControlUiGitHubPreview(value) { if (!isRecord(value) || !parseGitHubItemTarget(value)) return false; return [ "createdAt", "login", "state", "title", "updatedAt" ].every((key) => typeof value[key] === "string") && [ "avatarDataUrl", "closedAt", "mergedAt", "stateReason" ].every((key) => value[key] === void 0 || typeof value[key] === "string") && [ "additions", "changedFiles", "comments", "deletions", "coAuthorCount" ].every((key) => value[key] === void 0 || typeof value[key] === "number" && Number.isFinite(value[key])) && (value.draft === void 0 || typeof value.draft === "boolean") && (value.coAuthors === void 0 || Array.isArray(value.coAuthors) && value.coAuthors.every((author) => isRecord(author) && typeof author.login === "string" && (author.avatarDataUrl === void 0 || typeof author.avatarDataUrl === "string"))); } //#endregion //#region extensions/github/src/handlers.ts async function handleGitHubRequest(method, { params, respond }) { const parsed = parseGitHubLinkParams(params); if (!parsed || method === "github.preview" && parsed.target.kind === "commit") { respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "invalid " + method + " params")); return; } try { const result = await dispatchGatewayMethod(method === "github.preview" ? "controlUi.githubPreview" : "controlUi.githubDetail", { ...parsed.target, ...parsed.agentId ? { agentId: parsed.agentId } : {}, ...parsed.refresh ? { refresh: true } : {} }); if (!result.ok) { respond(false, result.payload, result.error, result.meta); return; } if (method === "github.preview" && parsed.target.kind !== "commit") { if (!isControlUiGitHubPreview(result.payload) || githubTargetUrl(result.payload).toLowerCase() !== githubTargetUrl(parsed.target).toLowerCase()) throw new ControlUiGitHubError(502, "GitHub preview returned an invalid response"); respond(true, { ...githubPreviewView(result.payload), url: parsed.url }, void 0, result.meta); } else { const document = result.payload; if (!isRecord(document) || typeof document.title !== "string" || typeof document.body !== "string" || typeof document.url !== "string" || document.url.toLowerCase() !== githubTargetUrl(parsed.target).toLowerCase()) throw new ControlUiGitHubError(502, "GitHub document returned a different resource"); respond(true, { ...document, url: parsed.url, filesExpanded: parsed.filesExpanded }, void 0, result.meta); } } catch (error) { const { message, ...details } = formatControlUiGitHubPreviewError(error); respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, message, details)); } } const githubHandlers = { "github.image": async ({ params, respond }) => { const url = parseGitHubImageParams(params); if (!url) { respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "invalid github.image params")); return; } try { respond(true, await loadGitHubImage(url), void 0); } catch { respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, "GitHub image is unavailable")); } }, "github.preview": (options) => handleGitHubRequest("github.preview", options), "github.detail": (options) => handleGitHubRequest("github.detail", options) }; //#endregion export { githubHandlers };