diff --git a/src/plugins/provider-registry-shared.test.ts b/src/plugins/provider-registry-shared.test.ts new file mode 100644 index 00000000000..95c84c472ad --- /dev/null +++ b/src/plugins/provider-registry-shared.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, it } from "vitest"; +import { + buildCapabilityProviderMaps, + normalizeCapabilityProviderId, +} from "./provider-registry-shared.js"; + +describe("provider registry shared", () => { + it("normalizes provider ids case-insensitively", () => { + expect(normalizeCapabilityProviderId(" OpenAI ")).toBe("openai"); + expect(normalizeCapabilityProviderId(" ")).toBeUndefined(); + }); + + it("indexes providers by id and alias", () => { + const { canonical, aliases } = buildCapabilityProviderMaps([ + { id: "Microsoft", aliases: [" EDGE ", "ms"] }, + { id: "OpenAI" }, + ]); + + expect([...canonical.keys()]).toEqual(["microsoft", "openai"]); + expect(aliases.get("edge")?.id).toBe("Microsoft"); + expect(aliases.get("ms")?.id).toBe("Microsoft"); + expect(aliases.get("openai")?.id).toBe("OpenAI"); + }); +}); diff --git a/src/plugins/provider-registry-shared.ts b/src/plugins/provider-registry-shared.ts new file mode 100644 index 00000000000..e840ae9e658 --- /dev/null +++ b/src/plugins/provider-registry-shared.ts @@ -0,0 +1,34 @@ +export function normalizeCapabilityProviderId(providerId: string | undefined): string | undefined { + const trimmed = providerId?.trim().toLowerCase(); + return trimmed ? trimmed : undefined; +} + +export function buildCapabilityProviderMaps( + providers: readonly T[], + normalizeId: ( + providerId: string | undefined, + ) => string | undefined = normalizeCapabilityProviderId, +): { + canonical: Map; + aliases: Map; +} { + const canonical = new Map(); + const aliases = new Map(); + + for (const provider of providers) { + const id = normalizeId(provider.id); + if (!id) { + continue; + } + canonical.set(id, provider); + aliases.set(id, provider); + for (const alias of provider.aliases ?? []) { + const normalizedAlias = normalizeId(alias); + if (normalizedAlias) { + aliases.set(normalizedAlias, provider); + } + } + } + + return { canonical, aliases }; +} diff --git a/src/realtime-voice/provider-registry.ts b/src/realtime-voice/provider-registry.ts index b2de16385c1..c7146580b79 100644 --- a/src/realtime-voice/provider-registry.ts +++ b/src/realtime-voice/provider-registry.ts @@ -1,17 +1,16 @@ import type { OpenClawConfig } from "../config/config.js"; import { resolvePluginCapabilityProviders } from "../plugins/capability-provider-runtime.js"; +import { + buildCapabilityProviderMaps, + normalizeCapabilityProviderId, +} from "../plugins/provider-registry-shared.js"; import type { RealtimeVoiceProviderPlugin } from "../plugins/types.js"; import type { RealtimeVoiceProviderId } from "./provider-types.js"; -function trimToUndefined(value: string | undefined): string | undefined { - const trimmed = value?.trim().toLowerCase(); - return trimmed ? trimmed : undefined; -} - export function normalizeRealtimeVoiceProviderId( providerId: string | undefined, ): RealtimeVoiceProviderId | undefined { - return trimToUndefined(providerId); + return normalizeCapabilityProviderId(providerId); } function resolveRealtimeVoiceProviderEntries(cfg?: OpenClawConfig): RealtimeVoiceProviderPlugin[] { @@ -25,28 +24,7 @@ function buildProviderMaps(cfg?: OpenClawConfig): { canonical: Map; aliases: Map; } { - const canonical = new Map(); - const aliases = new Map(); - const register = (provider: RealtimeVoiceProviderPlugin) => { - const id = normalizeRealtimeVoiceProviderId(provider.id); - if (!id) { - return; - } - canonical.set(id, provider); - aliases.set(id, provider); - for (const alias of provider.aliases ?? []) { - const normalizedAlias = normalizeRealtimeVoiceProviderId(alias); - if (normalizedAlias) { - aliases.set(normalizedAlias, provider); - } - } - }; - - for (const provider of resolveRealtimeVoiceProviderEntries(cfg)) { - register(provider); - } - - return { canonical, aliases }; + return buildCapabilityProviderMaps(resolveRealtimeVoiceProviderEntries(cfg)); } export function listRealtimeVoiceProviders(cfg?: OpenClawConfig): RealtimeVoiceProviderPlugin[] { diff --git a/src/tts/provider-registry.ts b/src/tts/provider-registry.ts index 8d71f467d6a..1b9f3041499 100644 --- a/src/tts/provider-registry.ts +++ b/src/tts/provider-registry.ts @@ -1,17 +1,16 @@ import type { OpenClawConfig } from "../config/config.js"; import { resolvePluginCapabilityProviders } from "../plugins/capability-provider-runtime.js"; +import { + buildCapabilityProviderMaps, + normalizeCapabilityProviderId, +} from "../plugins/provider-registry-shared.js"; import type { SpeechProviderPlugin } from "../plugins/types.js"; import type { SpeechProviderId } from "./provider-types.js"; -function trimToUndefined(value: string | undefined): string | undefined { - const trimmed = value?.trim().toLowerCase(); - return trimmed ? trimmed : undefined; -} - export function normalizeSpeechProviderId( providerId: string | undefined, ): SpeechProviderId | undefined { - return trimToUndefined(providerId); + return normalizeCapabilityProviderId(providerId); } function resolveSpeechProviderPluginEntries(cfg?: OpenClawConfig): SpeechProviderPlugin[] { @@ -25,28 +24,7 @@ function buildProviderMaps(cfg?: OpenClawConfig): { canonical: Map; aliases: Map; } { - const canonical = new Map(); - const aliases = new Map(); - const register = (provider: SpeechProviderPlugin) => { - const id = normalizeSpeechProviderId(provider.id); - if (!id) { - return; - } - canonical.set(id, provider); - aliases.set(id, provider); - for (const alias of provider.aliases ?? []) { - const normalizedAlias = normalizeSpeechProviderId(alias); - if (normalizedAlias) { - aliases.set(normalizedAlias, provider); - } - } - }; - - for (const provider of resolveSpeechProviderPluginEntries(cfg)) { - register(provider); - } - - return { canonical, aliases }; + return buildCapabilityProviderMaps(resolveSpeechProviderPluginEntries(cfg)); } export function listSpeechProviders(cfg?: OpenClawConfig): SpeechProviderPlugin[] {