import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test"; import * as actualManagedSpeech from "../platform/managed-speech.js"; let mockManagedSpeechAvailable = false; mock.module("../platform/managed-speech.js", () => ({ ...actualManagedSpeech, managedSpeechAvailable: async () => mockManagedSpeechAvailable, })); // --------------------------------------------------------------------------- // Mocks — declared before imports that depend on platform/logger // --------------------------------------------------------------------------- const WORKSPACE_DIR = process.env.VELLUM_WORKSPACE_DIR!; const CONFIG_PATH = join(WORKSPACE_DIR, "config.json"); function ensureTestDir(): void { const dirs = [ WORKSPACE_DIR, join(WORKSPACE_DIR, "data"), join(WORKSPACE_DIR, "data", "memory"), join(WORKSPACE_DIR, "data", "memory", "knowledge"), join(WORKSPACE_DIR, "data", "logs"), ]; for (const dir of dirs) { if (!existsSync(dir)) { mkdirSync(dir, { recursive: true }); } } } import settingsSkillTools from "../config/bundled-skills/settings/TOOLS.json" with { type: "json" }; import { run, VALID_SETTINGS, } from "../config/bundled-skills/settings/tools/voice-config-update.js"; import { getConfig, invalidateConfigCache } from "../config/loader.js"; import type { ToolContext } from "../tools/types.js"; import { listCatalogProviderIds } from "../tts/provider-catalog.js"; // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- function writeConfig(obj: unknown): void { writeFileSync(CONFIG_PATH, JSON.stringify(obj)); } function readConfig(): Record { return JSON.parse(readFileSync(CONFIG_PATH, "utf8")); } function makeContext(overrides?: Partial): ToolContext { return { conversationId: "test-conv", turnId: "test-turn", sendToClient: overrides?.sendToClient ?? (() => {}), ...overrides, } as ToolContext; } // --------------------------------------------------------------------------- // Setup / Teardown // --------------------------------------------------------------------------- beforeEach(() => { ensureTestDir(); writeConfig({}); invalidateConfigCache(); mockManagedSpeechAvailable = false; }); afterEach(() => { try { writeConfig({}); invalidateConfigCache(); } catch { // best-effort cleanup } }); // --------------------------------------------------------------------------- // Tests: tts_provider persists to canonical path only // --------------------------------------------------------------------------- describe("voice_config_update — tts_provider", () => { test("persists tts_provider to canonical services.tts.provider", async () => { writeConfig({}); invalidateConfigCache(); const result = await run( { setting: "tts_provider", value: "fish-audio" }, makeContext(), ); expect(result.isError).toBe(false); const config = readConfig(); expect((config.services as any)?.tts?.provider).toBe("fish-audio"); }); test("does not write to legacy calls.voice.ttsProvider", async () => { writeConfig({}); invalidateConfigCache(); const result = await run( { setting: "tts_provider", value: "fish-audio" }, makeContext(), ); expect(result.isError).toBe(false); const config = readConfig(); expect((config.calls as any)?.voice?.ttsProvider).toBeUndefined(); }); test("rejects invalid tts_provider", async () => { const result = await run( { setting: "tts_provider", value: "invalid-provider" }, makeContext(), ); expect(result.isError).toBe(true); expect(result.content).toContain("tts_provider must be one of"); }); test("a provider switch scrubs a legacy mode key from the raw config", async () => { // The daemon ignores a legacy `mode` key, but the settings cards read // `mode: "managed"` as the Vellum marker — left behind, they would render // Vellum while the daemon routes the newly chosen provider. writeConfig({ services: { tts: { mode: "managed", provider: "vellum" } } }); invalidateConfigCache(); const result = await run( { setting: "tts_provider", value: "elevenlabs" }, makeContext(), ); expect(result.isError).toBe(false); const tts = (readConfig().services as any).tts; expect(tts.provider).toBe("elevenlabs"); expect(tts).not.toHaveProperty("mode"); }); test("tts_provider vellum persists when a platform connection is available", async () => { mockManagedSpeechAvailable = true; const result = await run( { setting: "tts_provider", value: "vellum" }, makeContext(), ); expect(result.isError).toBe(false); expect((readConfig().services as any).tts.provider).toBe("vellum"); }); test("broadcasts ttsProvider to client", async () => { const messages: any[] = []; const ctx = makeContext({ sendToClient: (msg: any) => messages.push(msg), }); await run({ setting: "tts_provider", value: "elevenlabs" }, ctx); expect(messages).toHaveLength(1); expect(messages[0].key).toBe("ttsProvider"); expect(messages[0].value).toBe("elevenlabs"); }); }); // --------------------------------------------------------------------------- // Tests: tts_voice_id persists to canonical path only // --------------------------------------------------------------------------- describe("voice_config_update — tts_voice_id", () => { test("persists to canonical services.tts.providers.elevenlabs.voiceId", async () => { writeConfig({}); invalidateConfigCache(); const result = await run( { setting: "tts_voice_id", value: "abc123" }, makeContext(), ); expect(result.isError).toBe(false); const config = readConfig(); expect((config.services as any)?.tts?.providers?.elevenlabs?.voiceId).toBe( "abc123", ); }); test("does not write to legacy elevenlabs.voiceId", async () => { writeConfig({}); invalidateConfigCache(); const result = await run( { setting: "tts_voice_id", value: "abc123" }, makeContext(), ); expect(result.isError).toBe(false); const config = readConfig(); expect((config.elevenlabs as any)?.voiceId).toBeUndefined(); }); test("rejects non-alphanumeric voice ID when provider is elevenlabs", async () => { const result = await run( { setting: "tts_voice_id", value: "abc-123!" }, makeContext(), ); expect(result.isError).toBe(true); expect(result.content).toContain("alphanumeric"); }); }); // --------------------------------------------------------------------------- // Tests: tts_voice_id targets the active provider's voice field // --------------------------------------------------------------------------- describe("voice_config_update — tts_voice_id (managed/vellum active)", () => { function makeVellumActive(): void { writeConfig({ services: { tts: { provider: "vellum" } } }); invalidateConfigCache(); } test("writes a Deepgram Aura model id to services.tts.providers.vellum.model", async () => { makeVellumActive(); const result = await run( { setting: "tts_voice_id", value: "aura-2-zeus-en" }, makeContext(), ); expect(result.isError).toBe(false); const config = readConfig(); expect((config.services as any)?.tts?.providers?.vellum?.model).toBe( "aura-2-zeus-en", ); // The ElevenLabs field is never touched when vellum is active. expect( (config.services as any)?.tts?.providers?.elevenlabs?.voiceId, ).toBeUndefined(); }); test("accepts an ElevenLabs voice id as a managed model (managed serves ElevenLabs voices)", async () => { makeVellumActive(); const result = await run( { setting: "tts_voice_id", value: "EXAVITQu4vr4xnSDxMaL" }, makeContext(), ); expect(result.isError).toBe(false); expect((readConfig().services as any)?.tts?.providers?.vellum?.model).toBe( "EXAVITQu4vr4xnSDxMaL", ); }); test("does not broadcast the ElevenLabs ttsVoiceId key for a managed voice", async () => { makeVellumActive(); const messages: any[] = []; const ctx = makeContext({ sendToClient: (msg: any) => messages.push(msg), }); const result = await run( { setting: "tts_voice_id", value: "aura-2-zeus-en" }, ctx, ); expect(result.isError).toBe(false); expect(messages).toHaveLength(0); expect(result.content).not.toContain("broadcast"); }); test("hints the inline picker for the next managed voice change", async () => { makeVellumActive(); const result = await run( { setting: "tts_voice_id", value: "aura-2-zeus-en" }, makeContext(), ); expect(result.isError).toBe(false); expect(result.content).toStartWith("Vellum managed voice updated to"); expect(result.content).toContain( 'ui_show { surface_type: "voice_picker", data: {} }', ); }); test("does not hint the picker when the voice is not managed", async () => { writeConfig({ services: { tts: { provider: "elevenlabs" } } }); invalidateConfigCache(); const result = await run( { setting: "tts_voice_id", value: "abc123" }, makeContext(), ); expect(result.isError).toBe(false); expect(result.content).not.toContain("voice_picker"); }); test("does not hint the picker for a non-voice setting", async () => { makeVellumActive(); const result = await run( { setting: "conversation_timeout", value: 30 }, makeContext(), ); expect(result.isError).toBe(false); expect(result.content).not.toContain("voice_picker"); }); test("rejects a value with characters invalid for any voice id", async () => { makeVellumActive(); const result = await run( { setting: "tts_voice_id", value: "a british man" }, makeContext(), ); expect(result.isError).toBe(true); expect((readConfig().services as any)?.tts?.providers?.vellum?.model).toBe( undefined, ); }); test("broadcasts the ElevenLabs ttsVoiceId key when elevenlabs is active", async () => { writeConfig({ services: { tts: { provider: "elevenlabs" } } }); invalidateConfigCache(); const messages: any[] = []; const ctx = makeContext({ sendToClient: (msg: any) => messages.push(msg), }); const result = await run({ setting: "tts_voice_id", value: "abc123" }, ctx); expect(result.isError).toBe(false); expect(messages).toHaveLength(1); expect(messages[0].key).toBe("ttsVoiceId"); expect(messages[0].value).toBe("abc123"); }); }); // --------------------------------------------------------------------------- // Tests: fish_audio_reference_id persists to canonical path only // --------------------------------------------------------------------------- describe("voice_config_update — fish_audio_reference_id", () => { test("persists to canonical services.tts.providers.fish-audio.referenceId", async () => { writeConfig({}); invalidateConfigCache(); const result = await run( { setting: "fish_audio_reference_id", value: "voice-ref-123" }, makeContext(), ); expect(result.isError).toBe(false); const config = readConfig(); expect( (config.services as any)?.tts?.providers?.["fish-audio"]?.referenceId, ).toBe("voice-ref-123"); }); test("does not write to legacy fishAudio.referenceId", async () => { writeConfig({}); invalidateConfigCache(); const result = await run( { setting: "fish_audio_reference_id", value: "voice-ref-123" }, makeContext(), ); expect(result.isError).toBe(false); const config = readConfig(); expect((config.fishAudio as any)?.referenceId).toBeUndefined(); }); test("rejects empty reference ID", async () => { const result = await run( { setting: "fish_audio_reference_id", value: "" }, makeContext(), ); expect(result.isError).toBe(true); expect(result.content).toContain("non-empty string"); }); }); // --------------------------------------------------------------------------- // Tests: conversation_timeout persists to canonical path only // --------------------------------------------------------------------------- describe("voice_config_update — conversation_timeout", () => { test("persists to canonical services.tts.providers.elevenlabs.conversationTimeoutSeconds", async () => { writeConfig({}); invalidateConfigCache(); const result = await run( { setting: "conversation_timeout", value: 15 }, makeContext(), ); expect(result.isError).toBe(false); const config = readConfig(); expect( (config.services as any)?.tts?.providers?.elevenlabs ?.conversationTimeoutSeconds, ).toBe(15); }); test("does not write to legacy elevenlabs.conversationTimeoutSeconds", async () => { writeConfig({}); invalidateConfigCache(); const result = await run( { setting: "conversation_timeout", value: 15 }, makeContext(), ); expect(result.isError).toBe(false); const config = readConfig(); expect( (config.elevenlabs as any)?.conversationTimeoutSeconds, ).toBeUndefined(); }); test("rejects invalid timeout", async () => { const result = await run( { setting: "conversation_timeout", value: 42 }, makeContext(), ); expect(result.isError).toBe(true); expect(result.content).toContain("conversation_timeout must be one of"); }); }); // --------------------------------------------------------------------------- // Tests: validation edge cases // --------------------------------------------------------------------------- describe("voice_config_update — manifest parity", () => { // The skill's TOOLS.json schema gates calls before the executor runs: a // setting missing from the enum is rejected by JSON-schema validation, and // an enum entry the executor doesn't know dies with "Unknown setting". The // two lists must stay identical. test("the TOOLS.json setting enum matches the executor's settings", () => { const manifest = settingsSkillTools.tools.find( (t) => t.name === "voice_config_update", ); const enumValues = manifest?.input_schema.properties.setting ?.enum as readonly string[]; expect([...enumValues].sort()).toEqual([...VALID_SETTINGS].sort()); }); }); describe("voice_config_update — validation", () => { test("missing setting returns error", async () => { const result = await run({ value: "test" }, makeContext()); expect(result.isError).toBe(true); expect(result.content).toContain("setting"); }); test("missing value returns error", async () => { const result = await run({ setting: "tts_provider" }, makeContext()); expect(result.isError).toBe(true); expect(result.content).toContain("value"); }); test("unknown setting returns error", async () => { const result = await run( { setting: "unknown_setting", value: "test" }, makeContext(), ); expect(result.isError).toBe(true); expect(result.content).toContain("Unknown setting"); }); }); // --------------------------------------------------------------------------- // Tests: deepgram tts_provider integration // --------------------------------------------------------------------------- describe("voice_config_update — deepgram", () => { test("accepts deepgram as tts_provider", async () => { writeConfig({}); invalidateConfigCache(); const result = await run( { setting: "tts_provider", value: "deepgram" }, makeContext(), ); expect(result.isError).toBe(false); expect(result.content).toContain("updated to"); const config = readConfig(); expect((config.services as any)?.tts?.provider).toBe("deepgram"); }); test("broadcasts deepgram ttsProvider to client", async () => { const messages: any[] = []; const ctx = makeContext({ sendToClient: (msg: any) => messages.push(msg), }); await run({ setting: "tts_provider", value: "deepgram" }, ctx); expect(messages).toHaveLength(1); expect(messages[0].type).toBe("client_settings_update"); expect(messages[0].key).toBe("ttsProvider"); expect(messages[0].value).toBe("deepgram"); }); }); // --------------------------------------------------------------------------- // Tests: stt_mode / tts_mode // --------------------------------------------------------------------------- describe("voice_config_update — stt_provider", () => { test("persists a BYOK provider to services.stt.provider", async () => { const result = await run( { setting: "stt_provider", value: "openai-whisper" }, makeContext(), ); expect(result.isError).toBe(false); expect((readConfig().services as any)?.stt?.provider).toBe( "openai-whisper", ); }); test("persists the vellum provider when a platform connection is available", async () => { mockManagedSpeechAvailable = true; const result = await run( { setting: "stt_provider", value: "vellum" }, makeContext(), ); expect(result.isError).toBe(false); expect((readConfig().services as any)?.stt?.provider).toBe("vellum"); }); test("rejects the vellum provider without a platform connection", async () => { mockManagedSpeechAvailable = false; const result = await run( { setting: "stt_provider", value: "vellum" }, makeContext(), ); expect(result.isError).toBe(true); expect(result.content).toContain("assistant platform connect"); expect((readConfig().services as any)?.stt?.provider).toBeUndefined(); }); test("switching away from vellum takes effect", async () => { writeConfig({ services: { stt: { provider: "vellum" } } }); invalidateConfigCache(); const result = await run( { setting: "stt_provider", value: "deepgram" }, makeContext(), ); expect(result.isError).toBe(false); expect((readConfig().services as any)?.stt?.provider).toBe("deepgram"); }); test("a provider switch scrubs a legacy mode key from the raw config", async () => { writeConfig({ services: { stt: { mode: "managed", provider: "vellum" } } }); invalidateConfigCache(); const result = await run( { setting: "stt_provider", value: "deepgram" }, makeContext(), ); expect(result.isError).toBe(false); const stt = (readConfig().services as any).stt; expect(stt.provider).toBe("deepgram"); expect(stt).not.toHaveProperty("mode"); }); test("rejects an unknown provider", async () => { const result = await run( { setting: "stt_provider", value: "bogus" }, makeContext(), ); expect(result.isError).toBe(true); expect(result.content).toContain("stt_provider must be one of"); }); test("does not broadcast stt_provider changes to the desktop client", async () => { const messages: any[] = []; const ctx = makeContext({ sendToClient: (msg: any) => messages.push(msg), }); const result = await run( { setting: "stt_provider", value: "deepgram" }, ctx, ); expect(result.isError).toBe(false); expect(messages).toHaveLength(0); }); }); // --------------------------------------------------------------------------- // Tests: stt_language // --------------------------------------------------------------------------- describe("voice_config_update - stt_language", () => { test("persists a curated code to services.stt.language", async () => { const result = await run( { setting: "stt_language", value: "hi" }, makeContext(), ); expect(result.isError).toBe(false); expect((readConfig().services as any)?.stt?.language).toBe("hi"); }); test("a sparse config write seeds the provider so the block survives validation", async () => { // With no services.stt block on disk, a language-only write would persist // { stt: { language } } without the required provider field, and the // loader's validation salvage can then reset the whole services section // (the LUM-2758 failure family). Assert on getConfig(), not just the raw // JSON: the raw file can hold the language while validation throws it // away, which is exactly the failure this guards against. writeConfig({ services: { tts: { provider: "elevenlabs" } } }); invalidateConfigCache(); const result = await run( { setting: "stt_language", value: "hi" }, makeContext(), ); expect(result.isError).toBe(false); // The effective config round-trips the language and keeps a valid provider. expect(getConfig().services.stt.language).toBe("hi"); expect(getConfig().services.stt.provider).toBe("deepgram"); // Unrelated TTS settings survive (no services-section reset). expect(getConfig().services.tts.provider).toBe("elevenlabs"); // The raw block is self-consistent on disk too. const stt = (readConfig().services as any)?.stt; expect(stt).toEqual({ provider: "deepgram", language: "hi" }); }); test("a write against an existing services.stt block is a pure language patch", async () => { writeConfig({ services: { stt: { provider: "xai" } } }); invalidateConfigCache(); const result = await run( { setting: "stt_language", value: "fr" }, makeContext(), ); expect(result.isError).toBe(false); // The configured provider is untouched, not re-seeded from defaults. expect((readConfig().services as any)?.stt).toEqual({ provider: "xai", language: "fr", }); expect(getConfig().services.stt.provider).toBe("xai"); expect(getConfig().services.stt.language).toBe("fr"); }); test("normalizes a language-name alias (case-insensitive, trimmed)", async () => { const result = await run( { setting: "stt_language", value: " Hindi " }, makeContext(), ); expect(result.isError).toBe(false); expect((readConfig().services as any)?.stt?.language).toBe("hi"); }); test("normalizes multilingual to multi", async () => { const result = await run( { setting: "stt_language", value: "multilingual" }, makeContext(), ); expect(result.isError).toBe(false); expect((readConfig().services as any)?.stt?.language).toBe("multi"); }); test("normalizes extended-roster language names (tamil, korean, mandarin)", async () => { // A few of the nova-3 monolingual roster's aliases, including an // alternate name mapping (mandarin -> zh alongside chinese -> zh). for (const [name, code] of [ ["tamil", "ta"], ["Korean", "ko"], ["mandarin", "zh"], ] as const) { const result = await run( { setting: "stt_language", value: name }, makeContext(), ); expect(result.isError).toBe(false); expect((readConfig().services as any)?.stt?.language).toBe(code); } }); test("accepts an extended-roster code directly", async () => { const result = await run( { setting: "stt_language", value: "vi" }, makeContext(), ); expect(result.isError).toBe(false); expect((readConfig().services as any)?.stt?.language).toBe("vi"); }); test("accepts multi directly", async () => { const result = await run( { setting: "stt_language", value: "multi" }, makeContext(), ); expect(result.isError).toBe(false); expect((readConfig().services as any)?.stt?.language).toBe("multi"); }); test("deepgram accepts an extended-roster code", async () => { writeConfig({ services: { stt: { provider: "deepgram" } } }); invalidateConfigCache(); const result = await run( { setting: "stt_language", value: "ta" }, makeContext(), ); expect(result.isError).toBe(false); expect((readConfig().services as any)?.stt?.language).toBe("ta"); }); test("xai rejects an extended-roster code, naming the provider and its set, and writes nothing", async () => { // The extended codes are verified for Deepgram nova-3 only; persisting // one under xai would have every web surface withhold it while the // resolver forwards it unverified. writeConfig({ services: { stt: { provider: "xai" } } }); invalidateConfigCache(); const result = await run( { setting: "stt_language", value: "ta" }, makeContext(), ); expect(result.isError).toBe(true); expect(result.content).toContain("xai"); expect(result.content).toContain("en, es, fr, de, hi, ru, pt, ja, it, nl"); expect(result.content).toContain("Deepgram/managed"); expect((readConfig().services as any)?.stt?.language).toBeUndefined(); }); test("xai rejects multi with the provider-naming error and writes nothing", async () => { // Previously accepted as a silent no-op (the resolver drops "multi" for // xAI); the honest rejection replaces a dead persisted value. writeConfig({ services: { stt: { provider: "xai" } } }); invalidateConfigCache(); const result = await run( { setting: "stt_language", value: "multi" }, makeContext(), ); expect(result.isError).toBe(true); expect(result.content).toContain("xai"); expect(result.content).toContain("Deepgram/managed"); expect((readConfig().services as any)?.stt?.language).toBeUndefined(); }); test("the alias path normalizes before the provider check", async () => { writeConfig({ services: { stt: { provider: "xai" } } }); invalidateConfigCache(); // "Tamil" normalizes to ta first, so it fails with the provider-scoped // error rather than the generic unknown-language one. const rejected = await run( { setting: "stt_language", value: " Tamil " }, makeContext(), ); expect(rejected.isError).toBe(true); expect(rejected.content).toContain("xai"); expect((readConfig().services as any)?.stt?.language).toBeUndefined(); // "French" normalizes to fr, which is inside xai's verified set. const accepted = await run( { setting: "stt_language", value: "French" }, makeContext(), ); expect(accepted.isError).toBe(false); expect((readConfig().services as any)?.stt?.language).toBe("fr"); }); test("an auto-detect provider keeps the unconditional write and notes the setting is ignored", async () => { writeConfig({ services: { stt: { provider: "google-gemini" } } }); invalidateConfigCache(); const result = await run( { setting: "stt_language", value: "ta" }, makeContext(), ); expect(result.isError).toBe(false); expect((readConfig().services as any)?.stt?.language).toBe("ta"); expect(result.content).toContain("google-gemini"); expect(result.content).toContain("auto-detects"); }); test("a language-forwarding provider's success carries no auto-detect note", async () => { writeConfig({ services: { stt: { provider: "deepgram" } } }); invalidateConfigCache(); const result = await run( { setting: "stt_language", value: "hi" }, makeContext(), ); expect(result.isError).toBe(false); expect(result.content).not.toContain("auto-detects"); }); test("rejects an unknown language with the accepted set and writes nothing", async () => { const result = await run( { setting: "stt_language", value: "klingon" }, makeContext(), ); expect(result.isError).toBe(true); expect(result.content).toContain("stt_language must be one of"); expect(result.content).toContain("multi"); expect((readConfig().services as any)?.stt?.language).toBeUndefined(); }); test("rejects a non-string value and writes nothing", async () => { const result = await run( { setting: "stt_language", value: 42 }, makeContext(), ); expect(result.isError).toBe(true); expect(result.content).toContain("stt_language must be one of"); expect((readConfig().services as any)?.stt?.language).toBeUndefined(); }); test("does not broadcast stt_language changes to the desktop client", async () => { const messages: any[] = []; const ctx = makeContext({ sendToClient: (msg: any) => messages.push(msg), }); const result = await run({ setting: "stt_language", value: "fr" }, ctx); expect(result.isError).toBe(false); expect(messages).toHaveLength(0); }); }); // --------------------------------------------------------------------------- // Tests: catalog-driven provider validation // --------------------------------------------------------------------------- describe("voice_config_update — catalog-driven provider validation", () => { test("rejects tts_provider vellum without a platform connection", async () => { mockManagedSpeechAvailable = false; const result = await run( { setting: "tts_provider", value: "vellum" }, makeContext(), ); expect(result.isError).toBe(true); expect(result.content).toContain("assistant platform connect"); expect((readConfig().services as any)?.tts?.provider).toBeUndefined(); }); test("accepts every provider ID in the catalog", async () => { // The catalog includes vellum, which is gated on a platform connection. mockManagedSpeechAvailable = true; const catalogIds = listCatalogProviderIds(); expect(catalogIds.length).toBeGreaterThanOrEqual(1); for (const id of catalogIds) { writeConfig({}); invalidateConfigCache(); const result = await run( { setting: "tts_provider", value: id }, makeContext(), ); expect(result.isError).toBe(false); expect(result.content).toContain("updated to"); } }); test("rejects provider IDs not in the catalog", async () => { const bogusIds = ["nonexistent-provider", "google-tts", "amazon-polly", ""]; for (const id of bogusIds) { writeConfig({}); invalidateConfigCache(); const result = await run( { setting: "tts_provider", value: id }, makeContext(), ); expect(result.isError).toBe(true); expect(result.content).toContain("tts_provider must be one of"); } }); test("error message lists all catalog provider IDs", async () => { const catalogIds = listCatalogProviderIds(); const result = await run( { setting: "tts_provider", value: "bogus" }, makeContext(), ); expect(result.isError).toBe(true); for (const id of catalogIds) { expect(result.content).toContain(id); } }); });