openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
331 lines (330 loc) • 11.9 kB
JavaScript
import { n as normalizeAccountId, t as DEFAULT_ACCOUNT_ID } from "./account-id-Df9e41E6.js";
import { At as boolean, Tn as object } from "./schemas-6cH6bZ7o.js";
import { r as isSetupSingleAccountPromotionKey, t as collectSingleAccountPromotionEntries } from "./setup-promotion-keys-Oj7oZvpC.js";
//#region src/channels/plugins/setup-helpers.ts
/**
* Channel setup config mutation helpers.
*
* Applies account names and validates setup results for channel onboarding adapters.
*/
const NAMED_ACCOUNT_PROMOTION_KEYS_BY_CHANNEL = {
matrix: [
"name",
"homeserver",
"userId",
"accessToken",
"password",
"deviceId",
"deviceName",
"avatarUrl",
"initialSyncLimit",
"encryption"
],
telegram: ["botToken", "tokenFile"]
};
function channelHasAccounts(cfg, channelKey) {
const base = cfg.channels?.[channelKey];
return Boolean(base?.accounts && Object.keys(base.accounts).length > 0);
}
function shouldStoreNameInAccounts(params) {
if (params.alwaysUseAccounts) return true;
if (params.accountId !== "default") return true;
return channelHasAccounts(params.cfg, params.channelKey);
}
function applyAccountNameToChannelSection(params) {
const trimmed = params.name?.trim();
if (!trimmed) return params.cfg;
const accountId = normalizeAccountId(params.accountId);
const baseConfig = params.cfg.channels?.[params.channelKey];
const base = typeof baseConfig === "object" && baseConfig ? baseConfig : void 0;
if (!shouldStoreNameInAccounts({
cfg: params.cfg,
channelKey: params.channelKey,
accountId,
alwaysUseAccounts: params.alwaysUseAccounts
}) && accountId === "default") {
const safeBase = base ?? {};
return {
...params.cfg,
channels: {
...params.cfg.channels,
[params.channelKey]: {
...safeBase,
name: trimmed
}
}
};
}
const baseAccounts = base?.accounts ?? {};
const existingAccount = baseAccounts[accountId] ?? {};
const baseWithoutName = accountId === "default" ? (({ name: _ignored, ...rest }) => rest)(base ?? {}) : base ?? {};
return {
...params.cfg,
channels: {
...params.cfg.channels,
[params.channelKey]: {
...baseWithoutName,
accounts: {
...baseAccounts,
[accountId]: {
...existingAccount,
name: trimmed
}
}
}
}
};
}
/** Moves a root-level channel name into `accounts.default` before adding named accounts. */
function migrateBaseNameToDefaultAccount(params) {
if (params.alwaysUseAccounts) return params.cfg;
const base = params.cfg.channels?.[params.channelKey];
const baseName = base?.name?.trim();
if (!baseName) return params.cfg;
const accounts = { ...base?.accounts };
const defaultAccount = accounts["default"] ?? {};
if (!defaultAccount.name) accounts[DEFAULT_ACCOUNT_ID] = {
...defaultAccount,
name: baseName
};
const { name: _ignored, ...rest } = base ?? {};
return {
...params.cfg,
channels: {
...params.cfg.channels,
[params.channelKey]: {
...rest,
accounts
}
}
};
}
/** Applies setup-time account naming and optional root-name migration in one step. */
function prepareScopedSetupConfig(params) {
const namedConfig = applyAccountNameToChannelSection({
cfg: params.cfg,
channelKey: params.channelKey,
accountId: params.accountId,
name: params.name,
alwaysUseAccounts: params.alwaysUseAccounts
});
if (!params.migrateBaseName || normalizeAccountId(params.accountId) === "default") return namedConfig;
return migrateBaseNameToDefaultAccount({
cfg: namedConfig,
channelKey: params.channelKey,
alwaysUseAccounts: params.alwaysUseAccounts
});
}
/** Applies a setup patch using account-scoped config semantics. */
function applySetupAccountConfigPatch(params) {
return patchScopedAccountConfig({
cfg: params.cfg,
channelKey: params.channelKey,
accountId: params.accountId,
patch: params.patch
});
}
/** Creates a setup adapter that turns validated setup input into an account config patch. */
function createPatchedAccountSetupAdapter(params) {
return {
resolveAccountId: ({ accountId }) => normalizeAccountId(accountId),
applyAccountName: ({ cfg, accountId, name }) => prepareScopedSetupConfig({
cfg,
channelKey: params.channelKey,
accountId,
name,
alwaysUseAccounts: params.alwaysUseAccounts
}),
validateInput: params.validateInput,
applyAccountConfig: ({ cfg, accountId, input }) => {
const next = prepareScopedSetupConfig({
cfg,
channelKey: params.channelKey,
accountId,
name: input.name,
alwaysUseAccounts: params.alwaysUseAccounts,
migrateBaseName: !params.alwaysUseAccounts
});
const patch = params.buildPatch(input);
return patchScopedAccountConfig({
cfg: next,
channelKey: params.channelKey,
accountId,
patch,
accountPatch: patch,
ensureChannelEnabled: params.ensureChannelEnabled ?? !params.alwaysUseAccounts,
ensureAccountEnabled: params.ensureAccountEnabled ?? true,
scopeDefaultToAccounts: params.alwaysUseAccounts
});
}
};
}
/** Creates a Zod-backed setup input validator with an optional typed semantic check. */
function createZodSetupInputValidator(params) {
return (inputParams) => {
const parsed = params.schema.safeParse(inputParams.input);
if (!parsed.success) return parsed.error.issues[0]?.message ?? "invalid input";
return params.validate?.({
...inputParams,
input: parsed.data
}) ?? null;
};
}
const GenericSetupInputSchema = object({ useEnv: boolean().optional() }).passthrough();
function hasPresentSetupValue(value) {
if (typeof value === "string") return value.trim().length > 0;
return value !== void 0 && value !== null;
}
function createSetupInputPresenceValidator(params) {
return createZodSetupInputValidator({
schema: GenericSetupInputSchema,
validate: (inputParams) => {
if (params.defaultAccountOnlyEnvError && inputParams.input.useEnv && inputParams.accountId !== "default") return params.defaultAccountOnlyEnvError;
if (!inputParams.input.useEnv) {
const inputRecord = inputParams.input;
for (const requirement of params.whenNotUseEnv ?? []) {
if (requirement.someOf.some((key) => hasPresentSetupValue(inputRecord[key]))) continue;
return requirement.message;
}
}
return params.validate?.(inputParams) ?? null;
}
});
}
/** Creates a setup adapter that supports env-backed default account auth and patched credentials. */
function createEnvPatchedAccountSetupAdapter(params) {
return createPatchedAccountSetupAdapter({
channelKey: params.channelKey,
alwaysUseAccounts: params.alwaysUseAccounts,
ensureChannelEnabled: params.ensureChannelEnabled,
ensureAccountEnabled: params.ensureAccountEnabled,
validateInput: (inputParams) => {
if (inputParams.input.useEnv && inputParams.accountId !== "default") return params.defaultAccountOnlyEnvError;
if (!inputParams.input.useEnv && !params.hasCredentials(inputParams.input)) return params.missingCredentialError;
return params.validateInput?.(inputParams) ?? null;
},
buildPatch: params.buildPatch
});
}
/** Patches channel config at root for default accounts or under `accounts.<id>` for named accounts. */
function patchScopedAccountConfig(params) {
const accountId = normalizeAccountId(params.accountId);
const channelConfig = params.cfg.channels?.[params.channelKey];
const base = typeof channelConfig === "object" && channelConfig ? channelConfig : void 0;
const ensureChannelEnabled = params.ensureChannelEnabled ?? true;
const ensureAccountEnabled = params.ensureAccountEnabled ?? ensureChannelEnabled;
const patch = params.patch;
const accountPatch = params.accountPatch ?? patch;
if (accountId === "default" && !params.scopeDefaultToAccounts) return {
...params.cfg,
channels: {
...params.cfg.channels,
[params.channelKey]: {
...base,
...ensureChannelEnabled ? { enabled: true } : {},
...patch
}
}
};
const accounts = base?.accounts ?? {};
const existingAccount = accounts[accountId] ?? {};
return {
...params.cfg,
channels: {
...params.cfg.channels,
[params.channelKey]: {
...base,
...ensureChannelEnabled ? { enabled: true } : {},
accounts: {
...accounts,
[accountId]: {
...existingAccount,
...ensureAccountEnabled ? { enabled: typeof existingAccount.enabled === "boolean" ? existingAccount.enabled : true } : {},
...accountPatch
}
}
}
}
};
}
function cloneIfObject(value) {
if (value && typeof value === "object") return structuredClone(value);
return value;
}
function moveSingleAccountKeysIntoAccount(params) {
const nextAccount = { ...params.baseAccount };
for (const key of params.keysToMove) nextAccount[key] = cloneIfObject(params.channel[key]);
const nextChannel = { ...params.channel };
for (const key of params.keysToMove) delete nextChannel[key];
return {
...params.cfg,
channels: {
...params.cfg.channels,
[params.channelKey]: {
...nextChannel,
accounts: {
...params.accounts,
[params.targetAccountId]: nextAccount
}
}
}
};
}
function resolveExistingAccountKey(accounts, targetAccountId) {
for (const existingKey of Object.keys(accounts)) if (normalizeAccountId(existingKey) === targetAccountId) return existingKey;
return targetAccountId;
}
function resolveSingleAccountKeysToMove(params) {
const { entries, hasNamedAccounts } = collectSingleAccountPromotionEntries(params.channel);
const keysToMove = entries.filter(isSetupSingleAccountPromotionKey);
if (!hasNamedAccounts || keysToMove.length === 0) return keysToMove;
const namedAccountPromotionKeys = NAMED_ACCOUNT_PROMOTION_KEYS_BY_CHANNEL[params.channelKey];
return namedAccountPromotionKeys ? keysToMove.filter((key) => namedAccountPromotionKeys.includes(key)) : keysToMove;
}
function resolveSingleAccountPromotionTarget(params) {
const accounts = params.channel.accounts ?? {};
const normalizedDefaultAccount = typeof params.channel.defaultAccount === "string" && params.channel.defaultAccount.trim() ? normalizeAccountId(params.channel.defaultAccount) : void 0;
if (normalizedDefaultAccount) return Object.keys(accounts).find((accountId) => normalizeAccountId(accountId) === normalizedDefaultAccount) ?? "default";
const namedAccounts = Object.keys(accounts).filter(Boolean);
return namedAccounts.length === 1 ? namedAccounts[0] : DEFAULT_ACCOUNT_ID;
}
/**
* Promotes legacy single-account channel fields into the account map for multi-account setup.
*/
function moveSingleAccountChannelSectionToDefaultAccount(params) {
const baseConfig = params.cfg.channels?.[params.channelKey];
const base = typeof baseConfig === "object" && baseConfig ? baseConfig : void 0;
if (!base) return params.cfg;
const accounts = base.accounts ?? {};
if (Object.keys(accounts).length > 0) {
const keysToMove = resolveSingleAccountKeysToMove({
channelKey: params.channelKey,
channel: base
});
if (keysToMove.length === 0) return params.cfg;
const resolvedTargetAccountKey = resolveExistingAccountKey(accounts, resolveSingleAccountPromotionTarget({ channel: base }));
return moveSingleAccountKeysIntoAccount({
cfg: params.cfg,
channelKey: params.channelKey,
channel: base,
accounts,
keysToMove,
targetAccountId: resolvedTargetAccountKey,
baseAccount: accounts[resolvedTargetAccountKey]
});
}
const keysToMove = resolveSingleAccountKeysToMove({
channelKey: params.channelKey,
channel: base
});
return moveSingleAccountKeysIntoAccount({
cfg: params.cfg,
channelKey: params.channelKey,
channel: base,
accounts,
keysToMove,
targetAccountId: DEFAULT_ACCOUNT_ID
});
}
//#endregion
export { createSetupInputPresenceValidator as a, moveSingleAccountChannelSectionToDefaultAccount as c, createPatchedAccountSetupAdapter as i, patchScopedAccountConfig as l, applySetupAccountConfigPatch as n, createZodSetupInputValidator as o, createEnvPatchedAccountSetupAdapter as r, migrateBaseNameToDefaultAccount as s, applyAccountNameToChannelSection as t, prepareScopedSetupConfig as u };