UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

285 lines (284 loc) 13.8 kB
import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js"; import { t as WorkerProviderError } from "./capability-provider.types-cizOzEy5.js"; import "./string-coerce-runtime-GQa0ehRA.js"; import "./plugin-entry-zfBGJaNO.js"; import fs from "node:fs"; import path from "node:path"; import { createHash } from "node:crypto"; //#region extensions/crabbox/src/crabbox-worker-timeouts.ts const CRABBOX_WARMUP_ATTEMPTS = 2; const CRABBOX_WARMUP_TIMEOUT_MS = CRABBOX_WARMUP_ATTEMPTS * 15e5; const CRABBOX_DESKTOP_WARMUP_TIMEOUT_MS = CRABBOX_WARMUP_ATTEMPTS * 3e6; const CRABBOX_LIFECYCLE_TIMEOUT_MS = 6e4; const CRABBOX_STOP_TIMEOUT_MS = 1005e3; const CRABBOX_COMMAND_SETTLEMENT_TIMEOUT_MS = 1e4; const CRABBOX_HEARTBEAT_TIMEOUT_MS = 15e4; const CRABBOX_MACHINE_CATALOG_TIMEOUT_MS = 5e3; const CRABBOX_MACHINE0_LIFECYCLE_TIMEOUT_MS = 3e5; const CRABBOX_SETUP_TIMEOUT_MS = 9e5; const CRABBOX_NODE_ENROLLMENT_TIMEOUT_MS = 9e5; const CRABBOX_NODE_ENROLLMENT_DIAGNOSTIC_TIMEOUT_MS = 6e4; function resolveCrabboxReadyPollIntervalMs(provider) { return provider === "machine0" ? 6e4 : 2e3; } function resolveCrabboxLifecycleTimeoutMs(provider) { return provider === "machine0" ? CRABBOX_MACHINE0_LIFECYCLE_TIMEOUT_MS : CRABBOX_LIFECYCLE_TIMEOUT_MS; } function resolveCrabboxProvisionBaseTimeoutMs(profile) { return (profile.desktop ? CRABBOX_DESKTOP_WARMUP_TIMEOUT_MS : CRABBOX_WARMUP_TIMEOUT_MS) + resolveCrabboxLifecycleTimeoutMs(profile.provider) * (profile.provider === "machine0" ? 2 : 1); } function countCrabboxProvisionSetupPhases(profile) { return Number(Boolean(profile.desktop)) + Number(Boolean(profile.setup)); } function resolveCrabboxProvisionCallTimeoutMs(profile) { return resolveCrabboxProvisionBaseTimeoutMs(profile) + countCrabboxProvisionSetupPhases(profile) * CRABBOX_SETUP_TIMEOUT_MS + CRABBOX_NODE_ENROLLMENT_TIMEOUT_MS + CRABBOX_NODE_ENROLLMENT_DIAGNOSTIC_TIMEOUT_MS + CRABBOX_STOP_TIMEOUT_MS + 3 * CRABBOX_COMMAND_SETTLEMENT_TIMEOUT_MS; } //#endregion //#region extensions/crabbox/src/crabbox-worker-profile.ts const PROFILE_KEYS = /* @__PURE__ */ new Set([ "binary", "class", "desktop", "idleTimeout", "provider", "setup", "setupEnv", "ttl", "warmImage" ]); const GO_DURATION_PATTERN = /^\+?(?:(?:\d+(?:\.\d*)?|\.\d+)(?:ns|us|µs|μs|ms|s|m|h))+$/u; const GO_DURATION_TOKEN_PATTERN = /(\d+(?:\.\d*)?|\.\d+)(ns|us|µs|μs|ms|s|m|h)/gu; const MAX_GO_DURATION_NANOSECONDS = 9223372036854775807n; const CRABBOX_LEASE_ID_DOMAIN = "openclaw:crabbox-worker-lease-id:v1\0"; const LEGACY_PROVISION_OPERATION_ID_PATTERN = /^provision:[a-f0-9]{64}$/u; const DURATION_UNIT_NANOSECONDS = { h: 3600000000000n, m: 60000000000n, s: 1000000000n, ms: 1000000n, us: 1000n, µs: 1000n, μs: 1000n, ns: 1n }; const MAX_CRABBOX_MACHINE_CLASS_LENGTH = 128; const MAX_CRABBOX_MACHINE_OPTIONS = 32; const CRABBOX_DESKTOP_PROVIDERS = /* @__PURE__ */ new Set(["aws", "hetzner"]); const CRABBOX_WORKER_PROVIDER_ID = "crabbox"; function requirePositiveDuration(value, key) { const duration = normalizeOptionalString(value); const nanoseconds = duration ? parsePositiveGoDurationNanoseconds(duration) : void 0; if (!duration || nanoseconds === void 0) throw new WorkerProviderError(`Crabbox profile ${key} must be a positive Go duration such as 60m`); return { duration, milliseconds: Number(nanoseconds) / 1e6 }; } function parsePositiveGoDurationNanoseconds(duration) { if (!GO_DURATION_PATTERN.test(duration)) return; let total = 0n; for (const match of duration.matchAll(GO_DURATION_TOKEN_PATTERN)) { const numberText = match[1]; const unit = match[2] ? DURATION_UNIT_NANOSECONDS[match[2]] : void 0; if (!numberText || unit === void 0) return; const [wholeText = "", fractionText = ""] = numberText.split(".", 2); const whole = wholeText.replace(/^0+/u, "") || "0"; if (whole.length > 19) return; total += BigInt(whole) * unit; const fraction = fractionText.slice(0, 18); if (fraction) total += BigInt(fraction) * unit / 10n ** BigInt(fraction.length); if (total > MAX_GO_DURATION_NANOSECONDS) return; } return total > 0n ? total : void 0; } function heartbeatIntervalMs(idleTimeoutMs) { const referenceIntervalMs = Math.max(5e3, Math.min(6e4, idleTimeoutMs / 3)); return Math.min(referenceIntervalMs, Math.max(1, Math.floor(idleTimeoutMs / 2))); } function parseCrabboxProfile(profile) { for (const key of Object.keys(profile)) if (!PROFILE_KEYS.has(key)) throw new WorkerProviderError(`unknown Crabbox profile setting: ${key}`); const provider = normalizeOptionalString(profile.provider)?.toLowerCase(); const machineClass = normalizeOptionalString(profile.class); if (!provider) throw new WorkerProviderError("Crabbox profile provider must be a non-empty string"); if (profile.class !== void 0 && !machineClass) throw new WorkerProviderError("Crabbox profile class must be a non-empty string"); const { duration: ttl } = requirePositiveDuration(profile.ttl, "ttl"); const { duration: idleTimeout, milliseconds: idleTimeoutMs } = requirePositiveDuration(profile.idleTimeout, "idleTimeout"); const binaryValue = profile.binary; const binary = binaryValue === void 0 ? void 0 : normalizeOptionalString(binaryValue); if (binaryValue !== void 0 && !binary) throw new WorkerProviderError("Crabbox profile binary must be a non-empty string"); if (binary && !path.isAbsolute(binary)) throw new WorkerProviderError("Crabbox profile binary must be an absolute path"); const setupValue = profile.setup; const setup = setupValue === void 0 ? void 0 : normalizeOptionalString(setupValue); if (setupValue !== void 0 && !setup) throw new WorkerProviderError("Crabbox profile setup must be a non-empty command string"); let setupEnv; if (profile.setupEnv !== void 0) { if (!Array.isArray(profile.setupEnv)) throw new WorkerProviderError("Crabbox profile setupEnv must be an array"); if (profile.setupEnv.length > 16) throw new WorkerProviderError("Crabbox profile setupEnv must contain at most 16 names"); setupEnv = profile.setupEnv.map((name) => { if (typeof name !== "string" || !/^[A-Za-z_][A-Za-z0-9_]*$/u.test(name)) throw new WorkerProviderError("Crabbox profile setupEnv must contain only valid POSIX environment variable names"); if (name === "CRABBOX_ENV_ALLOW") throw new WorkerProviderError(`Crabbox profile setupEnv name ${name} is reserved`); return name; }); if (new Set(setupEnv).size !== setupEnv.length) throw new WorkerProviderError("Crabbox profile setupEnv must not contain duplicate names"); if (setupEnv.length > 0 && !setup) throw new WorkerProviderError("Crabbox profile setupEnv requires setup"); } const desktop = profile.desktop; if (desktop !== void 0 && typeof desktop !== "boolean") throw new WorkerProviderError("Crabbox profile desktop must be a boolean"); if (desktop && !CRABBOX_DESKTOP_PROVIDERS.has(provider)) throw new WorkerProviderError("Crabbox desktop profiles support only AWS and coordinator-backed Hetzner"); const warmImage = profile.warmImage; if (warmImage !== void 0 && typeof warmImage !== "boolean") throw new WorkerProviderError("Crabbox profile warmImage must be a boolean"); return { binary, class: machineClass, desktop, heartbeatIntervalMs: heartbeatIntervalMs(idleTimeoutMs), heartbeatTimeoutMs: Math.min(CRABBOX_HEARTBEAT_TIMEOUT_MS, Math.max(1, Math.floor(idleTimeoutMs / 2))), idleTimeout, provider, setup, setupEnv, ttl, warmImage }; } function resolveCrabboxProfileSetupEnv(setupEnv) { if (!setupEnv?.length) return; return Object.fromEntries(setupEnv.map((name) => { const value = process.env[name]; if (!Object.hasOwn(process.env, name) || value === void 0) throw new Error(`Crabbox profile setupEnv variable is missing: ${name}`); return [name, value]; })); } function resolveCrabboxWarmImageProfile(profile, machineClass = profile.class) { return { ...profile, class: machineClass, warmImage: profile.warmImage ?? (machineClass !== void 0 && !profile.setupEnv?.length) }; } function resolveCrabboxProvisionProfile(profile, requestedClassValue) { const configured = parseCrabboxProfile(profile); const requestedClass = normalizeOptionalString(requestedClassValue); if (requestedClassValue !== void 0 && (!requestedClass || requestedClass.length > MAX_CRABBOX_MACHINE_CLASS_LENGTH)) throw new WorkerProviderError("Crabbox machine class must be a non-empty string of at most 128 characters"); const resolved = resolveCrabboxWarmImageProfile(configured, requestedClass ?? configured.class); let provisionProfile; if (!resolved.warmImage) provisionProfile = { ...resolved, warmImage: false }; else { if (!resolved.class) throw new WorkerProviderError("Crabbox warmImage requires a configured class or a placement machine class"); provisionProfile = { ...resolved, class: resolved.class, warmImage: true }; } return { profile: provisionProfile, forwardedEnv: resolveCrabboxProfileSetupEnv(resolved.setupEnv) }; } function listCrabboxMachineOptions(configuredClass, shapes = []) { const seen = /* @__PURE__ */ new Set(); const candidates = shapes.filter((shape) => { if (shape.class.length > MAX_CRABBOX_MACHINE_CLASS_LENGTH || seen.has(shape.class)) return false; seen.add(shape.class); return true; }); if (candidates.length === 0) return []; const catalogLimit = configuredClass === void 0 || candidates.slice(0, MAX_CRABBOX_MACHINE_OPTIONS).some((shape) => shape.class === configuredClass) ? MAX_CRABBOX_MACHINE_OPTIONS : 31; const options = candidates.slice(0, catalogLimit).map((shape) => { const id = shape.class; const result = { id, label: id.replace(/^./u, (initial) => initial.toUpperCase()) }; if (shape.cpu !== void 0) result.cpu = shape.cpu; if (shape.memoryGb !== void 0) result.memoryGb = shape.memoryGb; if (id === configuredClass) result.default = true; return result; }); if (configuredClass !== void 0 && !options.some((option) => option.id === configuredClass)) options.push({ id: configuredClass, label: configuredClass, default: true }); return options; } function buildCrabboxAllocationArgs(profile, leaseId, slug) { const args = [ "--provider", profile.provider, "--network", "public", "--tailscale=false", ...profile.class ? ["--class", profile.class] : [], "--ttl", profile.ttl, "--idle-timeout", profile.idleTimeout, "--lease-id", leaseId, "--slug", slug, "--keep=true" ]; if (profile.desktop) args.push("--desktop", "--browser", "--desktop-env", "xfce"); return args; } function defaultIsExecutable(candidate, platform) { try { if (!fs.statSync(candidate).isFile()) return false; fs.accessSync(candidate, platform === "win32" ? fs.constants.F_OK : fs.constants.X_OK); return true; } catch { return false; } } function binaryCandidates(base, platform) { return platform === "win32" ? [ ".exe", ".cmd", ".bat", ".com", "" ].map((suffix) => `${base}${suffix}`) : [base]; } function resolveCrabboxBinary(params) { if (params.explicit) return params.explicit; return findCrabboxBinary(params) ?? "crabbox"; } function findCrabboxBinary(params) { const platform = params.platform ?? process.platform; const isExecutable = params.isExecutable ?? ((candidate) => defaultIsExecutable(candidate, platform)); if (params.explicit) return isExecutable(params.explicit) ? params.explicit : void 0; const siblingBase = path.resolve(params.openclawRoot, "../crabbox/bin/crabbox"); for (const candidate of binaryCandidates(siblingBase, platform)) if (isExecutable(candidate)) return candidate; const delimiter = platform === "win32" ? ";" : ":"; const executableNames = binaryCandidates("crabbox", platform); for (const directory of (params.pathEnv ?? "").split(delimiter)) { if (!directory) continue; for (const name of executableNames) { const candidate = path.resolve(directory, name); if (isExecutable(candidate)) return candidate; } } } function resolveOpenClawRoot(pluginRoot) { if (!pluginRoot) return process.cwd(); const extensionsDir = path.dirname(pluginRoot); if (path.basename(extensionsDir) !== "extensions") return process.cwd(); const extensionParent = path.dirname(extensionsDir); return path.basename(extensionParent) === "dist" || path.basename(extensionParent) === "dist-runtime" ? path.dirname(extensionParent) : extensionParent; } function operationSlug(operationId) { return `openclaw-${createHash("sha256").update(operationId).digest("hex").slice(0, 32)}`; } function operationLeaseId(operationId) { if (!operationId.trim()) throw new Error("Crabbox provision requires an operation id"); if (LEGACY_PROVISION_OPERATION_ID_PATTERN.test(operationId)) throw new Error("Legacy Crabbox provision state cannot be replayed safely; clean up any prior lease and dispatch again"); return `cbx_${createHash("sha256").update(CRABBOX_LEASE_ID_DOMAIN).update(operationId).digest("hex").slice(0, 12)}`; } //#endregion export { resolveCrabboxProvisionBaseTimeoutMs as C, resolveCrabboxLifecycleTimeoutMs as S, resolveCrabboxReadyPollIntervalMs as T, CRABBOX_NODE_ENROLLMENT_TIMEOUT_MS as _, operationLeaseId as a, CRABBOX_WARMUP_TIMEOUT_MS as b, resolveCrabboxBinary as c, resolveOpenClawRoot as d, CRABBOX_COMMAND_SETTLEMENT_TIMEOUT_MS as f, CRABBOX_NODE_ENROLLMENT_DIAGNOSTIC_TIMEOUT_MS as g, CRABBOX_MACHINE_CATALOG_TIMEOUT_MS as h, listCrabboxMachineOptions as i, resolveCrabboxProvisionProfile as l, CRABBOX_LIFECYCLE_TIMEOUT_MS as m, buildCrabboxAllocationArgs as n, operationSlug as o, CRABBOX_DESKTOP_WARMUP_TIMEOUT_MS as p, findCrabboxBinary as r, parseCrabboxProfile as s, CRABBOX_WORKER_PROVIDER_ID as t, resolveCrabboxWarmImageProfile as u, CRABBOX_SETUP_TIMEOUT_MS as v, resolveCrabboxProvisionCallTimeoutMs as w, countCrabboxProvisionSetupPhases as x, CRABBOX_STOP_TIMEOUT_MS as y };