diff --git a/src/extension-host/runtime-backend-catalog.test.ts b/src/extension-host/runtime-backend-catalog.test.ts index c6e6438e17e..c30671e6ed6 100644 --- a/src/extension-host/runtime-backend-catalog.test.ts +++ b/src/extension-host/runtime-backend-catalog.test.ts @@ -36,8 +36,8 @@ vi.mock("./media-runtime-registry.js", () => ({ ), })); -vi.mock("./tts-runtime-registry.js", () => ({ - listExtensionHostTtsRuntimeProviders: vi.fn(() => [ +vi.mock("./tts-runtime-backends.js", () => ({ + listExtensionHostTtsRuntimeBackends: vi.fn(() => [ { id: "openai", supportsTelephony: true }, { id: "elevenlabs", supportsTelephony: true }, { id: "edge", supportsTelephony: false }, diff --git a/src/extension-host/runtime-backend-catalog.ts b/src/extension-host/runtime-backend-catalog.ts index 46e6ccd16df..6de91699e38 100644 --- a/src/extension-host/runtime-backend-catalog.ts +++ b/src/extension-host/runtime-backend-catalog.ts @@ -13,7 +13,7 @@ import { buildExtensionHostMediaUnderstandingRegistry, normalizeExtensionHostMediaProviderId, } from "./media-runtime-registry.js"; -import { listExtensionHostTtsRuntimeProviders } from "./tts-runtime-registry.js"; +import { listExtensionHostTtsRuntimeBackends } from "./tts-runtime-backends.js"; export const EXTENSION_HOST_RUNTIME_BACKEND_FAMILY = "capability.runtime-backend"; @@ -203,7 +203,7 @@ export function resolveExtensionHostMediaRuntimeDefaultModel(params: { } export function listExtensionHostTtsRuntimeBackendCatalogEntries(): readonly ExtensionHostRuntimeBackendCatalogEntry[] { - return listExtensionHostTtsRuntimeProviders().map((provider, defaultRank) => ({ + return listExtensionHostTtsRuntimeBackends().map((provider, defaultRank) => ({ id: buildRuntimeBackendCatalogId("tts", provider.id), family: EXTENSION_HOST_RUNTIME_BACKEND_FAMILY, subsystemId: "tts", diff --git a/src/extension-host/tts-runtime-backends.test.ts b/src/extension-host/tts-runtime-backends.test.ts new file mode 100644 index 00000000000..5f5aea6be60 --- /dev/null +++ b/src/extension-host/tts-runtime-backends.test.ts @@ -0,0 +1,37 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { + EXTENSION_HOST_TTS_RUNTIME_BACKEND_IDS, + getExtensionHostTtsRuntimeBackend, + listExtensionHostTtsRuntimeBackends, +} from "./tts-runtime-backends.js"; + +describe("tts-runtime-backends", () => { + afterEach(() => { + vi.unstubAllEnvs(); + }); + + it("keeps the built-in backend order stable", () => { + expect(EXTENSION_HOST_TTS_RUNTIME_BACKEND_IDS).toEqual(["openai", "elevenlabs", "edge"]); + expect(listExtensionHostTtsRuntimeBackends().map((backend) => backend.id)).toEqual([ + "openai", + "elevenlabs", + "edge", + ]); + }); + + it("resolves API keys and configuration through shared backend definitions", () => { + vi.stubEnv("OPENAI_API_KEY", ""); + vi.stubEnv("ELEVENLABS_API_KEY", ""); + vi.stubEnv("XI_API_KEY", ""); + + const config = { + openai: { apiKey: "openai-key" }, + elevenlabs: { apiKey: "" }, + edge: { enabled: true }, + } as const; + + expect(getExtensionHostTtsRuntimeBackend("openai")?.resolveApiKey(config)).toBe("openai-key"); + expect(getExtensionHostTtsRuntimeBackend("elevenlabs")?.isConfigured(config)).toBe(false); + expect(getExtensionHostTtsRuntimeBackend("edge")?.supportsTelephony).toBe(false); + }); +}); diff --git a/src/extension-host/tts-runtime-backends.ts b/src/extension-host/tts-runtime-backends.ts new file mode 100644 index 00000000000..eeaad8133ec --- /dev/null +++ b/src/extension-host/tts-runtime-backends.ts @@ -0,0 +1,56 @@ +import type { TtsProvider } from "../config/types.tts.js"; +import type { ResolvedTtsConfig } from "./tts-config.js"; + +export type ExtensionHostTtsRuntimeBackend = { + id: TtsProvider; + supportsTelephony: boolean; + resolveApiKey: (config: ResolvedTtsConfig) => string | undefined; + isConfigured: (config: ResolvedTtsConfig) => boolean; +}; + +const EXTENSION_HOST_TTS_RUNTIME_BACKENDS: readonly ExtensionHostTtsRuntimeBackend[] = [ + { + id: "openai", + supportsTelephony: true, + resolveApiKey(config) { + return config.openai.apiKey || process.env.OPENAI_API_KEY; + }, + isConfigured(config) { + return Boolean(this.resolveApiKey(config)); + }, + }, + { + id: "elevenlabs", + supportsTelephony: true, + resolveApiKey(config) { + return config.elevenlabs.apiKey || process.env.ELEVENLABS_API_KEY || process.env.XI_API_KEY; + }, + isConfigured(config) { + return Boolean(this.resolveApiKey(config)); + }, + }, + { + id: "edge", + supportsTelephony: false, + resolveApiKey() { + return undefined; + }, + isConfigured(config) { + return config.edge.enabled; + }, + }, +] as const; + +export const EXTENSION_HOST_TTS_RUNTIME_BACKEND_IDS = EXTENSION_HOST_TTS_RUNTIME_BACKENDS.map( + (backend) => backend.id, +) as readonly TtsProvider[]; + +export function listExtensionHostTtsRuntimeBackends(): readonly ExtensionHostTtsRuntimeBackend[] { + return EXTENSION_HOST_TTS_RUNTIME_BACKENDS; +} + +export function getExtensionHostTtsRuntimeBackend( + id: TtsProvider, +): ExtensionHostTtsRuntimeBackend | undefined { + return EXTENSION_HOST_TTS_RUNTIME_BACKENDS.find((backend) => backend.id === id); +} diff --git a/src/extension-host/tts-runtime-registry.ts b/src/extension-host/tts-runtime-registry.ts index 331b97cebbe..7af49fcc99e 100644 --- a/src/extension-host/tts-runtime-registry.ts +++ b/src/extension-host/tts-runtime-registry.ts @@ -1,58 +1,24 @@ import type { TtsProvider } from "../config/types.tts.js"; import type { ResolvedTtsConfig } from "./tts-config.js"; +import { + EXTENSION_HOST_TTS_RUNTIME_BACKEND_IDS, + getExtensionHostTtsRuntimeBackend, + listExtensionHostTtsRuntimeBackends, + type ExtensionHostTtsRuntimeBackend, +} from "./tts-runtime-backends.js"; -export type ExtensionHostTtsRuntimeProvider = { - id: TtsProvider; - supportsTelephony: boolean; - resolveApiKey: (config: ResolvedTtsConfig) => string | undefined; - isConfigured: (config: ResolvedTtsConfig) => boolean; -}; +export type ExtensionHostTtsRuntimeProvider = ExtensionHostTtsRuntimeBackend; -const EXTENSION_HOST_TTS_RUNTIME_PROVIDERS: readonly ExtensionHostTtsRuntimeProvider[] = [ - { - id: "openai", - supportsTelephony: true, - resolveApiKey(config) { - return config.openai.apiKey || process.env.OPENAI_API_KEY; - }, - isConfigured(config) { - return Boolean(this.resolveApiKey(config)); - }, - }, - { - id: "elevenlabs", - supportsTelephony: true, - resolveApiKey(config) { - return config.elevenlabs.apiKey || process.env.ELEVENLABS_API_KEY || process.env.XI_API_KEY; - }, - isConfigured(config) { - return Boolean(this.resolveApiKey(config)); - }, - }, - { - id: "edge", - supportsTelephony: false, - resolveApiKey() { - return undefined; - }, - isConfigured(config) { - return config.edge.enabled; - }, - }, -] as const; - -export const EXTENSION_HOST_TTS_PROVIDER_IDS = EXTENSION_HOST_TTS_RUNTIME_PROVIDERS.map( - (provider) => provider.id, -) as readonly TtsProvider[]; +export const EXTENSION_HOST_TTS_PROVIDER_IDS = EXTENSION_HOST_TTS_RUNTIME_BACKEND_IDS; export function listExtensionHostTtsRuntimeProviders(): readonly ExtensionHostTtsRuntimeProvider[] { - return EXTENSION_HOST_TTS_RUNTIME_PROVIDERS; + return listExtensionHostTtsRuntimeBackends(); } export function getExtensionHostTtsRuntimeProvider( id: TtsProvider, ): ExtensionHostTtsRuntimeProvider | undefined { - return EXTENSION_HOST_TTS_RUNTIME_PROVIDERS.find((provider) => provider.id === id); + return getExtensionHostTtsRuntimeBackend(id); } export function resolveExtensionHostTtsApiKey(