refactor: move built-in channel normalization to ids

This commit is contained in:
Shakker
2026-04-02 01:22:43 +01:00
committed by Peter Steinberger
parent 57ee3d9673
commit 8bd3067e69
14 changed files with 101 additions and 59 deletions

View File

@@ -99,39 +99,10 @@ function buildChatChannelMetaById(): Record<ChatChannelId, ChatChannelMeta> {
const CHAT_CHANNEL_META = buildChatChannelMetaById();
export const CHAT_CHANNEL_ALIASES: Record<string, ChatChannelId> = Object.freeze(
Object.fromEntries(
Object.values(CHAT_CHANNEL_META)
.flatMap((meta) =>
(meta.aliases ?? []).map((alias) => [alias.trim().toLowerCase(), meta.id] as const),
)
.filter(([alias]) => alias.length > 0)
.toSorted(([left], [right]) => left.localeCompare(right)),
),
) as Record<string, ChatChannelId>;
function normalizeChannelKey(raw?: string | null): string | undefined {
const normalized = raw?.trim().toLowerCase();
return normalized || undefined;
}
export function listChatChannels(): ChatChannelMeta[] {
return CHAT_CHANNEL_ORDER.map((id) => CHAT_CHANNEL_META[id]);
}
export function listChatChannelAliases(): string[] {
return Object.keys(CHAT_CHANNEL_ALIASES);
}
export function getChatChannelMeta(id: ChatChannelId): ChatChannelMeta {
return CHAT_CHANNEL_META[id];
}
export function normalizeChatChannelId(raw?: string | null): ChatChannelId | null {
const normalized = normalizeChannelKey(raw);
if (!normalized) {
return null;
}
const resolved = CHAT_CHANNEL_ALIASES[normalized] ?? normalized;
return CHAT_CHANNEL_ORDER.includes(resolved) ? resolved : null;
}

57
src/channels/ids.test.ts Normal file
View File

@@ -0,0 +1,57 @@
import { describe, expect, it } from "vitest";
import { listBundledPluginMetadata } from "../plugins/bundled-plugin-metadata.js";
import {
CHAT_CHANNEL_ALIASES,
CHAT_CHANNEL_ORDER,
normalizeChatChannelId,
type ChatChannelId,
} from "./ids.js";
function collectBundledChatChannelAliases(): Record<string, ChatChannelId> {
const aliases = new Map<string, ChatChannelId>();
for (const entry of listBundledPluginMetadata({
includeChannelConfigs: true,
includeSyntheticChannelConfigs: false,
})) {
const channel =
entry.packageManifest && "channel" in entry.packageManifest
? entry.packageManifest.channel
: undefined;
const rawId = channel?.id?.trim();
if (!rawId || !CHAT_CHANNEL_ORDER.includes(rawId as ChatChannelId)) {
continue;
}
const channelId = rawId as ChatChannelId;
if (!channel) {
continue;
}
for (const alias of channel.aliases ?? []) {
const normalizedAlias = alias.trim().toLowerCase();
if (!normalizedAlias) {
continue;
}
aliases.set(normalizedAlias, channelId);
}
}
return Object.fromEntries(
[...aliases.entries()].toSorted(([left], [right]) => left.localeCompare(right)),
) as Record<string, ChatChannelId>;
}
describe("channel ids", () => {
it("normalizes built-in aliases + trims whitespace", () => {
expect(normalizeChatChannelId(" imsg ")).toBe("imessage");
expect(normalizeChatChannelId("gchat")).toBe("googlechat");
expect(normalizeChatChannelId("google-chat")).toBe("googlechat");
expect(normalizeChatChannelId("internet-relay-chat")).toBe("irc");
expect(normalizeChatChannelId("telegram")).toBe("telegram");
expect(normalizeChatChannelId("web")).toBeNull();
expect(normalizeChatChannelId("nope")).toBeNull();
});
it("matches bundled built-in channel alias metadata", () => {
expect(CHAT_CHANNEL_ALIASES).toEqual(collectBundledChatChannelAliases());
});
});

View File

@@ -16,3 +16,32 @@ export const CHAT_CHANNEL_ORDER = [
export type ChatChannelId = (typeof CHAT_CHANNEL_ORDER)[number];
export const CHANNEL_IDS = [...CHAT_CHANNEL_ORDER] as const;
const BUILT_IN_CHAT_CHANNEL_ALIAS_ENTRIES = [
["gchat", "googlechat"],
["google-chat", "googlechat"],
["imsg", "imessage"],
["internet-relay-chat", "irc"],
] as const satisfies ReadonlyArray<readonly [string, ChatChannelId]>;
export const CHAT_CHANNEL_ALIASES: Record<string, ChatChannelId> = Object.freeze(
Object.fromEntries(BUILT_IN_CHAT_CHANNEL_ALIAS_ENTRIES),
) as Record<string, ChatChannelId>;
function normalizeChannelKey(raw?: string | null): string | undefined {
const normalized = raw?.trim().toLowerCase();
return normalized || undefined;
}
export function listChatChannelAliases(): string[] {
return Object.keys(CHAT_CHANNEL_ALIASES);
}
export function normalizeChatChannelId(raw?: string | null): ChatChannelId | null {
const normalized = normalizeChannelKey(raw);
if (!normalized) {
return null;
}
const resolved = CHAT_CHANNEL_ALIASES[normalized] ?? normalized;
return CHAT_CHANNEL_ORDER.includes(resolved) ? resolved : null;
}

View File

@@ -1,21 +1,7 @@
import { describe, expect, it } from "vitest";
import {
formatChannelSelectionLine,
listChatChannels,
normalizeChatChannelId,
} from "./registry.js";
import { formatChannelSelectionLine, listChatChannels } from "./registry.js";
describe("channel registry helpers", () => {
it("normalizes aliases + trims whitespace", () => {
expect(normalizeChatChannelId(" imsg ")).toBe("imessage");
expect(normalizeChatChannelId("gchat")).toBe("googlechat");
expect(normalizeChatChannelId("google-chat")).toBe("googlechat");
expect(normalizeChatChannelId("internet-relay-chat")).toBe("irc");
expect(normalizeChatChannelId("telegram")).toBe("telegram");
expect(normalizeChatChannelId("web")).toBeNull();
expect(normalizeChatChannelId("nope")).toBeNull();
});
it("keeps Telegram first in the default order", () => {
const channels = listChatChannels();
expect(channels[0]?.id).toBe("telegram");

View File

@@ -1,13 +1,13 @@
import { getActivePluginRegistry } from "../plugins/runtime.js";
import { getChatChannelMeta, listChatChannels, type ChatChannelMeta } from "./chat-meta.js";
import {
CHANNEL_IDS,
CHAT_CHANNEL_ALIASES,
getChatChannelMeta,
CHAT_CHANNEL_ORDER,
listChatChannelAliases,
listChatChannels,
normalizeChatChannelId,
type ChatChannelMeta,
} from "./chat-meta.js";
import { CHANNEL_IDS, CHAT_CHANNEL_ORDER, type ChatChannelId } from "./ids.js";
type ChatChannelId,
} from "./ids.js";
import type { ChannelId, ChannelMeta } from "./plugins/types.js";
export { CHANNEL_IDS, CHAT_CHANNEL_ORDER } from "./ids.js";
export type { ChatChannelId } from "./ids.js";

View File

@@ -16,7 +16,7 @@ vi.mock("node:fs", async () => {
};
});
vi.mock("../channels/registry.js", () => ({
vi.mock("../channels/ids.js", () => ({
CHAT_CHANNEL_ORDER: ["telegram", "discord"],
}));

View File

@@ -1,7 +1,7 @@
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { CHAT_CHANNEL_ORDER } from "../channels/registry.js";
import { CHAT_CHANNEL_ORDER } from "../channels/ids.js";
function dedupe(values: string[]): string[] {
const seen = new Set<string>();

View File

@@ -1,4 +1,4 @@
import { normalizeChatChannelId } from "../../../channels/registry.js";
import { normalizeChatChannelId } from "../../../channels/ids.js";
import type { OpenClawConfig } from "../../../config/config.js";
import { readChannelAllowFromStore } from "../../../pairing/pairing-store.js";
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../../routing/session-key.js";

View File

@@ -1,4 +1,4 @@
import { normalizeChatChannelId } from "../../../channels/registry.js";
import { normalizeChatChannelId } from "../../../channels/ids.js";
import { listRouteBindings } from "../../../config/bindings.js";
import type { OpenClawConfig } from "../../../config/config.js";
import {

View File

@@ -1,7 +1,6 @@
import path from "node:path";
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
import { CHANNEL_IDS } from "../channels/ids.js";
import { normalizeChatChannelId } from "../channels/chat-meta.js";
import { CHANNEL_IDS, normalizeChatChannelId } from "../channels/ids.js";
import { withBundledPluginAllowlistCompat } from "../plugins/bundled-compat.js";
import { listBundledWebSearchPluginIds } from "../plugins/bundled-web-search-ids.js";
import {

View File

@@ -1,4 +1,4 @@
import { normalizeChatChannelId } from "../channels/registry.js";
import { normalizeChatChannelId } from "../channels/ids.js";
import type { OpenClawConfig } from "../config/config.js";
import { defaultSlotIdForKey, hasKind } from "./slots.js";
import type { PluginKind, PluginOrigin } from "./types.js";

View File

@@ -1,4 +1,4 @@
import { normalizeChatChannelId } from "../channels/registry.js";
import { normalizeChatChannelId } from "../channels/ids.js";
import type { OpenClawConfig } from "../config/config.js";
import { ensurePluginAllowlisted } from "../config/plugins-allowlist.js";
import { setPluginEnabledInConfig } from "./toggle-config.js";

View File

@@ -1,4 +1,4 @@
import { normalizeChatChannelId } from "../channels/registry.js";
import { normalizeChatChannelId } from "../channels/ids.js";
import type { OpenClawConfig } from "../config/config.js";
export function setPluginEnabledInConfig(

View File

@@ -1,5 +1,5 @@
import { resolveDefaultAgentId } from "../agents/agent-scope.js";
import { normalizeChatChannelId } from "../channels/registry.js";
import { normalizeChatChannelId } from "../channels/ids.js";
import { listRouteBindings } from "../config/bindings.js";
import type { OpenClawConfig } from "../config/config.js";
import type { AgentRouteBinding } from "../config/types.agents.js";