refactor: share channel doctor alias normalization

This commit is contained in:
Peter Steinberger
2026-04-20 23:21:08 +01:00
parent e2abd4bc62
commit 8b7418b127
5 changed files with 167 additions and 171 deletions

View File

@@ -3,11 +3,7 @@ import type {
ChannelDoctorLegacyConfigRule,
} from "openclaw/plugin-sdk/channel-contract";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import {
asObjectRecord,
normalizeLegacyDmAliases,
normalizeLegacyStreamingAliases,
} from "openclaw/plugin-sdk/runtime-doctor";
import { asObjectRecord, normalizeLegacyChannelAliases } from "openclaw/plugin-sdk/runtime-doctor";
import { resolveDiscordPreviewStreamMode } from "./preview-streaming.js";
const LEGACY_TTS_PROVIDER_KEYS = ["openai", "elevenlabs", "microsoft", "edge"] as const;
@@ -137,77 +133,40 @@ export function normalizeCompatibilityConfig({
let changed = false;
const shouldPromoteRootDmAllowFrom = !asObjectRecord(updated.accounts);
const dm = normalizeLegacyDmAliases({
entry: updated,
const aliases = normalizeLegacyChannelAliases({
entry: rawEntry,
pathPrefix: "channels.discord",
changes,
promoteAllowFrom: shouldPromoteRootDmAllowFrom,
});
updated = dm.entry;
changed = changed || dm.changed;
const streaming = normalizeLegacyStreamingAliases({
entry: updated,
pathPrefix: "channels.discord",
changes,
resolvedMode: resolveDiscordPreviewStreamMode(updated),
includePreviewChunk: true,
});
updated = streaming.entry;
changed = changed || streaming.changed;
const rawAccounts = asObjectRecord(updated.accounts);
if (rawAccounts) {
let accountsChanged = false;
const accounts = { ...rawAccounts };
for (const [accountId, rawAccount] of Object.entries(rawAccounts)) {
const account = asObjectRecord(rawAccount);
if (!account) {
continue;
}
let accountEntry = account;
let accountChanged = false;
const accountDm = normalizeLegacyDmAliases({
entry: accountEntry,
pathPrefix: `channels.discord.accounts.${accountId}`,
changes,
});
accountEntry = accountDm.entry;
accountChanged = accountDm.changed;
const accountStreaming = normalizeLegacyStreamingAliases({
entry: accountEntry,
pathPrefix: `channels.discord.accounts.${accountId}`,
changes,
resolvedMode: resolveDiscordPreviewStreamMode(accountEntry),
includePreviewChunk: true,
});
accountEntry = accountStreaming.entry;
accountChanged = accountChanged || accountStreaming.changed;
const accountVoice = asObjectRecord(accountEntry.voice);
normalizeDm: true,
rootDmPromoteAllowFrom: shouldPromoteRootDmAllowFrom,
normalizeAccountDm: true,
resolveStreamingOptions: (entry) => ({
resolvedMode: resolveDiscordPreviewStreamMode(entry),
includePreviewChunk: true,
}),
normalizeAccountExtra: ({ account, pathPrefix }) => {
const accountVoice = asObjectRecord(account.voice);
if (
accountVoice &&
migrateLegacyTtsConfig(
!accountVoice ||
!migrateLegacyTtsConfig(
asObjectRecord(accountVoice.tts),
`channels.discord.accounts.${accountId}.voice.tts`,
`${pathPrefix}.voice.tts`,
changes,
)
) {
accountEntry = {
...accountEntry,
return { entry: account, changed: false };
}
return {
entry: {
...account,
voice: accountVoice,
};
accountChanged = true;
}
if (accountChanged) {
accounts[accountId] = accountEntry;
accountsChanged = true;
}
}
if (accountsChanged) {
updated = { ...updated, accounts };
changed = true;
}
}
},
changed: true,
};
},
});
updated = aliases.entry;
changed = aliases.changed;
const voice = asObjectRecord(updated.voice);
if (

View File

@@ -4,19 +4,13 @@ import type {
} from "openclaw/plugin-sdk/channel-contract";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import {
asObjectRecord,
hasLegacyAccountStreamingAliases,
hasLegacyStreamingAliases,
normalizeLegacyDmAliases,
normalizeLegacyStreamingAliases,
normalizeLegacyChannelAliases,
} from "openclaw/plugin-sdk/runtime-doctor";
import { resolveSlackNativeStreaming, resolveSlackStreamingMode } from "./streaming-compat.js";
function asObjectRecord(value: unknown): Record<string, unknown> | null {
return value && typeof value === "object" && !Array.isArray(value)
? (value as Record<string, unknown>)
: null;
}
function hasLegacySlackStreamingAliases(value: unknown): boolean {
return hasLegacyStreamingAliases(value, { includeNativeTransport: true });
}
@@ -50,61 +44,19 @@ export function normalizeCompatibilityConfig({
let updated = rawEntry;
let changed = false;
const dm = normalizeLegacyDmAliases({
entry: updated,
const aliases = normalizeLegacyChannelAliases({
entry: rawEntry,
pathPrefix: "channels.slack",
changes,
normalizeDm: true,
normalizeAccountDm: true,
resolveStreamingOptions: (entry) => ({
resolvedMode: resolveSlackStreamingMode(entry),
resolvedNativeTransport: resolveSlackNativeStreaming(entry),
}),
});
updated = dm.entry;
changed = changed || dm.changed;
const streaming = normalizeLegacyStreamingAliases({
entry: updated,
pathPrefix: "channels.slack",
changes,
resolvedMode: resolveSlackStreamingMode(updated),
resolvedNativeTransport: resolveSlackNativeStreaming(updated),
});
updated = streaming.entry;
changed = changed || streaming.changed;
const rawAccounts = asObjectRecord(updated.accounts);
if (rawAccounts) {
let accountsChanged = false;
const accounts = { ...rawAccounts };
for (const [accountId, rawAccount] of Object.entries(rawAccounts)) {
const account = asObjectRecord(rawAccount);
if (!account) {
continue;
}
let accountEntry = account;
let accountChanged = false;
const accountDm = normalizeLegacyDmAliases({
entry: accountEntry,
pathPrefix: `channels.slack.accounts.${accountId}`,
changes,
});
accountEntry = accountDm.entry;
accountChanged = accountDm.changed;
const accountStreaming = normalizeLegacyStreamingAliases({
entry: accountEntry,
pathPrefix: `channels.slack.accounts.${accountId}`,
changes,
resolvedMode: resolveSlackStreamingMode(accountEntry),
resolvedNativeTransport: resolveSlackNativeStreaming(accountEntry),
});
accountEntry = accountStreaming.entry;
accountChanged = accountChanged || accountStreaming.changed;
if (accountChanged) {
accounts[accountId] = accountEntry;
accountsChanged = true;
}
}
if (accountsChanged) {
updated = { ...updated, accounts };
changed = true;
}
}
updated = aliases.entry;
changed = aliases.changed;
if (!changed) {
return { config: cfg, changes: [] };

View File

@@ -7,7 +7,7 @@ import {
asObjectRecord,
hasLegacyAccountStreamingAliases,
hasLegacyStreamingAliases,
normalizeLegacyStreamingAliases,
normalizeLegacyChannelAliases,
} from "openclaw/plugin-sdk/runtime-doctor";
import { resolveTelegramPreviewStreamMode } from "./preview-streaming.js";
@@ -93,42 +93,17 @@ export function normalizeCompatibilityConfig({
}
}
const streaming = normalizeLegacyStreamingAliases({
const aliases = normalizeLegacyChannelAliases({
entry: updated,
pathPrefix: "channels.telegram",
changes,
includePreviewChunk: true,
resolvedMode: resolveTelegramPreviewStreamMode(updated),
resolveStreamingOptions: (entry) => ({
includePreviewChunk: true,
resolvedMode: resolveTelegramPreviewStreamMode(entry),
}),
});
updated = streaming.entry;
changed = changed || streaming.changed;
const rawAccounts = asObjectRecord(updated.accounts);
if (rawAccounts) {
let accountsChanged = false;
const accounts = { ...rawAccounts };
for (const [accountId, rawAccount] of Object.entries(rawAccounts)) {
const account = asObjectRecord(rawAccount);
if (!account) {
continue;
}
const accountStreaming = normalizeLegacyStreamingAliases({
entry: account,
pathPrefix: `channels.telegram.accounts.${accountId}`,
changes,
includePreviewChunk: true,
resolvedMode: resolveTelegramPreviewStreamMode(account),
});
if (accountStreaming.changed) {
accounts[accountId] = accountStreaming.entry;
accountsChanged = true;
}
}
if (accountsChanged) {
updated = { ...updated, accounts };
changed = true;
}
}
updated = aliases.entry;
changed = changed || aliases.changed;
if (!changed && changes.length === 0) {
return { config: cfg, changes: [] };

View File

@@ -1,10 +1,24 @@
import { normalizeStringEntries } from "../shared/string-normalization.js";
type CompatMutationResult = {
export type CompatMutationResult = {
entry: Record<string, unknown>;
changed: boolean;
};
export type LegacyStreamingAliasOptions = {
resolvedMode: string;
includePreviewChunk?: boolean;
resolvedNativeTransport?: unknown;
offModeLegacyNotice?: (pathPrefix: string) => string;
};
export type NormalizeLegacyChannelAccountParams = {
account: Record<string, unknown>;
accountId: string;
pathPrefix: string;
changes: string[];
};
export function asObjectRecord(value: unknown): Record<string, unknown> | null {
return value && typeof value === "object" && !Array.isArray(value)
? (value as Record<string, unknown>)
@@ -120,15 +134,13 @@ export function normalizeLegacyDmAliases(params: {
return { entry: updated, changed };
}
export function normalizeLegacyStreamingAliases(params: {
entry: Record<string, unknown>;
pathPrefix: string;
changes: string[];
resolvedMode: string;
includePreviewChunk?: boolean;
resolvedNativeTransport?: unknown;
offModeLegacyNotice?: (pathPrefix: string) => string;
}): CompatMutationResult {
export function normalizeLegacyStreamingAliases(
params: {
entry: Record<string, unknown>;
pathPrefix: string;
changes: string[];
} & LegacyStreamingAliasOptions,
): CompatMutationResult {
const beforeStreaming = params.entry.streaming;
const hadLegacyStreamMode = params.entry.streamMode !== undefined;
const hasLegacyFlatFields =
@@ -254,6 +266,98 @@ export function normalizeLegacyStreamingAliases(params: {
return { entry: updated, changed };
}
export function normalizeLegacyChannelAliases(params: {
entry: Record<string, unknown>;
pathPrefix: string;
changes: string[];
normalizeDm?: boolean;
rootDmPromoteAllowFrom?: boolean;
normalizeAccountDm?: boolean;
resolveStreamingOptions: (entry: Record<string, unknown>) => LegacyStreamingAliasOptions;
normalizeAccountExtra?: (params: NormalizeLegacyChannelAccountParams) => CompatMutationResult;
}): CompatMutationResult {
let updated = params.entry;
let changed = false;
if (params.normalizeDm === true) {
const dm = normalizeLegacyDmAliases({
entry: updated,
pathPrefix: params.pathPrefix,
changes: params.changes,
promoteAllowFrom: params.rootDmPromoteAllowFrom,
});
updated = dm.entry;
changed = dm.changed;
}
const streaming = normalizeLegacyStreamingAliases({
entry: updated,
pathPrefix: params.pathPrefix,
changes: params.changes,
...params.resolveStreamingOptions(updated),
});
updated = streaming.entry;
changed = changed || streaming.changed;
const rawAccounts = asObjectRecord(updated.accounts);
if (!rawAccounts) {
return { entry: updated, changed };
}
let accountsChanged = false;
const accounts = { ...rawAccounts };
for (const [accountId, rawAccount] of Object.entries(rawAccounts)) {
const account = asObjectRecord(rawAccount);
if (!account) {
continue;
}
let accountEntry = account;
let accountChanged = false;
const accountPathPrefix = `${params.pathPrefix}.accounts.${accountId}`;
if (params.normalizeAccountDm === true) {
const accountDm = normalizeLegacyDmAliases({
entry: accountEntry,
pathPrefix: accountPathPrefix,
changes: params.changes,
});
accountEntry = accountDm.entry;
accountChanged = accountDm.changed;
}
const accountStreaming = normalizeLegacyStreamingAliases({
entry: accountEntry,
pathPrefix: accountPathPrefix,
changes: params.changes,
...params.resolveStreamingOptions(accountEntry),
});
accountEntry = accountStreaming.entry;
accountChanged = accountChanged || accountStreaming.changed;
const accountExtra = params.normalizeAccountExtra?.({
account: accountEntry,
accountId,
pathPrefix: accountPathPrefix,
changes: params.changes,
});
if (accountExtra) {
accountEntry = accountExtra.entry;
accountChanged = accountChanged || accountExtra.changed;
}
if (accountChanged) {
accounts[accountId] = accountEntry;
accountsChanged = true;
}
}
if (accountsChanged) {
updated = { ...updated, accounts };
changed = true;
}
return { entry: updated, changed };
}
export function hasLegacyStreamingAliases(
value: unknown,
options?: { includePreviewChunk?: boolean; includeNativeTransport?: boolean },

View File

@@ -3,9 +3,15 @@ export {
asObjectRecord,
hasLegacyAccountStreamingAliases,
hasLegacyStreamingAliases,
normalizeLegacyChannelAliases,
normalizeLegacyDmAliases,
normalizeLegacyStreamingAliases,
} from "../config/channel-compat-normalization.js";
export type {
CompatMutationResult,
LegacyStreamingAliasOptions,
NormalizeLegacyChannelAccountParams,
} from "../config/channel-compat-normalization.js";
export {
detectPluginInstallPathIssue,
formatPluginInstallPathIssue,