UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

276 lines (275 loc) • 13 kB
import { c as isRecord } from "./record-coerce-TIsquoVR.mjs"; import { Et as _enum, Fn as object, Jn as string, Kt as custom, Lt as boolean, Nt as array, Pn as number, nr as tuple, or as union, sr as unknown, xn as literal } from "./schemas-BOYIvvln.mjs"; import { n as GATEWAY_CLIENT_IDS, r as GATEWAY_CLIENT_MODES } from "./client-info-C8RatpKO.mjs"; import { s as SessionPermissionModeSchema } from "./sessions-row-DAFw32gg.mjs"; import "./worker-protocol-primitives-BajTgPT4.mjs"; import { l as WorkerInferenceModelRefSchema, u as WorkerInferenceOptionsSchema } from "./worker-inference-B7U4TRc2.mjs"; import { L as WorkerTranscriptMessageSchema, R as WorkerTranscriptUserMessageSchema, b as WorkerConnectRequestFrameSchema } from "./worker-admission-CNeLWzVQ.mjs"; import "./version-CwNT1gaY.mjs"; import { d as ComputerUseCapabilityDescriptorSchema } from "./computer-use-contract-g23cJXOi.mjs"; import { i as normalizeTlsFingerprint } from "./client-address-utils-BafGwQBm.mjs"; import { a as resolveGatewayWebSocketTransport, n as GatewayWebSocketTransportConfigurationError } from "./websocket-transport-D1HAAgy3.mjs"; import { a as workerProtocolObject, r as hasExactOwnKeys } from "./protocol-record-CQCXx-YV.mjs"; import { n as SkillResourceDeliverySchema } from "./skill-resources-BYZ-VOKK.mjs"; import { n as isWorkerDesktopArgs, r as isWorkerDesktopString } from "./worker-desktop-descriptor-DZyj9bOd.mjs"; import { n as isWorkerTranscriptMessageFrameSafe } from "./transcript-message-Dz1c-AeG.mjs"; import path from "node:path"; import { Value } from "typebox/value"; //#region packages/gateway-client/src/cloudflare-access.ts const CF_ACCESS_CLIENT_ID_HEADER = "CF-Access-Client-Id"; const CF_ACCESS_CLIENT_SECRET_HEADER = "CF-Access-Client-Secret"; /** Build only the two headers Cloudflare Access defines for service-token auth. */ function buildCloudflareAccessHeaders(credentials) { return { [CF_ACCESS_CLIENT_ID_HEADER]: credentials.clientId, [CF_ACCESS_CLIENT_SECRET_HEADER]: credentials.clientSecret }; } //#endregion //#region src/worker/worker-connection-endpoint.ts const ENDPOINT_FIELD_MAX_LENGTH = 4096; const WORKER_CONNECTION_ENDPOINT_MAX_JSON_BYTES = Math.max(Buffer.byteLength(JSON.stringify({ kind: "unix", socketPath: "\0".repeat(256) })), Buffer.byteLength(JSON.stringify({ kind: "websocket", url: "\0".repeat(ENDPOINT_FIELD_MAX_LENGTH), tlsFingerprint: "0".repeat(64), cloudflareAccess: { clientId: "\0".repeat(ENDPOINT_FIELD_MAX_LENGTH), clientSecret: "\0".repeat(ENDPOINT_FIELD_MAX_LENGTH) } }))); var WorkerConnectionEndpointError = class extends Error { constructor(..._args) { super(..._args); this.name = "WorkerConnectionEndpointError"; } }; const AccessCredential = string().refine((value) => Boolean(value.trim()) && value.length <= ENDPOINT_FIELD_MAX_LENGTH); const EndpointSchema = union([workerProtocolObject({ kind: literal("unix"), socketPath: string().refine((value) => value.length <= 256 && path.isAbsolute(value) && !value.includes(":")) }), workerProtocolObject({ kind: literal("websocket"), url: string().refine((value) => value.length <= ENDPOINT_FIELD_MAX_LENGTH), tlsFingerprint: string().transform(normalizeTlsFingerprint).refine(Boolean).optional(), cloudflareAccess: workerProtocolObject({ clientId: AccessCredential, clientSecret: AccessCredential }).optional() }).refine((value) => { const url = URL.parse(value.url); return url !== null && (url.protocol === "ws:" || url.protocol === "wss:") && url.username === "" && url.password === "" && url.search === "" && url.hash === "" && url.pathname.endsWith("/__openclaw__/worker") && (value.tlsFingerprint === void 0 && value.cloudflareAccess === void 0 || url.protocol === "wss:"); }).transform(({ tlsFingerprint, cloudflareAccess, ...endpoint }) => ({ ...endpoint, ...tlsFingerprint ? { tlsFingerprint } : {}, ...cloudflareAccess ? { cloudflareAccess } : {} }))]); function parseWorkerConnectionEndpoint(value) { return EndpointSchema.safeParse(value).data; } function resolveWorkerConnectionTarget(endpoint, env = process.env) { if (endpoint.kind === "unix") return { url: `ws+unix://${endpoint.socketPath}:/`, options: {} }; if (endpoint.cloudflareAccess && new URL(endpoint.url).protocol !== "wss:") throw new WorkerConnectionEndpointError("Cloudflare Access credentials require a wss:// worker endpoint"); try { const transport = resolveGatewayWebSocketTransport({ url: endpoint.url, tlsFingerprint: endpoint.tlsFingerprint, env, options: endpoint.cloudflareAccess ? { followRedirects: false, headers: buildCloudflareAccessHeaders(endpoint.cloudflareAccess) } : {} }); return { url: endpoint.url, ...transport }; } catch (error) { if (error instanceof GatewayWebSocketTransportConfigurationError) throw new WorkerConnectionEndpointError(error.message); throw error; } } //#endregion //#region src/worker/launch-descriptor.ts const LAUNCH_VERSION = 4; const Identifier = string().min(1).max(256).refine((value) => value.trim() === value); const Sequence = number().int().min(0).max(Number.MAX_SAFE_INTEGER); const AbsoluteHostPath = string().refine((value) => path.posix.isAbsolute(value) || path.win32.isAbsolute(value)); const WorkspacePath = AbsoluteHostPath.refine((value) => value.trim() === value && value.length <= 4096 && !value.includes("\0")); const ExecAuthorityFields = { security: _enum([ "deny", "allowlist", "full" ]), ask: _enum([ "off", "on-miss", "always" ]), safeBins: tuple([]).optional() }; const ExecAuthoritySchema = union([unknown().refine((value) => isRecord(value) && value.node === void 0).pipe(workerProtocolObject({ host: _enum(["sandbox", "gateway"]), ...ExecAuthorityFields })), workerProtocolObject({ host: literal("node"), ...ExecAuthorityFields, node: string().min(1).refine((value) => value.trim() === value).optional() }).transform(({ node, ...authority }) => ({ ...authority, ...node === void 0 ? {} : { node } }))]).refine((value) => !Object.hasOwn(value, "safeBins") || value.safeBins !== void 0); const ToolAuthoritySchema = workerProtocolObject({ allowedToolNames: array(Identifier).max(256).refine((names) => new Set(names).size === names.length), exec: ExecAuthoritySchema.optional() }).transform(({ exec, ...authority }) => ({ ...authority, ...exec === void 0 ? {} : { exec } })); const BrowserLaunchSchema = workerProtocolObject({ cdpUrl: string().refine((value) => { const url = URL.parse(value); return url !== null && url.protocol === "http:" && url.hostname === "127.0.0.1" && url.username === "" && url.password === "" && url.port !== "" && Number(url.port) >= 1 && Number(url.port) <= 65535 && url.pathname === "/" && url.search === "" && url.hash === ""; }), launcherPath: AbsoluteHostPath.refine(isWorkerDesktopString), launcherArgs: custom(isWorkerDesktopArgs).optional() }); const ComputerLaunchSchema = workerProtocolObject({ nodeId: Identifier, computerUse: custom((value) => Value.Check(ComputerUseCapabilityDescriptorSchema, value)) }); const GitAuthorField = string().max(256).refine((value) => Boolean(value.trim()) && !/[\0\r\n]/u.test(value)); const GitAuthorSchema = workerProtocolObject({ name: GitAuthorField.optional(), email: GitAuthorField.optional() }).refine((value) => Object.values(value).every((entry) => entry !== void 0)); const GitHubLaunchSchema = workerProtocolObject({ token: string().min(1).max(2048).refine((value) => !/[\s\p{Cc}]/u.test(value)), login: string().regex(/^[A-Za-z0-9-]{1,39}$/u).refine((value) => value.trim() === value), branch: string().min(1).max(256).refine((value) => !/[\s~^:?*[\\]/u.test(value) && !value.includes("\0") && !value.startsWith("-") && !value.includes("..") && !value.includes("@{")), remoteUrl: string().regex(/^https:\/\/github\.com\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\.git$/u).refine((value) => value.trim() === value).optional(), gitAuthor: GitAuthorSchema.optional() }).refine((value) => Object.values(value).every((entry) => entry !== void 0)); function parseWorkerGitHubLaunchBinding(value) { return GitHubLaunchSchema.safeParse(value).data; } const AssignmentSchema = workerProtocolObject({ skillAuthoring: workerProtocolObject({ multipleProfiles: boolean() }).optional(), skillResources: custom((value) => Value.Check(SkillResourceDeliverySchema, value)).optional(), agentId: Identifier, operationalRunInstance: object({ instanceId: Identifier, runId: Identifier }).readonly(), agentRuntimeIdentityToken: string().min(1).max(16384), runId: Identifier, turnId: Identifier, prompt: custom((value) => typeof value === "string" || Value.Check(WorkerTranscriptUserMessageSchema, { role: "user", content: value, timestamp: 0 })), suppressPromptTranscript: boolean(), workspaceDir: WorkspacePath, modelRef: custom((value) => Value.Check(WorkerInferenceModelRefSchema, value)), inferenceOptions: custom((value) => Value.Check(WorkerInferenceOptionsSchema, value)), systemPrompt: string().optional(), initialMessages: custom((value) => Array.isArray(value) && value.length <= 1024 && value.every((message) => Value.Check(WorkerTranscriptMessageSchema, message))), transcript: workerProtocolObject({ baseLeafId: Identifier.nullable(), nextSeq: Sequence.min(1) }), liveEvents: workerProtocolObject({ ackedSeq: Sequence, nextSeq: Sequence.min(1) }).refine((value) => value.nextSeq === value.ackedSeq + 1), toolAuthority: ToolAuthoritySchema, browser: BrowserLaunchSchema.optional(), computer: ComputerLaunchSchema.optional(), github: GitHubLaunchSchema.optional(), permissionMode: custom((value) => Value.Check(SessionPermissionModeSchema, value)).optional(), workerContainmentRoot: WorkspacePath.optional() }).refine((value) => value.operationalRunInstance.runId === value.runId && value.toolAuthority.allowedToolNames.includes("computer") === (value.computer !== void 0) && (!Object.hasOwn(value, "github") || value.github !== void 0) && (Object.hasOwn(value, "permissionMode") ? value.permissionMode !== void 0 && value.workerContainmentRoot !== void 0 : !Object.hasOwn(value, "workerContainmentRoot"))); function buildWorkerConnectParams(descriptor) { return { minProtocol: 4, maxProtocol: 4, client: { id: GATEWAY_CLIENT_IDS.WORKER, version: descriptor.admission.handshake.openclawVersion, platform: process.platform, mode: GATEWAY_CLIENT_MODES.WORKER }, role: "worker", admission: { ...descriptor.admission, runId: descriptor.assignment.runId } }; } function validateWorkerLaunchPlan(candidate) { const frame = { type: "req", id: "launch-validation", method: "connect", params: buildWorkerConnectParams(candidate) }; if (!Value.Check(WorkerConnectRequestFrameSchema, frame) || candidate.admission.sessionId === null || candidate.admission.ownerEpoch < 1 || !isWorkerTranscriptMessageFrameSafe({ role: "user", content: typeof candidate.assignment.prompt === "string" ? [{ type: "text", text: candidate.assignment.prompt }] : candidate.assignment.prompt, timestamp: Number.MAX_SAFE_INTEGER })) throw new Error("invalid worker launch descriptor"); return candidate; } function parseWorkerLaunchPlan(value) { if (!isRecord(value) || !hasExactOwnKeys(value, [ "version", "admission", "assignment" ]) || value.version !== LAUNCH_VERSION) throw new Error("invalid worker launch descriptor"); const parsed = AssignmentSchema.safeParse(value.assignment).data; if (!parsed || !isRecord(value.admission)) throw new Error("invalid worker launch descriptor"); const { permissionMode, workerContainmentRoot, ...assignment } = parsed; return validateWorkerLaunchPlan({ version: LAUNCH_VERSION, admission: value.admission, assignment: permissionMode !== void 0 && workerContainmentRoot !== void 0 ? { ...assignment, permissionMode, workerContainmentRoot } : assignment }); } function completeWorkerLaunchDescriptor(plan, connectionEndpoint) { const parsedPlan = parseWorkerLaunchPlan(plan); const parsedEndpoint = parseWorkerConnectionEndpoint(connectionEndpoint); if (!parsedEndpoint) throw new Error("invalid worker launch descriptor"); return { ...parsedPlan, connectionEndpoint: parsedEndpoint }; } function parseWorkerLaunchDescriptor(value) { if (!isRecord(value) || !hasExactOwnKeys(value, [ "version", "connectionEndpoint", "admission", "assignment" ])) throw new Error("invalid worker launch descriptor"); return completeWorkerLaunchDescriptor({ version: value.version, admission: value.admission, assignment: value.assignment }, value.connectionEndpoint); } //#endregion export { parseWorkerLaunchPlan as a, parseWorkerConnectionEndpoint as c, CF_ACCESS_CLIENT_SECRET_HEADER as d, buildCloudflareAccessHeaders as f, parseWorkerLaunchDescriptor as i, resolveWorkerConnectionTarget as l, completeWorkerLaunchDescriptor as n, WORKER_CONNECTION_ENDPOINT_MAX_JSON_BYTES as o, parseWorkerGitHubLaunchBinding as r, WorkerConnectionEndpointError as s, buildWorkerConnectParams as t, CF_ACCESS_CLIENT_ID_HEADER as u };