perf(utils): isolate message channel normalization

This commit is contained in:
Vincent Koc
2026-04-13 17:34:46 +01:00
parent be68309e7b
commit 96a6f55da8
6 changed files with 120 additions and 89 deletions

View File

@@ -3,8 +3,8 @@ import type { ChannelPlugin } from "../../channels/plugins/types.plugin.js";
import type { ChannelOutboundTargetMode } from "../../channels/plugins/types.public.js";
import { formatCliCommand } from "../../cli/command-format.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { INTERNAL_MESSAGE_CHANNEL } from "../../utils/message-channel-constants.js";
import type { GatewayMessageChannel } from "../../utils/message-channel.js";
import { INTERNAL_MESSAGE_CHANNEL } from "../../utils/message-channel.js";
import { missingTargetError } from "./target-errors.js";
export type OutboundTargetResolution = { ok: true; to: string } | { ok: false; error: Error };

View File

@@ -9,11 +9,11 @@ import { deliveryContextFromSession } from "../../utils/delivery-context.shared.
import type {
DeliverableMessageChannel,
GatewayMessageChannel,
} from "../../utils/message-channel.js";
} from "../../utils/message-channel-normalize.js";
import {
isDeliverableMessageChannel,
normalizeMessageChannel,
} from "../../utils/message-channel.js";
} from "../../utils/message-channel-normalize.js";
export type SessionDeliveryTarget = {
channel?: DeliverableMessageChannel;

View File

@@ -1,7 +1,7 @@
import { normalizeOptionalString } from "../shared/string-coerce.js";
import { normalizeAccountId } from "./account-id.js";
import type { DeliveryContext, DeliveryContextSessionSource } from "./delivery-context.types.js";
import { normalizeMessageChannel } from "./message-channel.js";
import { normalizeMessageChannel } from "./message-channel-normalize.js";
export type { DeliveryContext, DeliveryContextSessionSource } from "./delivery-context.types.js";
export function normalizeDeliveryContext(context?: DeliveryContext): DeliveryContext | undefined {

View File

@@ -0,0 +1,2 @@
export const INTERNAL_MESSAGE_CHANNEL = "webchat" as const;
export type InternalMessageChannel = typeof INTERNAL_MESSAGE_CHANNEL;

View File

@@ -0,0 +1,87 @@
import {
CHANNEL_IDS,
listChatChannelAliases,
listRegisteredChannelPluginAliases,
listRegisteredChannelPluginIds,
normalizeAnyChannelId,
normalizeChatChannelId,
} from "../channels/registry.js";
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
import {
INTERNAL_MESSAGE_CHANNEL,
type InternalMessageChannel,
} from "./message-channel-constants.js";
type ChannelId = string & { readonly __openclawChannelIdBrand?: never };
export type DeliverableMessageChannel = ChannelId;
export type GatewayMessageChannel = DeliverableMessageChannel;
export type GatewayAgentChannelHint = GatewayMessageChannel;
const listPluginChannelIds = (): string[] => {
return listRegisteredChannelPluginIds();
};
const listPluginChannelAliases = (): string[] => {
return listRegisteredChannelPluginAliases();
};
export function normalizeMessageChannel(raw?: string | null): string | undefined {
const normalized = normalizeOptionalLowercaseString(raw);
if (!normalized) {
return undefined;
}
if (normalized === INTERNAL_MESSAGE_CHANNEL) {
return INTERNAL_MESSAGE_CHANNEL;
}
const builtIn = normalizeChatChannelId(normalized);
if (builtIn) {
return builtIn;
}
return normalizeAnyChannelId(normalized) ?? normalized;
}
export const listDeliverableMessageChannels = (): ChannelId[] =>
Array.from(new Set([...CHANNEL_IDS, ...listPluginChannelIds()]));
export const listGatewayMessageChannels = (): GatewayMessageChannel[] => [
...listDeliverableMessageChannels(),
INTERNAL_MESSAGE_CHANNEL,
];
export const listGatewayAgentChannelAliases = (): string[] =>
Array.from(new Set([...listChatChannelAliases(), ...listPluginChannelAliases()]));
export const listGatewayAgentChannelValues = (): string[] =>
Array.from(
new Set([...listGatewayMessageChannels(), "last", ...listGatewayAgentChannelAliases()]),
);
export function isGatewayMessageChannel(value: string): value is GatewayMessageChannel {
return listGatewayMessageChannels().includes(value as GatewayMessageChannel);
}
export function isDeliverableMessageChannel(value: string): value is DeliverableMessageChannel {
return listDeliverableMessageChannels().includes(value as DeliverableMessageChannel);
}
export function resolveGatewayMessageChannel(
raw?: string | null,
): GatewayMessageChannel | undefined {
const normalized = normalizeMessageChannel(raw);
if (!normalized) {
return undefined;
}
return isGatewayMessageChannel(normalized) ? normalized : undefined;
}
export function resolveMessageChannel(
primary?: string | null,
fallback?: string | null,
): string | undefined {
return normalizeMessageChannel(primary) ?? normalizeMessageChannel(fallback);
}
export type { InternalMessageChannel };

View File

@@ -1,13 +1,5 @@
import { getChatChannelMeta } from "../channels/chat-meta.js";
import {
CHANNEL_IDS,
getRegisteredChannelPluginMeta,
listRegisteredChannelPluginAliases,
listRegisteredChannelPluginIds,
listChatChannelAliases,
normalizeChatChannelId,
normalizeAnyChannelId,
} from "../channels/registry.js";
import { getRegisteredChannelPluginMeta, normalizeChatChannelId } from "../channels/registry.js";
import {
GATEWAY_CLIENT_MODES,
GATEWAY_CLIENT_NAMES,
@@ -16,12 +8,32 @@ import {
normalizeGatewayClientMode,
normalizeGatewayClientName,
} from "../gateway/protocol/client-info.js";
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
type ChannelId = string & { readonly __openclawChannelIdBrand?: never };
export const INTERNAL_MESSAGE_CHANNEL = "webchat" as const;
export type InternalMessageChannel = typeof INTERNAL_MESSAGE_CHANNEL;
export {
isDeliverableMessageChannel,
isGatewayMessageChannel,
listDeliverableMessageChannels,
listGatewayAgentChannelAliases,
listGatewayAgentChannelValues,
listGatewayMessageChannels,
normalizeMessageChannel,
resolveGatewayMessageChannel,
resolveMessageChannel,
type DeliverableMessageChannel,
type GatewayAgentChannelHint,
type GatewayMessageChannel,
} from "./message-channel-normalize.js";
export {
INTERNAL_MESSAGE_CHANNEL,
type InternalMessageChannel,
} from "./message-channel-constants.js";
import {
INTERNAL_MESSAGE_CHANNEL,
type InternalMessageChannel,
} from "./message-channel-constants.js";
import {
normalizeMessageChannel,
type DeliverableMessageChannel,
} from "./message-channel-normalize.js";
export { GATEWAY_CLIENT_NAMES, GATEWAY_CLIENT_MODES };
export type { GatewayClientName, GatewayClientMode };
@@ -58,76 +70,6 @@ export function isWebchatClient(client?: GatewayClientInfoLike | null): boolean
return normalizeGatewayClientName(client?.id) === GATEWAY_CLIENT_NAMES.WEBCHAT_UI;
}
export function normalizeMessageChannel(raw?: string | null): string | undefined {
const normalized = normalizeOptionalLowercaseString(raw);
if (!normalized) {
return undefined;
}
if (normalized === INTERNAL_MESSAGE_CHANNEL) {
return INTERNAL_MESSAGE_CHANNEL;
}
const builtIn = normalizeChatChannelId(normalized);
if (builtIn) {
return builtIn;
}
return normalizeAnyChannelId(normalized) ?? normalized;
}
const listPluginChannelIds = (): string[] => {
return listRegisteredChannelPluginIds();
};
const listPluginChannelAliases = (): string[] => {
return listRegisteredChannelPluginAliases();
};
export const listDeliverableMessageChannels = (): ChannelId[] =>
Array.from(new Set([...CHANNEL_IDS, ...listPluginChannelIds()]));
export type DeliverableMessageChannel = ChannelId;
export type GatewayMessageChannel = DeliverableMessageChannel;
export const listGatewayMessageChannels = (): GatewayMessageChannel[] => [
...listDeliverableMessageChannels(),
INTERNAL_MESSAGE_CHANNEL,
];
export const listGatewayAgentChannelAliases = (): string[] =>
Array.from(new Set([...listChatChannelAliases(), ...listPluginChannelAliases()]));
export type GatewayAgentChannelHint = GatewayMessageChannel;
export const listGatewayAgentChannelValues = (): string[] =>
Array.from(
new Set([...listGatewayMessageChannels(), "last", ...listGatewayAgentChannelAliases()]),
);
export function isGatewayMessageChannel(value: string): value is GatewayMessageChannel {
return listGatewayMessageChannels().includes(value as GatewayMessageChannel);
}
export function isDeliverableMessageChannel(value: string): value is DeliverableMessageChannel {
return listDeliverableMessageChannels().includes(value as DeliverableMessageChannel);
}
export function resolveGatewayMessageChannel(
raw?: string | null,
): GatewayMessageChannel | undefined {
const normalized = normalizeMessageChannel(raw);
if (!normalized) {
return undefined;
}
return isGatewayMessageChannel(normalized) ? normalized : undefined;
}
export function resolveMessageChannel(
primary?: string | null,
fallback?: string | null,
): string | undefined {
return normalizeMessageChannel(primary) ?? normalizeMessageChannel(fallback);
}
export function isMarkdownCapableMessageChannel(raw?: string | null): boolean {
const channel = normalizeMessageChannel(raw);
if (!channel) {