import { resolveSupervisorToken } from "@rakazo/core"; import { signGithubBrokerRunToken } from "@rakazo/core/node/github-broker-token"; export type GithubBrokerSessionInput = { runId: string; workspacePath: string; allowedRemoteUrl: string | null; defaultBranch: string | null; githubToken: string; /** Where the workspace is mounted inside the computer. */ containerRoot: string; }; function supervisorBaseUrl(): string | undefined { const raw = process.env.SANDBOX_SUPERVISOR_URL?.trim(); return raw ? raw.replace(/\/$/, "") : undefined; } const LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "[::1]", "::1"]); /** The computer runs in a container, so a loopback supervisor address must become the host gateway. */ export function githubBrokerBaseUrl(): string | undefined { const configured = process.env.SANDBOX_GITHUB_BROKER_URL?.trim(); if (configured) return configured.replace(/\/$/, ""); const supervisor = supervisorBaseUrl(); if (!supervisor) return undefined; const url = new URL(`${supervisor}/internal/github-broker`); if (LOOPBACK_HOSTS.has(url.hostname)) url.hostname = "host.docker.internal"; return url.toString().replace(/\/$/, ""); } function supervisorAuthHeaders(): Record | undefined { const token = resolveSupervisorToken(process.env); return { authorization: `Bearer ${token}`, "content-type": "application/json" }; } export function githubBrokerRunToken(runId: string): string { return signGithubBrokerRunToken(runId, resolveSupervisorToken(process.env)); } export function githubBrokerEnvironmentForRun(runId: string): Record { const base = githubBrokerBaseUrl(); if (!base) return {}; return { RAKAZO_GITHUB_BROKER_URL: base, RAKAZO_GITHUB_BROKER_RUN_TOKEN: githubBrokerRunToken(runId), }; } export async function registerGithubBrokerSession(input: GithubBrokerSessionInput): Promise { const base = supervisorBaseUrl(); const headers = supervisorAuthHeaders(); if (!base || !headers) return; const res = await fetch(`${base}/internal/github-broker/sessions`, { method: "POST", headers, body: JSON.stringify({ runId: input.runId, workspacePath: input.workspacePath, allowedRemoteUrl: input.allowedRemoteUrl, defaultBranch: input.defaultBranch, githubToken: input.githubToken, containerRoot: input.containerRoot, pushApproved: false, }), }); if (!res.ok) { const detail = await res.text().catch(() => ""); throw new Error(`github broker session registration failed: ${res.status} ${detail}`.trim()); } } export async function setGithubBrokerPushApproved( runId: string, pushApproved: boolean, ): Promise { const base = supervisorBaseUrl(); const headers = supervisorAuthHeaders(); if (!base || !headers) return; await fetch(`${base}/internal/github-broker/sessions/${encodeURIComponent(runId)}`, { method: "PATCH", headers, body: JSON.stringify({ pushApproved }), }).catch(() => undefined); } export async function deleteGithubBrokerSession(runId: string): Promise { const base = supervisorBaseUrl(); const headers = supervisorAuthHeaders(); if (!base || !headers) return; await fetch(`${base}/internal/github-broker/sessions/${encodeURIComponent(runId)}`, { method: "DELETE", headers, }).catch(() => undefined); }