Files
OmniRoute/src/shared/constants/modalityBridgeDefaults.ts
Diego Rodrigues de Sa e Souza a7ddcf16ba feat(bridge): native-vision skip guard + configurable describe output cap (#10289)
* test(bridge): explicit native-vision skip guard + skip log

* feat(bridge): configurable describe output cap (modalityBridgeVisionMaxChars)

* feat(dashboard): maxChars field on Modality Bridge vision tab

Add the "Max description characters" field to the Vision tab's Advanced
panel (modalityBridgeVisionMaxChars, clamped to the 100-50000 schema
range with 0 treated as the explicit "unlimited" sentinel), wire the
en.json copy and sync it across all 42 locales, and document the new
setting in GUARDRAILS.md.

* fix(bridge): allow explicit 0 to disable the describe cap

updateSettingsSchema previously rejected modalityBridgeVisionMaxChars: 0
because the field's range was min(100).max(50000), so a dashboard PATCH
sending the explicit "unlimited" sentinel would 400. Widen the schema to
z.union([z.literal(0), z.number().int().min(100).max(50000)]) so 0
validates as its own valid value, not just an implicit default.

* chore(i18n): resync locale keys after release merge

* chore(quality): rebaseline deadExports for the OCR/image-to-text series

---------

Co-authored-by: Xiangzhe <bakryun0718@proton.me>
2026-08-14 13:06:29 -03:00

119 lines
4.4 KiB
TypeScript

/**
* 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 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,
} 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;
}
// 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<string, unknown> | 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<string, unknown> | 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,
};
}