UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

383 lines (382 loc) • 16.4 kB
import { A as asOptionalRecord, c as resolveChannelAccountKey, d as normalizeAccountId, f as normalizeOptionalAccountId, h as resolveGlobalSingleton, s as resolveAccountKey, u as DEFAULT_ACCOUNT_ID, w as normalizeUniqueStringEntries } from "./runtime-doctor-migrations-BhouWAUC.js"; import "./number-coercion-CWp8Ho-Y.js"; import "@openclaw/fs-safe/path"; import path from "node:path"; import "node:fs"; import "@openclaw/fs-safe/advanced"; import os from "node:os"; import { AsyncLocalStorage } from "node:async_hooks"; //#region src/config/channel-account-config.ts /** Merge authored account overrides over channel defaults, with owner-selected collection rules. */ function mergeAccountConfig(params) { const omitKeys = /* @__PURE__ */ new Set(["accounts", ...params.omitKeys ?? []]); const base = Object.fromEntries(Object.entries(params.channelConfig ?? {}).filter(([key]) => !omitKeys.has(key))); const account = params.accountConfig; const merged = { ...base, ...account }; const mergedFields = merged; for (const [key, kind] of Object.entries(params.inheritEmptyKeys ?? {})) { const value = account?.[key]; const collection = kind === "array" ? Array.isArray(value) ? value : void 0 : asOptionalRecord(value); if (collection && Object.keys(collection).length === 0) mergedFields[key] = base[key]; } for (const key of params.nestedObjectKeys ?? []) { const baseValue = asOptionalRecord(base[key]); const accountValue = asOptionalRecord(account?.[key]); if (baseValue && accountValue) mergedFields[key] = { ...baseValue, ...accountValue }; } if (params.preserveRootAllowFrom && Array.isArray(base.allowFrom) && Array.isArray(account?.allowFrom)) { if (base.allowFrom.some((entry) => { const value = String(entry).trim(); return value.length > 0 && value !== "*"; }) && account.allowFrom.some((entry) => String(entry).trim() === "*")) { const entries = account.allowFrom.filter((entry) => String(entry).trim() !== "*"); mergedFields.allowFrom = entries.length > 0 ? entries : base.allowFrom; } } return merged; } /** Resolve a named account before applying the shared channel inheritance rules. */ function resolveMergedAccountConfig(params) { const accountKey = params.channelId ? resolveChannelAccountKey(params.accounts, params.accountId, params.channelId, params.normalizeAccountId, params.accountKeyPolicy) : resolveAccountKey(params.accounts, params.accountId, params.normalizeAccountId, params.accountKeyPolicy); const accountConfig = accountKey === void 0 ? void 0 : params.accounts?.[accountKey]; return mergeAccountConfig({ ...params, accountConfig }); } Object.freeze([ "activity", "agents", "ai-agents", "appearance", "approve", "apps", "ask", "automation", "automations", "channels", "chat", "communications", "config", "cron", "custodian", "dashboard", "dashboards", "debug", "focus", "infrastructure", "lobsterdex", "logs", "mcp", "meetings", "memory-import", "model-providers", "model-setup", "new", "nodes", "plugin", "plugins", "portals", "profile", "sessions", "settings", "share", "skills", "systems", "terminal", "usage", "workboard", "worktrees" ]); //#endregion //#region src/channels/plugins/account-helpers.ts function createAccountListHelpers(channelKey, options) { function hasImplicitDefaultAccount(cfg) { const channel = cfg.channels?.[channelKey]; return Boolean(options?.hasImplicitDefaultAccount?.(cfg) || options?.implicitDefaultAccount?.channelKeys?.some((key) => hasConfiguredAccountValue(channel?.[key])) || options?.implicitDefaultAccount?.envVars?.some((key) => hasConfiguredAccountValue(process.env[key]))); } function resolveConfiguredDefaultAccountId(cfg) { const channel = cfg.channels?.[channelKey]; return normalizeOptionalAccountId(typeof channel?.defaultAccount === "string" ? channel.defaultAccount : void 0); } function listConfiguredAccountIds(cfg) { const accounts = (cfg.channels?.[channelKey])?.accounts; if (!accounts || typeof accounts !== "object") return []; const ids = Object.keys(accounts).filter(Boolean); const normalizeConfiguredAccountId = options?.normalizeAccountId; if (!normalizeConfiguredAccountId) return ids; return normalizeUniqueStringEntries(ids.map((id) => normalizeConfiguredAccountId(id))); } function listAccountIds(cfg) { return listCombinedAccountIds({ configuredAccountIds: listConfiguredAccountIds(cfg), additionalAccountIds: options?.additionalAccountIds?.(cfg), implicitAccountId: options?.resolveImplicitAccountId ? options.resolveImplicitAccountId(cfg) : hasImplicitDefaultAccount(cfg) ? DEFAULT_ACCOUNT_ID : void 0, fallbackAccountIdWhenEmpty: options?.fallbackAccountIdWhenEmpty === false ? void 0 : options?.fallbackAccountIdWhenEmpty ?? "default" }); } function resolveDefaultAccountId(cfg) { return resolveListedDefaultAccountId({ accountIds: listAccountIds(cfg), configuredDefaultAccountId: resolveConfiguredDefaultAccountId(cfg), allowUnlistedDefaultAccount: options?.allowUnlistedDefaultAccount }); } return { listConfiguredAccountIds, listAccountIds, resolveDefaultAccountId, resolveAccountConfig: (cfg, accountId) => { const channelConfig = cfg.channels?.[channelKey]; const accounts = channelConfig?.accounts; return resolveMergedAccountConfig({ channelConfig, channelId: channelKey, accounts, accountId, omitKeys: options?.omitKeys, normalizeAccountId: options?.normalizeAccountId, nestedObjectKeys: options?.nestedObjectKeys }); } }; } function hasConfiguredAccountValue(value) { if (typeof value === "string") return value.trim().length > 0; return value !== void 0 && value !== null; } function listCombinedAccountIds(params) { const ids = new Set([ ...params.configuredAccountIds, ...params.additionalAccountIds ?? [], ...params.implicitAccountId ? [params.implicitAccountId] : [] ].filter(Boolean)); if (ids.size === 0 && params.fallbackAccountIdWhenEmpty) return [params.fallbackAccountIdWhenEmpty]; return [...ids].toSorted((a, b) => a.localeCompare(b)); } function resolveListedDefaultAccountId(params) { const preferred = params.configuredDefaultAccountId; const normalizeListedAccountId = params.normalizeListedAccountId ?? normalizeAccountId; if (preferred && (params.allowUnlistedDefaultAccount || params.accountIds.some((accountId) => normalizeListedAccountId(accountId) === preferred))) return preferred; if (params.accountIds.includes("default")) return DEFAULT_ACCOUNT_ID; if (params.ambiguousFallbackAccountId && params.accountIds.length > 1) return params.ambiguousFallbackAccountId; return params.accountIds[0] ?? "default"; } //#endregion //#region packages/normalization-core/src/home-dir.ts function normalizeHomeDirValue(value) { const trimmed = value?.trim(); return trimmed && trimmed !== "undefined" && trimmed !== "null" ? trimmed : void 0; } function normalizeSafe(homedir) { try { return normalizeHomeDirValue(homedir()); } catch { return; } } function resolveTermuxHome(env) { const prefix = normalizeHomeDirValue(env.PREFIX); if (!prefix || !normalizeHomeDirValue(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 normalizeHomeDirValue(env.HOME) ?? normalizeHomeDirValue(env.USERPROFILE) ?? resolveTermuxHome(env) ?? normalizeSafe(homedir); } function resolveOsHomeDir(env = process.env, homedir = os.homedir) { const raw = resolveRawOsHomeDir(env, homedir); return raw ? path.resolve(raw) : void 0; } function resolveEffectiveHomeDir(env = process.env, homedir = os.homedir, options) { const explicitHome = normalizeHomeDirValue(env.OPENCLAW_HOME); if (!explicitHome) return resolveOsHomeDir(env, homedir); if (explicitHome === "~" || explicitHome.startsWith("~/") || explicitHome.startsWith("~\\")) { const osHome = resolveRawOsHomeDir(env, homedir); if (!osHome) return options?.preserveUnresolvedTilde ? path.resolve(explicitHome) : void 0; return path.resolve(explicitHome.replace(/^~(?=$|[\\/])/, () => osHome)); } return path.resolve(explicitHome); } //#endregion //#region src/infra/safe-cwd.ts function tryProcessCwd() { try { return process.cwd(); } catch { return; } } //#endregion //#region src/infra/home-dir.ts const userPathBaseDirectory = resolveGlobalSingleton(Symbol.for("openclaw.userPathBaseDirectory"), () => new AsyncLocalStorage()); /** 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 = normalizeHomeDirValue(opts?.home) ?? resolveEffectiveHomeDir(opts?.env ?? process.env, opts?.homedir ?? os.homedir); if (!home) return input; return input.replace(/^~(?=$|[\\/])/, () => home); } function resolvePathWithHome(input, opts, resolveHome) { const trimmed = input.trim(); if (!trimmed) return trimmed; const expanded = trimmed.startsWith("~") ? expandHomePrefix(trimmed, { home: resolveHome(opts?.env ?? process.env, opts?.homedir ?? os.homedir), env: opts?.env, homedir: opts?.homedir }) : trimmed; const base = userPathBaseDirectory.getStore(); return base ? path.resolve(base, expanded) : path.resolve(expanded); } /** Resolves a user-supplied path after trimming and expanding against the effective home. */ function resolveHomeRelativePath(input, opts) { return resolvePathWithHome(input, opts, resolveRequiredHomeDir); } /** 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 src/infra/config-dir.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"); } //#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 = async (ms) => { let remainingMs = ms; do { const delayMs = Math.min(remainingMs, MAX_TIMER_TIMEOUT_MS); await new Promise((resolve) => { setTimeout(resolve, delayMs); }); remainingMs -= delayMs; } while (remainingMs > 0); }; function clampNumber(value, fallback, min, max) { if (typeof value !== "number" || !Number.isFinite(value)) return fallback; return Math.min(Math.max(value, min ?? Number.NEGATIVE_INFINITY), max ?? Number.POSITIVE_INFINITY); } function resolveAttemptCount(value, fallback) { return Math.max(1, Math.round(clampNumber(value, fallback))); } function resolveRetryDelayMs(value) { return Math.min(Math.max(Math.round(value === Number.POSITIVE_INFINITY ? MAX_TIMER_TIMEOUT_MS : Number.isFinite(value) ? value : 0), 0), MAX_TIMER_TIMEOUT_MS); } function resolveJitterConfig(value, fallback) { if (value === "full") return "full"; return typeof value === "number" && Number.isFinite(value) ? Math.min(Math.max(value, 0), 1) : fallback; } 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 = []; const numeric = typeof attemptsOrOptions === "number"; const options = typeof attemptsOrOptions === "number" ? { attempts: attemptsOrOptions } : 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; if (numeric) { await runtimeSleep(resolveRetryDelayMs(initialDelayMs * 2 ** (attempt - 1))); continue; } 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(); String.raw`(?<=^|[\s"'\x60(\[{<=:;]|file://)`; String.raw`[\s"'\x60)\]}>]`; String.raw`[:;](?=[/\\~$]|[A-Za-z]:)`; resolveConfigDir(); //#endregion export { tryProcessCwd as a, mergeAccountConfig as c, resolveUserPath as i, resolveHomeRelativePath as n, createAccountListHelpers as o, resolveRequiredHomeDir as r, hasConfiguredAccountValue as s, expandHomePrefix as t };