UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

458 lines (457 loc) 21 kB
import { k as resolveOptionalIntegerOption } from "./number-coercion-CJQ8TR--.js"; import { i as formatErrorMessage } from "./errors-BXgSefBE.js"; import { o as coerceSecretRef, u as normalizeResolvedSecretInputString } from "./types.secrets-_0JOMGE5.js"; import { n as normalizeAccountId, r as normalizeOptionalAccountId } from "./account-id-Df9e41E6.js"; import { n as retryAsync } from "./retry-BSIArBBz.js"; import "./error-runtime-C8vbtAJt.js"; import "./number-runtime-DBLVDypr.js"; import { t as requireRuntimeConfig } from "./plugin-config-runtime-CpE6DYgJ.js"; import { o as isPrivateNetworkOptInEnabled, u as ssrfPolicyFromDangerouslyAllowPrivateNetwork } from "./ssrf-policy-sZLyyYht.js"; import "./ssrf-runtime-BOGN5pUi.js"; import "./retry-runtime-DdapUPJ5.js"; import "./secret-input-runtime-BgOxdITP.js"; import { a as resolveMatrixDefaultOrOnlyAccountId, n as requiresExplicitMatrixDefaultAccount, o as resolveMatrixAccountStringValues } from "./account-selection-CqvkLWBU.js"; import { t as getMatrixScopedEnvVarNames } from "./env-vars-Gd1yKtzb.js"; import { o as resolveMatrixBaseConfig, r as listNormalizedMatrixAccountIds, t as findMatrixAccountConfig } from "./account-config-C2f9UtTn.js"; import { i as resolveScopedMatrixEnvConfig, n as resolveGlobalMatrixEnvConfig } from "./env-auth-B_5QIHM9.js"; import { t as resolveMatrixConfigFieldPath } from "./config-paths-Cz5wYCcV.js"; import { t as resolveValidatedMatrixHomeserverUrl } from "./url-validation-Ck23jxmT.js"; import { i as repairCurrentTokenStorageMetaDeviceId } from "./storage-6UUh27xf.js"; //#region extensions/matrix/src/matrix/client/config.ts let matrixAuthClientDepsPromise; let matrixCredentialsReadDepsPromise; let matrixCredentialsWriteRuntimePromise; let matrixSecretInputDepsPromise; const MATRIX_AUTH_REQUEST_RETRY_RE = /\b(fetch failed|econnreset|econnrefused|enotfound|etimedout|ehostunreach|enetunreach|eai_again|und_err_|socket hang up|network|headers timeout|body timeout|connect timeout)\b/i; async function loadMatrixAuthClientDeps() { matrixAuthClientDepsPromise ??= Promise.all([import("./sdk-ChGrWr0y.js"), import("./logging-2XoG6Avv.js")]).then(([sdkModule, loggingModule]) => ({ MatrixClient: sdkModule.MatrixClient, ensureMatrixSdkLoggingConfigured: loggingModule.ensureMatrixSdkLoggingConfigured })); return await matrixAuthClientDepsPromise; } async function loadMatrixCredentialsReadDeps() { matrixCredentialsReadDepsPromise ??= import("./credentials-read-Dp1hgv5V.js").then((credentialsReadModule) => ({ loadMatrixCredentials: credentialsReadModule.loadMatrixCredentials, credentialsMatchConfig: credentialsReadModule.credentialsMatchConfig })); return await matrixCredentialsReadDepsPromise; } async function loadMatrixCredentialsWriteRuntime() { matrixCredentialsWriteRuntimePromise ??= import("./credentials-write.runtime.js"); return await matrixCredentialsWriteRuntimePromise; } async function loadMatrixSecretInputDeps() { matrixSecretInputDepsPromise ??= import("./config-secret-input.runtime.js").then((runtime) => ({ resolveConfiguredSecretInputString: runtime.resolveConfiguredSecretInputString })); return await matrixSecretInputDepsPromise; } function shouldRetryMatrixAuthRequest(err) { return MATRIX_AUTH_REQUEST_RETRY_RE.test(formatErrorMessage(err)); } function isAbortSignalTriggered(signal) { return signal?.aborted === true; } function credentialsMatchBackfillAuthLineage(params) { if (!params.stored) return true; return params.stored.homeserver === params.auth.homeserver && params.stored.userId === params.auth.userId && params.stored.accessToken === params.auth.accessToken; } async function retryMatrixAuthRequest(label, run) { return await retryAsync(run, { attempts: 3, minDelayMs: 250, maxDelayMs: 1500, jitter: .1, label, shouldRetry: (err) => shouldRetryMatrixAuthRequest(err) }); } async function fetchMatrixWhoamiIdentity(params) { const { MatrixClient, ensureMatrixSdkLoggingConfigured } = await loadMatrixAuthClientDeps(); ensureMatrixSdkLoggingConfigured(); const tempClient = new MatrixClient(params.homeserver, params.accessToken, { userId: params.userId, ssrfPolicy: params.ssrfPolicy, dispatcherPolicy: params.dispatcherPolicy }); return await retryMatrixAuthRequest("matrix auth whoami", async () => { return await tempClient.doRequest("GET", "/_matrix/client/v3/account/whoami"); }); } function readEnvSecretRefFallback(params) { const ref = coerceSecretRef(params.value, params.config?.secrets?.defaults); if (!ref || ref.source !== "env" || !params.env) return; const providerConfig = params.config?.secrets?.providers?.[ref.provider]; if (providerConfig) { if (providerConfig.source !== "env") throw new Error(`Secret provider "${ref.provider}" has source "${providerConfig.source}" but ref requests "env".`); if (providerConfig.allowlist && !providerConfig.allowlist.includes(ref.id)) throw new Error(`Environment variable "${ref.id}" is not allowlisted in secrets.providers.${ref.provider}.allowlist.`); } else if (ref.provider !== (params.config?.secrets?.defaults?.env?.trim() || "default")) throw new Error(`Secret provider "${ref.provider}" is not configured (ref: ${ref.source}:${ref.provider}:${ref.id}).`); const resolved = params.env[ref.id]; if (typeof resolved !== "string") return; const trimmed = resolved.trim(); return trimmed.length > 0 ? trimmed : void 0; } function clean(value, path, opts) { const ref = coerceSecretRef(value, opts?.config?.secrets?.defaults); if (opts?.suppressSecretRef && ref) return ""; return normalizeResolvedSecretInputString({ value: opts?.allowEnvSecretRefFallback ? ref?.source === "env" ? readEnvSecretRefFallback({ value, env: opts.env, config: opts.config }) ?? value : ref ? "" : value : value, path, defaults: opts?.config?.secrets?.defaults }) ?? ""; } function resolveMatrixBaseConfigFieldPath(field) { return `channels.matrix.${field}`; } function shouldAllowEnvSecretRefFallback(field) { return field === "accessToken" || field === "password"; } function hasConfiguredSecretInputValue(value, cfg) { return typeof value === "string" && value.trim().length > 0 || Boolean(coerceSecretRef(value, cfg.secrets?.defaults)); } function hasConfiguredMatrixAccessTokenSource(params) { const normalizedAccountId = normalizeAccountId(params.accountId); const account = findMatrixAccountConfig(params.cfg, normalizedAccountId) ?? {}; const scopedAccessTokenVar = getMatrixScopedEnvVarNames(normalizedAccountId).accessToken; if (hasConfiguredSecretInputValue(account.accessToken, params.cfg) || clean(params.env[scopedAccessTokenVar], scopedAccessTokenVar).length > 0) return true; if (normalizedAccountId !== "default") return false; return hasConfiguredSecretInputValue(resolveMatrixBaseConfig(params.cfg).accessToken, params.cfg) || clean(params.env.MATRIX_ACCESS_TOKEN, "MATRIX_ACCESS_TOKEN").length > 0; } function resolveConfiguredMatrixAuthInput(params) { const normalizedAccountId = normalizeAccountId(params.accountId); const accountValue = (findMatrixAccountConfig(params.cfg, normalizedAccountId) ?? {})[params.field]; if (accountValue !== void 0) return { value: accountValue, path: resolveMatrixConfigFieldPath(params.cfg, normalizedAccountId, params.field) }; const scopedKeys = getMatrixScopedEnvVarNames(normalizedAccountId); const scopedValue = resolveScopedMatrixEnvConfig(normalizedAccountId, params.env)[params.field]; if (scopedValue !== void 0) return { value: scopedValue, path: params.field === "accessToken" ? scopedKeys.accessToken : scopedKeys.password }; if (normalizedAccountId !== "default") return; const baseValue = resolveMatrixBaseConfig(params.cfg)[params.field]; if (baseValue !== void 0) return { value: baseValue, path: resolveMatrixBaseConfigFieldPath(params.field) }; const globalValue = params.field === "accessToken" ? params.env.MATRIX_ACCESS_TOKEN : params.env.MATRIX_PASSWORD; if (globalValue !== void 0) return { value: globalValue, path: params.field === "accessToken" ? "MATRIX_ACCESS_TOKEN" : "MATRIX_PASSWORD" }; } async function resolveConfiguredMatrixAuthSecretInput(params) { const configured = resolveConfiguredMatrixAuthInput(params); if (!configured) return; if (!coerceSecretRef(configured.value, params.cfg.secrets?.defaults)) return normalizeResolvedSecretInputString({ value: configured.value, path: configured.path, defaults: params.cfg.secrets?.defaults }); const { resolveConfiguredSecretInputString } = await loadMatrixSecretInputDeps(); const resolved = await resolveConfiguredSecretInputString({ config: params.cfg, env: params.env, value: configured.value, path: configured.path, unresolvedReasonStyle: "detailed" }); if (resolved.value !== void 0) return resolved.value; throw new Error(resolved.unresolvedRefReason ?? `${configured.path} SecretRef could not be resolved.`); } function readMatrixBaseConfigField(matrix, field, opts) { return clean(matrix[field], resolveMatrixBaseConfigFieldPath(field), { env: opts?.env, config: opts?.config, allowEnvSecretRefFallback: shouldAllowEnvSecretRefFallback(field), suppressSecretRef: opts?.suppressSecretRef }); } function readMatrixAccountConfigField(cfg, accountId, account, field, opts) { return clean(account[field], resolveMatrixConfigFieldPath(cfg, accountId, field), { env: opts?.env, config: opts?.config, allowEnvSecretRefFallback: shouldAllowEnvSecretRefFallback(field), suppressSecretRef: opts?.suppressSecretRef }); } function clampMatrixInitialSyncLimit(value) { return resolveOptionalIntegerOption(value, { min: 0 }); } function buildMatrixNetworkFields(params) { const dispatcherPolicy = params.dispatcherPolicy ?? (params.proxy ? { mode: "explicit-proxy", proxyUrl: params.proxy } : void 0); if (!params.allowPrivateNetwork && !dispatcherPolicy) return {}; return { ...params.allowPrivateNetwork ? { allowPrivateNetwork: true, ssrfPolicy: ssrfPolicyFromDangerouslyAllowPrivateNetwork(true) } : {}, ...dispatcherPolicy ? { dispatcherPolicy } : {} }; } function hasScopedMatrixEnvConfig(accountId, env) { const scoped = resolveScopedMatrixEnvConfig(accountId, env); return Boolean(scoped.homeserver || scoped.userId || scoped.accessToken || scoped.password || scoped.deviceId || scoped.deviceName); } function resolveMatrixConfigForAccount(cfg, accountId, env = process.env) { const matrix = resolveMatrixBaseConfig(cfg); const account = findMatrixAccountConfig(cfg, accountId) ?? {}; const normalizedAccountId = normalizeAccountId(accountId); const suppressInactivePasswordSecretRef = hasConfiguredMatrixAccessTokenSource({ cfg, env, accountId: normalizedAccountId }); const fieldReadOptions = { env, config: cfg }; const scopedEnv = resolveScopedMatrixEnvConfig(normalizedAccountId, env); const globalEnv = resolveGlobalMatrixEnvConfig(env); const accountField = (field) => readMatrixAccountConfigField(cfg, normalizedAccountId, account, field, { ...fieldReadOptions, suppressSecretRef: field === "password" ? suppressInactivePasswordSecretRef : void 0 }); const resolvedStrings = resolveMatrixAccountStringValues({ accountId: normalizedAccountId, account: { homeserver: accountField("homeserver"), userId: accountField("userId"), accessToken: accountField("accessToken"), password: accountField("password"), deviceId: accountField("deviceId"), deviceName: accountField("deviceName") }, scopedEnv, channel: { homeserver: readMatrixBaseConfigField(matrix, "homeserver", fieldReadOptions), userId: readMatrixBaseConfigField(matrix, "userId", fieldReadOptions), accessToken: readMatrixBaseConfigField(matrix, "accessToken", fieldReadOptions), password: readMatrixBaseConfigField(matrix, "password", { ...fieldReadOptions, suppressSecretRef: suppressInactivePasswordSecretRef }), deviceId: readMatrixBaseConfigField(matrix, "deviceId", fieldReadOptions), deviceName: readMatrixBaseConfigField(matrix, "deviceName", fieldReadOptions) }, globalEnv }); const initialSyncLimit = clampMatrixInitialSyncLimit(account.initialSyncLimit) ?? clampMatrixInitialSyncLimit(matrix.initialSyncLimit); const encryption = typeof account.encryption === "boolean" ? account.encryption : matrix.encryption ?? false; const allowPrivateNetwork = isPrivateNetworkOptInEnabled(account) || isPrivateNetworkOptInEnabled(matrix) ? true : void 0; return { homeserver: resolvedStrings.homeserver, userId: resolvedStrings.userId, accessToken: resolvedStrings.accessToken || void 0, password: resolvedStrings.password || void 0, deviceId: resolvedStrings.deviceId || void 0, deviceName: resolvedStrings.deviceName || void 0, initialSyncLimit, encryption, ...buildMatrixNetworkFields({ allowPrivateNetwork, proxy: account.proxy ?? matrix.proxy }) }; } function resolveImplicitMatrixAccountId(cfg, env = process.env) { if (requiresExplicitMatrixDefaultAccount(cfg, env)) return null; return normalizeAccountId(resolveMatrixDefaultOrOnlyAccountId(cfg, env)); } function resolveMatrixAuthContext(params) { const cfg = requireRuntimeConfig(params.cfg, "Matrix auth context"); const env = params?.env ?? process.env; const explicitAccountId = normalizeOptionalAccountId(params?.accountId); const effectiveAccountId = explicitAccountId ?? resolveImplicitMatrixAccountId(cfg, env); if (!effectiveAccountId) throw new Error("Multiple Matrix accounts are configured and channels.matrix.defaultAccount is not set. Set \"channels.matrix.defaultAccount\" to the intended account or pass --account <id>."); if (explicitAccountId && explicitAccountId !== "default" && !listNormalizedMatrixAccountIds(cfg).includes(explicitAccountId) && !hasScopedMatrixEnvConfig(explicitAccountId, env)) throw new Error(`Matrix account "${explicitAccountId}" is not configured. Add channels.matrix.accounts.${explicitAccountId} or define scoped ${getMatrixScopedEnvVarNames(explicitAccountId).accessToken.replace(/_ACCESS_TOKEN$/, "")}_* variables.`); return { cfg, env, accountId: effectiveAccountId, resolved: resolveMatrixConfigForAccount(cfg, effectiveAccountId, env) }; } async function resolveMatrixAuth(params) { if (!params?.cfg) throw new Error("Matrix auth requires a resolved runtime config. Load and resolve config at the command or gateway boundary, then pass cfg through the runtime path."); const { cfg, env, accountId, resolved } = resolveMatrixAuthContext({ cfg: params.cfg, env: params.env, accountId: params.accountId }); const accessToken = await resolveConfiguredMatrixAuthSecretInput({ cfg, env, accountId, field: "accessToken" }) ?? resolved.accessToken; const tokenAuthPassword = resolved.password; const homeserver = await resolveValidatedMatrixHomeserverUrl(resolved.homeserver, { dangerouslyAllowPrivateNetwork: resolved.allowPrivateNetwork }); const { loadMatrixCredentials, credentialsMatchConfig } = await loadMatrixCredentialsReadDeps(); const cached = loadMatrixCredentials(env, accountId); const cachedCredentials = cached && credentialsMatchConfig(cached, { homeserver, userId: resolved.userId || "", accessToken }) ? cached : null; if (accessToken) { let userId = resolved.userId; const hasMatchingCachedToken = cachedCredentials?.accessToken === accessToken; let knownDeviceId = hasMatchingCachedToken ? cachedCredentials?.deviceId || resolved.deviceId : resolved.deviceId; if (!userId) { const whoami = await fetchMatrixWhoamiIdentity({ homeserver, accessToken, userId, ssrfPolicy: resolved.ssrfPolicy, dispatcherPolicy: resolved.dispatcherPolicy }); const fetchedUserId = whoami.user_id?.trim(); if (!fetchedUserId) throw new Error("Matrix whoami did not return user_id"); userId = fetchedUserId; knownDeviceId = knownDeviceId || whoami.device_id?.trim() || resolved.deviceId; } if (!cachedCredentials || !hasMatchingCachedToken || cachedCredentials.userId !== userId || (cachedCredentials.deviceId || void 0) !== knownDeviceId) { const { saveMatrixCredentials } = await loadMatrixCredentialsWriteRuntime(); await saveMatrixCredentials({ homeserver, userId, accessToken, deviceId: knownDeviceId }, env, accountId); } else if (hasMatchingCachedToken) { const { touchMatrixCredentials } = await loadMatrixCredentialsWriteRuntime(); await touchMatrixCredentials(env, accountId); } return { accountId, homeserver, userId, accessToken, password: tokenAuthPassword, deviceId: knownDeviceId, deviceName: resolved.deviceName, initialSyncLimit: resolved.initialSyncLimit, encryption: resolved.encryption, ...buildMatrixNetworkFields({ allowPrivateNetwork: resolved.allowPrivateNetwork, dispatcherPolicy: resolved.dispatcherPolicy }) }; } if (cachedCredentials) { const { touchMatrixCredentials } = await loadMatrixCredentialsWriteRuntime(); await touchMatrixCredentials(env, accountId); return { accountId, homeserver: cachedCredentials.homeserver, userId: cachedCredentials.userId, accessToken: cachedCredentials.accessToken, password: tokenAuthPassword, deviceId: cachedCredentials.deviceId || resolved.deviceId, deviceName: resolved.deviceName, initialSyncLimit: resolved.initialSyncLimit, encryption: resolved.encryption, ...buildMatrixNetworkFields({ allowPrivateNetwork: resolved.allowPrivateNetwork, dispatcherPolicy: resolved.dispatcherPolicy }) }; } if (!resolved.userId) throw new Error("Matrix userId is required when no access token is configured (matrix.userId)"); const password = await resolveConfiguredMatrixAuthSecretInput({ cfg, env, accountId, field: "password" }) ?? resolved.password; if (!password) throw new Error("Matrix password is required when no access token is configured (matrix.password)"); const { MatrixClient, ensureMatrixSdkLoggingConfigured } = await loadMatrixAuthClientDeps(); ensureMatrixSdkLoggingConfigured(); const loginClient = new MatrixClient(homeserver, "", { ssrfPolicy: resolved.ssrfPolicy, dispatcherPolicy: resolved.dispatcherPolicy }); const login = await retryMatrixAuthRequest("matrix auth login", async () => { return await loginClient.doRequest("POST", "/_matrix/client/v3/login", void 0, { type: "m.login.password", identifier: { type: "m.id.user", user: resolved.userId }, password, device_id: resolved.deviceId, initial_device_display_name: resolved.deviceName ?? "OpenClaw Gateway" }); }); const loginAccessToken = login.access_token?.trim(); if (!loginAccessToken) throw new Error("Matrix login did not return an access token"); const auth = { accountId, homeserver, userId: login.user_id ?? resolved.userId, accessToken: loginAccessToken, password, deviceId: login.device_id ?? resolved.deviceId, deviceName: resolved.deviceName, initialSyncLimit: resolved.initialSyncLimit, encryption: resolved.encryption, ...buildMatrixNetworkFields({ allowPrivateNetwork: resolved.allowPrivateNetwork, dispatcherPolicy: resolved.dispatcherPolicy }) }; const { saveMatrixCredentials } = await loadMatrixCredentialsWriteRuntime(); await saveMatrixCredentials({ homeserver: auth.homeserver, userId: auth.userId, accessToken: auth.accessToken, deviceId: auth.deviceId }, env, accountId); return auth; } async function backfillMatrixAuthDeviceIdAfterStartup(params) { const knownDeviceId = params.auth.deviceId?.trim(); if (knownDeviceId) return knownDeviceId; if (isAbortSignalTriggered(params.abortSignal)) return; const deviceId = (await fetchMatrixWhoamiIdentity({ homeserver: params.auth.homeserver, accessToken: params.auth.accessToken, userId: params.auth.userId, ssrfPolicy: params.auth.ssrfPolicy, dispatcherPolicy: params.auth.dispatcherPolicy })).device_id?.trim(); if (!deviceId) return; if (isAbortSignalTriggered(params.abortSignal)) return; const env = params.env ?? process.env; const { loadMatrixCredentials } = await loadMatrixCredentialsReadDeps(); if (!credentialsMatchBackfillAuthLineage({ stored: loadMatrixCredentials(env, params.auth.accountId), auth: params.auth })) return; if (!repairCurrentTokenStorageMetaDeviceId({ homeserver: params.auth.homeserver, userId: params.auth.userId, accessToken: params.auth.accessToken, accountId: params.auth.accountId, deviceId, env: params.env })) throw new Error("Matrix deviceId backfill failed to repair current-token storage metadata"); if (isAbortSignalTriggered(params.abortSignal)) return; return await (await loadMatrixCredentialsWriteRuntime()).saveBackfilledMatrixDeviceId({ homeserver: params.auth.homeserver, userId: params.auth.userId, accessToken: params.auth.accessToken, deviceId }, env, params.auth.accountId) === "saved" ? deviceId : void 0; } //#endregion export { resolveMatrixConfigForAccount as i, resolveMatrixAuth as n, resolveMatrixAuthContext as r, backfillMatrixAuthDeviceIdAfterStartup as t };