/** * Modality Bridge default configuration values (PR-1). * * New `modalityBridge*` settings keys supersede the legacy `visionBridge*` * keys; the legacy keys stay accepted as a fallback for one release cycle * (rollback window) and are resolved here in a single place. */ import { VISION_BRIDGE_DEFAULTS } from "./visionBridgeDefaults"; export type VisionBridgeMode = "auto" | "describe" | "reroute"; export type VideoSamplingPolicy = "uniform" | "scene_aware"; export const VIDEO_BRIDGE_TIMEOUT_MIN_MS = 1_000; export const VIDEO_BRIDGE_TIMEOUT_MAX_MS = 120_000; export const MODALITY_BRIDGE_DEFAULTS = { visionMode: "auto" as VisionBridgeMode, visionTaskAware: true, cacheEnabled: true, cacheTtlMinutes: 60, cacheMaxEntries: 200, // 0 = no cap (existing behavior, description is passed through in full). visionMaxChars: 0, audioEnabled: true, audioModel: "", audioTimeoutMs: 60000, audioMaxClips: 3, videoEnabled: false, videoModel: "", videoFrameCount: 8, videoSamplingPolicy: "uniform" as VideoSamplingPolicy, videoMaxVideos: 1, videoTimeoutMs: 120000, } as const; export interface VisionBridgeRuntimeSettings { enabled: boolean; mode: VisionBridgeMode; model: string; taskAware: boolean; prompt: string; timeoutMs: number; maxImages: number; maxChars: number; cacheEnabled: boolean; cacheTtlMinutes: number; cacheMaxEntries: number; } export interface AudioBridgeRuntimeSettings { enabled: boolean; model: string; timeoutMs: number; maxClips: number; cacheEnabled: boolean; cacheTtlMinutes: number; cacheMaxEntries: number; } export interface VideoBridgeRuntimeSettings { enabled: boolean; model: string; frameCount: number; samplingPolicy: VideoSamplingPolicy; maxVideos: number; timeoutMs: 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 { for (const v of values) if (typeof v === "boolean") return v; return undefined; } function pickNumber(...values: unknown[]): number | undefined { for (const v of values) if (typeof v === "number" && Number.isFinite(v)) return v; return undefined; } function pickString(...values: unknown[]): string | undefined { for (const v of values) if (typeof v === "string") return v; return undefined; } /** New modalityBridge* keys win; legacy visionBridge* keys are a one-cycle fallback. */ export function resolveVisionBridgeRuntimeSettings( settings: Record | null | undefined ): VisionBridgeRuntimeSettings { const s = settings ?? {}; const mode = pickString(s.modalityBridgeVisionMode); return { enabled: pickBoolean(s.modalityBridgeVisionEnabled, s.visionBridgeEnabled) ?? VISION_BRIDGE_DEFAULTS.enabled, mode: mode === "describe" || mode === "reroute" ? mode : MODALITY_BRIDGE_DEFAULTS.visionMode, model: pickString(s.modalityBridgeVisionModel, s.visionBridgeModel) ?? VISION_BRIDGE_DEFAULTS.model, taskAware: pickBoolean(s.modalityBridgeVisionTaskAware) ?? MODALITY_BRIDGE_DEFAULTS.visionTaskAware, prompt: pickString(s.modalityBridgeVisionPrompt, s.visionBridgePrompt) ?? VISION_BRIDGE_DEFAULTS.prompt, timeoutMs: pickNumber(s.modalityBridgeVisionTimeout, s.visionBridgeTimeout) ?? VISION_BRIDGE_DEFAULTS.timeoutMs, maxImages: pickNumber(s.modalityBridgeVisionMaxImages, s.visionBridgeMaxImages) ?? VISION_BRIDGE_DEFAULTS.maxImagesPerRequest, maxChars: pickNumber(s.modalityBridgeVisionMaxChars) ?? MODALITY_BRIDGE_DEFAULTS.visionMaxChars, cacheEnabled: pickBoolean(s.modalityBridgeCacheEnabled) ?? MODALITY_BRIDGE_DEFAULTS.cacheEnabled, cacheTtlMinutes: pickNumber(s.modalityBridgeCacheTtlMinutes) ?? MODALITY_BRIDGE_DEFAULTS.cacheTtlMinutes, cacheMaxEntries: 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, }; } /** Resolve persisted Video Bridge settings with safe, bounded defaults. */ export function resolveVideoBridgeRuntimeSettings( settings: Record | null | undefined ): VideoBridgeRuntimeSettings { const s = settings ?? {}; return { enabled: pickBoolean(s.modalityBridgeVideoEnabled) ?? MODALITY_BRIDGE_DEFAULTS.videoEnabled, model: pickString(s.modalityBridgeVideoModel) ?? MODALITY_BRIDGE_DEFAULTS.videoModel, frameCount: pickNumber(s.modalityBridgeVideoFrameCount) ?? MODALITY_BRIDGE_DEFAULTS.videoFrameCount, samplingPolicy: pickString(s.modalityBridgeVideoSamplingPolicy) === "scene_aware" ? "scene_aware" : MODALITY_BRIDGE_DEFAULTS.videoSamplingPolicy, maxVideos: pickNumber(s.modalityBridgeVideoMaxVideos) ?? MODALITY_BRIDGE_DEFAULTS.videoMaxVideos, timeoutMs: Math.min( VIDEO_BRIDGE_TIMEOUT_MAX_MS, Math.max( VIDEO_BRIDGE_TIMEOUT_MIN_MS, pickNumber(s.modalityBridgeVideoTimeout) ?? MODALITY_BRIDGE_DEFAULTS.videoTimeoutMs ) ), cacheEnabled: pickBoolean(s.modalityBridgeCacheEnabled) ?? MODALITY_BRIDGE_DEFAULTS.cacheEnabled, cacheTtlMinutes: pickNumber(s.modalityBridgeCacheTtlMinutes) ?? MODALITY_BRIDGE_DEFAULTS.cacheTtlMinutes, cacheMaxEntries: pickNumber(s.modalityBridgeCacheMaxEntries) ?? MODALITY_BRIDGE_DEFAULTS.cacheMaxEntries, }; }