UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

228 lines (227 loc) 9.92 kB
import "./number-coercion-DBgYsQt5.js"; import "./fs-safe-defaults-BbLMaB4h.js"; import "node:fs/promises"; import path from "node:path"; import "node:fs"; import { isPathInside } from "@openclaw/fs-safe/path"; import { sameFileIdentity } from "@openclaw/fs-safe/advanced"; import os from "node:os"; import "@openclaw/fs-safe/output"; import "@openclaw/fs-safe/root"; import "@openclaw/fs-safe/errors"; import "@openclaw/fs-safe/secret"; import "@openclaw/fs-safe/secure-file"; import "@openclaw/fs-safe/walk"; //#region src/shared/regexp.ts /** Escape text so it can be embedded literally inside a RegExp pattern. */ function escapeRegExp(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } //#endregion //#region src/infra/safe-cwd.ts function tryProcessCwd() { try { return process.cwd(); } catch { return; } } //#endregion //#region src/infra/home-dir.ts function normalize(value) { const trimmed = value?.trim(); if (!trimmed || trimmed === "undefined" || trimmed === "null") return; return trimmed; } function normalizeSafe(homedir) { try { return normalize(homedir()); } catch { return; } } function resolveTermuxHome(env) { const prefix = normalize(env.PREFIX); if (!prefix || !normalize(env.ANDROID_DATA)) return; if (!/(?:^|\/)com\.termux\/files\/usr\/?$/u.test(prefix.replace(/\\/gu, "/"))) return; return path.resolve(prefix, "..", "home"); } function resolveRawOsHomeDir(env, homedir) { return normalize(env.HOME) ?? normalize(env.USERPROFILE) ?? resolveTermuxHome(env) ?? normalizeSafe(homedir); } function resolveRawHomeDir(env, homedir) { const explicitHome = normalize(env.OPENCLAW_HOME); if (!explicitHome) return resolveRawOsHomeDir(env, homedir); if (explicitHome === "~" || explicitHome.startsWith("~/") || explicitHome.startsWith("~\\")) { const fallbackHome = resolveRawOsHomeDir(env, homedir); return fallbackHome ? explicitHome.replace(/^~(?=$|[\\/])/, () => fallbackHome) : void 0; } return explicitHome; } /** Resolves OpenClaw's effective home, honoring OPENCLAW_HOME before OS homes. */ function resolveEffectiveHomeDir(env = process.env, homedir = os.homedir) { const raw = resolveRawHomeDir(env, homedir); return raw ? path.resolve(raw) : void 0; } /** Resolves the effective home or falls back to cwd when no home source exists. */ function resolveRequiredHomeDir(env = process.env, homedir = os.homedir) { const resolved = resolveEffectiveHomeDir(env, homedir) ?? tryProcessCwd(); if (resolved) return path.resolve(resolved); throw new Error("Unable to resolve an OpenClaw home: set OPENCLAW_HOME, HOME, or USERPROFILE, or run from an existing directory."); } /** Expands leading `~`, `~/`, or `~\` with the effective home when one is known. */ function expandHomePrefix(input, opts) { if (!input.startsWith("~")) return input; const home = normalize(opts?.home) ?? resolveEffectiveHomeDir(opts?.env ?? process.env, opts?.homedir ?? os.homedir); if (!home) return input; return input.replace(/^~(?=$|[\\/])/, () => home); } /** Resolves a user-supplied path after trimming and expanding against the effective home. */ function resolveHomeRelativePath(input, opts) { const trimmed = input.trim(); if (!trimmed) return trimmed; if (trimmed.startsWith("~")) { const expanded = expandHomePrefix(trimmed, { home: resolveRequiredHomeDir(opts?.env ?? process.env, opts?.homedir ?? os.homedir), env: opts?.env, homedir: opts?.homedir }); return path.resolve(expanded); } return path.resolve(trimmed); } /** Resolves a user path against the effective home, preserving an empty input. */ function resolveUserPath(input, env = process.env, homedir = os.homedir) { if (!input) return ""; return resolveHomeRelativePath(input, { env, homedir }); } //#endregion //#region packages/retry/src/index.ts const MAX_TIMER_TIMEOUT_MS = 2147e6; const DEFAULT_RETRY_CONFIG = { attempts: 3, minDelayMs: 300, maxDelayMs: 3e4, jitter: 0 }; const defaultSleep = (ms) => new Promise((resolve) => { setTimeout(resolve, ms); }); function clampNumber(value, fallback, min, max) { const next = Number.isFinite(value) ? value : void 0; if (next === void 0) return fallback; return Math.min(Math.max(next, min ?? Number.NEGATIVE_INFINITY), max ?? Number.POSITIVE_INFINITY); } function resolveAttemptCount(value, fallback) { return Math.max(1, Math.round(Number.isFinite(value) ? value : fallback)); } function resolveRetryDelayMs(value) { const finite = value === Number.POSITIVE_INFINITY ? MAX_TIMER_TIMEOUT_MS : Number.isFinite(value) ? value : 0; return Math.min(Math.max(Math.round(finite), 0), MAX_TIMER_TIMEOUT_MS); } function resolveJitterConfig(value, fallback) { if (value === "full") return "full"; const fraction = Number.isFinite(value) ? value : void 0; return fraction === void 0 ? fallback : Math.min(Math.max(fraction, 0), 1); } function resolveRetryConfig(defaults = DEFAULT_RETRY_CONFIG, overrides) { const attempts = resolveAttemptCount(overrides?.attempts, defaults.attempts); const minDelayMs = resolveRetryDelayMs(clampNumber(overrides?.minDelayMs, defaults.minDelayMs, 0)); return { attempts, minDelayMs, maxDelayMs: Math.max(minDelayMs, resolveRetryDelayMs(clampNumber(overrides?.maxDelayMs, defaults.maxDelayMs, 0))), jitter: resolveJitterConfig(overrides?.jitter, defaults.jitter) }; } function applyJitter(delayMs, jitter, mode, random) { if (jitter === "full") { if (mode === "symmetric") return Math.max(0, Math.round(delayMs * (.5 + random() * .5))); return Math.max(0, Math.ceil(delayMs * (1 + random()))); } if (jitter <= 0) return mode === "positive" ? Math.ceil(delayMs) : delayMs; const fraction = random(); const raw = delayMs * (1 + (mode === "positive" ? fraction * jitter : (fraction * 2 - 1) * jitter)); return Math.max(0, mode === "positive" ? Math.ceil(raw) : Math.round(raw)); } function toRetryError(value, fallbackMessage = "Non-Error thrown") { if (value instanceof Error) return value; if (typeof value === "string") return new Error(value); const error = new Error(fallbackMessage, { cause: value }); if (typeof value === "object" && value !== null || typeof value === "function") Object.assign(error, value); return error; } function createRetryRunner(runtime = {}) { const runtimeSleep = runtime.sleep ?? defaultSleep; const runtimeRandom = runtime.random ?? Math.random; const createFailure = runtime.createFailure ?? ((errors) => toRetryError(errors.at(-1) ?? /* @__PURE__ */ new Error("Retry failed"))); return async function retryAsync(fn, attemptsOrOptions = 3, initialDelayMs = 300) { const attemptErrors = []; if (typeof attemptsOrOptions === "number") { const attempts = resolveAttemptCount(attemptsOrOptions, DEFAULT_RETRY_CONFIG.attempts); for (let index = 0; index < attempts; index += 1) try { return await fn(); } catch (err) { attemptErrors.push(err); if (index === attempts - 1) break; await runtimeSleep(resolveRetryDelayMs(initialDelayMs * 2 ** index)); } throw createFailure(attemptErrors); } const options = attemptsOrOptions; const resolved = resolveRetryConfig(DEFAULT_RETRY_CONFIG, options); const maxAttempts = resolved.attempts; const minDelayMs = resolved.minDelayMs; const maxDelayMs = resolved.maxDelayMs > 0 ? resolved.maxDelayMs : Number.POSITIVE_INFINITY; const retryAfterMaxDelayMs = options.retryAfterMaxDelayMs === void 0 ? maxDelayMs : Math.max(minDelayMs, resolveRetryDelayMs(clampNumber(options.retryAfterMaxDelayMs, maxDelayMs, 0))); const random = options.random ?? runtimeRandom; const sleep = options.sleep ?? runtimeSleep; const shouldRetry = options.shouldRetry ?? (() => true); for (let attempt = 1; attempt <= maxAttempts; attempt += 1) try { return await fn(); } catch (err) { attemptErrors.push(err); if (attempt >= maxAttempts || !shouldRetry(err, attempt)) break; const context = { attempt, maxAttempts, err, label: options.label }; const retryAfterMs = options.retryAfterMs?.(err); const hasRetryAfter = typeof retryAfterMs === "number" && Number.isFinite(retryAfterMs); const configuredDelay = typeof options.delayMs === "function" ? options.delayMs(context) : options.delayMs; const resolvedConfiguredDelay = configuredDelay === void 0 ? void 0 : resolveRetryDelayMs(configuredDelay); const baseDelay = hasRetryAfter ? Math.max(retryAfterMs, minDelayMs) : resolvedConfiguredDelay === void 0 ? minDelayMs * 2 ** (attempt - 1) : Math.max(resolvedConfiguredDelay, minDelayMs); const delayCap = hasRetryAfter ? retryAfterMaxDelayMs : maxDelayMs; let delay = Math.min(baseDelay, delayCap); const canHonorRetryAfter = hasRetryAfter && (retryAfterMs ?? 0) <= delayCap; const wantsPositiveDraw = resolved.jitter === "full" ? !hasRetryAfter || canHonorRetryAfter : canHonorRetryAfter; delay = applyJitter(delay, resolved.jitter, wantsPositiveDraw ? "positive" : "symmetric", random); delay = Math.min(Math.max(delay, minDelayMs), delayCap); await options.onRetry?.({ ...context, delayMs: delay }); if (delay > 0) await sleep(delay); } throw createFailure(attemptErrors); }; } createRetryRunner(); //#endregion //#region src/utils.ts /** Resolves the OpenClaw config directory from state/config env overrides or home. */ function resolveConfigDir(env = process.env, homedir = os.homedir) { const override = env.OPENCLAW_STATE_DIR?.trim(); if (override) return resolveUserPath(override, env, homedir); const configPath = env.OPENCLAW_CONFIG_PATH?.trim(); if (configPath) return path.dirname(resolveUserPath(configPath, env, homedir)); return path.join(resolveRequiredHomeDir(env, homedir), ".openclaw"); } resolveConfigDir(); //#endregion export { resolveHomeRelativePath as a, sameFileIdentity as c, expandHomePrefix as i, escapeRegExp as l, toRetryError as n, resolveRequiredHomeDir as o, isPathInside as r, tryProcessCwd as s, createRetryRunner as t };