diff --git a/src/shared/constants/modalityBridgeDefaults.ts b/src/shared/constants/modalityBridgeDefaults.ts index 336d45b280..4fd98b9d61 100644 --- a/src/shared/constants/modalityBridgeDefaults.ts +++ b/src/shared/constants/modalityBridgeDefaults.ts @@ -34,6 +34,16 @@ export interface VisionBridgeRuntimeSettings { cacheMaxEntries: number; } +export interface AudioBridgeRuntimeSettings { + enabled: boolean; + model: string; + timeoutMs: number; + maxClips: number; + cacheEnabled: boolean; + cacheTtlMinutes: number; + cacheMaxEntries: number; +} + // Typed candidate pickers: a stored value of the wrong type (e.g. the string // "off" in a boolean field) is skipped so the next candidate/default wins. function pickBoolean(...values: unknown[]): boolean | undefined { @@ -83,3 +93,22 @@ export function resolveVisionBridgeRuntimeSettings( pickNumber(s.modalityBridgeCacheMaxEntries) ?? MODALITY_BRIDGE_DEFAULTS.cacheMaxEntries, }; } + +/** Resolve persisted Audio Bridge settings with the shared PR-1 defaults. */ +export function resolveAudioBridgeRuntimeSettings( + settings: Record | null | undefined +): AudioBridgeRuntimeSettings { + const s = settings ?? {}; + return { + enabled: pickBoolean(s.modalityBridgeAudioEnabled) ?? MODALITY_BRIDGE_DEFAULTS.audioEnabled, + model: pickString(s.modalityBridgeAudioModel) ?? MODALITY_BRIDGE_DEFAULTS.audioModel, + timeoutMs: pickNumber(s.modalityBridgeAudioTimeout) ?? MODALITY_BRIDGE_DEFAULTS.audioTimeoutMs, + maxClips: pickNumber(s.modalityBridgeAudioMaxClips) ?? MODALITY_BRIDGE_DEFAULTS.audioMaxClips, + cacheEnabled: + pickBoolean(s.modalityBridgeCacheEnabled) ?? MODALITY_BRIDGE_DEFAULTS.cacheEnabled, + cacheTtlMinutes: + pickNumber(s.modalityBridgeCacheTtlMinutes) ?? MODALITY_BRIDGE_DEFAULTS.cacheTtlMinutes, + cacheMaxEntries: + pickNumber(s.modalityBridgeCacheMaxEntries) ?? MODALITY_BRIDGE_DEFAULTS.cacheMaxEntries, + }; +} diff --git a/tests/unit/audio-bridge-settings.test.ts b/tests/unit/audio-bridge-settings.test.ts new file mode 100644 index 0000000000..3dcfe736cb --- /dev/null +++ b/tests/unit/audio-bridge-settings.test.ts @@ -0,0 +1,56 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { + MODALITY_BRIDGE_DEFAULTS, + resolveAudioBridgeRuntimeSettings, +} from "../../src/shared/constants/modalityBridgeDefaults.ts"; + +test("Audio Bridge runtime settings use the PR-1 defaults", () => { + assert.deepEqual(resolveAudioBridgeRuntimeSettings({}), { + enabled: MODALITY_BRIDGE_DEFAULTS.audioEnabled, + model: MODALITY_BRIDGE_DEFAULTS.audioModel, + timeoutMs: MODALITY_BRIDGE_DEFAULTS.audioTimeoutMs, + maxClips: MODALITY_BRIDGE_DEFAULTS.audioMaxClips, + cacheEnabled: MODALITY_BRIDGE_DEFAULTS.cacheEnabled, + cacheTtlMinutes: MODALITY_BRIDGE_DEFAULTS.cacheTtlMinutes, + cacheMaxEntries: MODALITY_BRIDGE_DEFAULTS.cacheMaxEntries, + }); +}); + +test("Audio Bridge runtime settings accept typed persisted values", () => { + assert.deepEqual( + resolveAudioBridgeRuntimeSettings({ + modalityBridgeAudioEnabled: false, + modalityBridgeAudioModel: "deepgram/nova-3", + modalityBridgeAudioTimeout: 12_000, + modalityBridgeAudioMaxClips: 2, + modalityBridgeCacheEnabled: false, + modalityBridgeCacheTtlMinutes: 5, + modalityBridgeCacheMaxEntries: 20, + }), + { + enabled: false, + model: "deepgram/nova-3", + timeoutMs: 12_000, + maxClips: 2, + cacheEnabled: false, + cacheTtlMinutes: 5, + cacheMaxEntries: 20, + } + ); +}); + +test("Audio Bridge runtime settings ignore wrong-typed persisted values", () => { + const settings = resolveAudioBridgeRuntimeSettings({ + modalityBridgeAudioEnabled: "false", + modalityBridgeAudioModel: 42, + modalityBridgeAudioTimeout: "12000", + modalityBridgeAudioMaxClips: null, + }); + + assert.equal(settings.enabled, MODALITY_BRIDGE_DEFAULTS.audioEnabled); + assert.equal(settings.model, MODALITY_BRIDGE_DEFAULTS.audioModel); + assert.equal(settings.timeoutMs, MODALITY_BRIDGE_DEFAULTS.audioTimeoutMs); + assert.equal(settings.maxClips, MODALITY_BRIDGE_DEFAULTS.audioMaxClips); +});