openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
292 lines (291 loc) • 10.1 kB
JavaScript
import { r as asNullableRecord } from "./record-coerce-TIsquoVR.mjs";
import { t as mergeDeep } from "./deep-merge-B-AVx-Uc.mjs";
import { i as resolveChannelAccountKey } from "./account-lookup-D_rRUHd5.mjs";
import { isDeepStrictEqual } from "node:util";
//#region src/config/channel-config-normalization.ts
/** Applies one channel-specific doctor migration to every object-shaped account. */
function normalizeChannelAccounts(params) {
const rawAccounts = asNullableRecord(params.entry.accounts);
if (!rawAccounts) return {
entry: params.entry,
changed: false
};
let changed = false;
const accounts = { ...rawAccounts };
for (const [accountId, value] of Object.entries(rawAccounts)) {
const account = asNullableRecord(value);
if (!account) continue;
const normalized = params.normalizeAccount({
account,
accountId,
pathPrefix: `${params.pathPrefix}.accounts.${accountId}`,
changes: params.changes
});
if (normalized.changed) {
accounts[accountId] = normalized.entry;
changed = true;
}
}
return changed ? {
entry: {
...params.entry,
accounts
},
changed: true
} : {
entry: params.entry,
changed: false
};
}
/** Applies the same channel-specific doctor migration at root and account scope. */
function normalizeChannelConfigEntries(params) {
const changes = params.changes ?? [];
const channels = params.cfg.channels;
const entry = asNullableRecord(channels?.[params.channelId]);
if (!entry) return {
config: params.cfg,
changes
};
const channelPath = `channels.${params.channelId}`;
const root = params.normalizeEntry({
entry,
pathPrefix: channelPath,
changes
});
const accounts = normalizeChannelAccounts({
entry: root.entry,
pathPrefix: channelPath,
changes,
normalizeAccount: (accountParams) => params.normalizeEntry({
entry: accountParams.account,
accountId: accountParams.accountId,
pathPrefix: accountParams.pathPrefix,
changes: accountParams.changes
})
});
if (!root.changed && !accounts.changed) return {
config: params.cfg,
changes
};
return {
config: {
...params.cfg,
channels: {
...channels,
[params.channelId]: accounts.entry
}
},
changes
};
}
//#endregion
//#region src/config/channel-doctor-helpers.ts
function stripRetiredKeys(params) {
if (params.recursive && Array.isArray(params.value)) {
let changed = false;
const value = params.value.map((item, index) => {
const stripped = stripRetiredKeys({
...params,
value: item,
pathPrefix: `${params.pathPrefix}[${index}]`
});
changed = changed || stripped.changed;
return stripped.value;
});
return {
value: changed ? value : params.value,
changed
};
}
const record = asNullableRecord(params.value);
if (!record) return {
value: params.value,
changed: false
};
let changed = false;
const value = {};
for (const [key, child] of Object.entries(record)) {
if (params.keys.has(key)) {
params.onRemove?.({
key,
pathPrefix: params.pathPrefix
});
changed = true;
continue;
}
if (!params.recursive) {
value[key] = child;
continue;
}
const stripped = stripRetiredKeys({
...params,
value: child,
pathPrefix: `${params.pathPrefix}.${key}`
});
changed = changed || stripped.changed;
value[key] = stripped.value;
}
return {
value: changed ? value : params.value,
changed
};
}
/** Removes retired keys recursively or from a channel root and its accounts. */
function stripRetiredChannelKeys(params) {
const channels = params.cfg.channels;
const entry = asNullableRecord(channels?.[params.channelId]);
if (!entry) return {
config: params.cfg,
changed: false
};
const channelPath = `channels.${params.channelId}`;
if (params.scope === "recursive") {
const stripped = stripRetiredKeys({
value: entry,
keys: params.keys,
pathPrefix: channelPath,
recursive: true,
onRemove: params.onRemove
});
return stripped.changed ? {
config: {
...params.cfg,
channels: {
...channels,
[params.channelId]: stripped.value
}
},
changed: true
} : {
config: params.cfg,
changed: false
};
}
const normalized = normalizeChannelConfigEntries({
cfg: params.cfg,
channelId: params.channelId,
normalizeEntry: (entryParams) => {
const stripped = stripRetiredKeys({
value: entryParams.entry,
keys: params.keys,
pathPrefix: entryParams.pathPrefix,
recursive: false,
onRemove: params.onRemove
});
return {
entry: stripped.value,
changed: stripped.changed
};
}
});
return {
config: normalized.config,
changed: normalized.config !== params.cfg
};
}
/** Materializes root/default-account inheritance after aliases create streaming. */
function materializeInheritedAccountStreaming(params) {
const channels = params.cfg.channels;
const entry = asNullableRecord(channels?.[params.channelId]);
const accounts = asNullableRecord(entry?.accounts);
if (!entry || !accounts) return params.cfg;
const rootStreaming = asNullableRecord(entry.streaming);
const defaultKey = resolveChannelAccountKey(accounts, "default", params.channelId, (key) => key.trim().toLowerCase());
let changed = false;
const nextAccounts = { ...accounts };
const accountIds = Object.keys(accounts).toSorted((left, right) => left === defaultKey ? -1 : right === defaultKey ? 1 : left.localeCompare(right));
for (const accountId of accountIds) {
if (asNullableRecord(params.accountsBefore?.[accountId])?.streaming !== void 0) continue;
const account = asNullableRecord(nextAccounts[accountId]);
const created = asNullableRecord(account?.streaming);
if (!account || !created) continue;
const defaultStreaming = defaultKey ? asNullableRecord(asNullableRecord(nextAccounts[defaultKey])?.streaming) : null;
const inherited = accountId === defaultKey ? rootStreaming : defaultStreaming ?? rootStreaming;
if (!inherited) continue;
const materialized = asNullableRecord(mergeDeep(inherited, created));
if (!materialized || isDeepStrictEqual(materialized, created)) continue;
nextAccounts[accountId] = {
...account,
streaming: materialized
};
changed = true;
const sourcePath = accountId !== defaultKey && defaultKey && defaultStreaming ? `channels.${params.channelId}.accounts.${defaultKey}.streaming` : `channels.${params.channelId}.streaming`;
params.changes.push(`Copied ${sourcePath} into channels.${params.channelId}.accounts.${accountId}.streaming to keep inherited settings while migrating flat streaming keys.`);
}
return changed ? {
...params.cfg,
channels: {
...channels,
[params.channelId]: {
...entry,
accounts: nextAccounts
}
}
} : params.cfg;
}
//#endregion
//#region src/config/legacy-private-network-migration.ts
/** Detects the retired flat `allowPrivateNetwork` key before doctor migration. */
function hasLegacyFlatAllowPrivateNetworkAlias(value) {
const entry = asNullableRecord(value);
return Boolean(entry && Object.hasOwn(entry, "allowPrivateNetwork"));
}
/** Moves flat private-network config into `network.dangerouslyAllowPrivateNetwork`. */
function migrateLegacyFlatAllowPrivateNetworkAlias(params) {
if (!hasLegacyFlatAllowPrivateNetworkAlias(params.entry)) return {
entry: params.entry,
changed: false
};
const legacyAllowPrivateNetwork = params.entry.allowPrivateNetwork;
const currentNetworkRecord = asNullableRecord(params.entry.network);
const currentNetwork = currentNetworkRecord ? { ...currentNetworkRecord } : {};
const currentDangerousAllowPrivateNetwork = currentNetwork.dangerouslyAllowPrivateNetwork;
const resolvedDangerousAllowPrivateNetwork = typeof currentDangerousAllowPrivateNetwork !== "boolean" && (typeof legacyAllowPrivateNetwork === "boolean" || currentDangerousAllowPrivateNetwork === void 0) ? legacyAllowPrivateNetwork : currentDangerousAllowPrivateNetwork;
delete currentNetwork.dangerouslyAllowPrivateNetwork;
if (resolvedDangerousAllowPrivateNetwork !== void 0) currentNetwork.dangerouslyAllowPrivateNetwork = resolvedDangerousAllowPrivateNetwork;
const nextEntry = { ...params.entry };
delete nextEntry.allowPrivateNetwork;
if (Object.keys(currentNetwork).length > 0) nextEntry.network = currentNetwork;
else delete nextEntry.network;
params.changes.push(`Moved ${params.pathPrefix}.allowPrivateNetwork → ${params.pathPrefix}.network.dangerouslyAllowPrivateNetwork (${String(resolvedDangerousAllowPrivateNetwork)}).`);
return {
entry: nextEntry,
changed: true
};
}
function hasLegacyAllowPrivateNetworkInAccounts(value) {
const accounts = asNullableRecord(value);
return Boolean(accounts && Object.values(accounts).some(hasLegacyFlatAllowPrivateNetworkAlias));
}
/** Build doctor rules that migrate legacy private-network aliases for one channel config. */
function createLegacyPrivateNetworkDoctorContract(params) {
const pathPrefix = `channels.${params.channelKey}`;
return {
legacyConfigRules: [{
path: ["channels", params.channelKey],
message: `${pathPrefix}.allowPrivateNetwork is legacy; use ${pathPrefix}.network.dangerouslyAllowPrivateNetwork instead. Run "openclaw doctor --fix".`,
match: hasLegacyFlatAllowPrivateNetworkAlias
}, {
path: [
"channels",
params.channelKey,
"accounts"
],
message: `${pathPrefix}.accounts.<id>.allowPrivateNetwork is legacy; use ${pathPrefix}.accounts.<id>.network.dangerouslyAllowPrivateNetwork instead. Run "openclaw doctor --fix".`,
match: hasLegacyAllowPrivateNetworkInAccounts
}],
normalizeCompatibilityConfig: ({ cfg }) => {
if (!asNullableRecord(cfg.channels)) return {
config: cfg,
changes: []
};
return normalizeChannelConfigEntries({
cfg,
channelId: params.channelKey,
normalizeEntry: migrateLegacyFlatAllowPrivateNetworkAlias
});
}
};
}
//#endregion
export { stripRetiredChannelKeys as a, materializeInheritedAccountStreaming as i, hasLegacyFlatAllowPrivateNetworkAlias as n, normalizeChannelAccounts as o, migrateLegacyFlatAllowPrivateNetworkAlias as r, normalizeChannelConfigEntries as s, createLegacyPrivateNetworkDoctorContract as t };