/ src / server / storage / settings / validate.ts
validate.ts
  1  import type {
  2    ProviderEndpointMode,
  3    ReasoningLevel,
  4    ThinkingLevel,
  5    WebSearchProvider,
  6  } from '@/lib/shared/chat'
  7  
  8  export function normalizeNullableString(
  9    value: string | null | undefined,
 10  ): string | null {
 11    const trimmed = value?.trim()
 12    return trimmed ? trimmed : null
 13  }
 14  
 15  export function normalizeThinkingLevel(
 16    value: string | null | undefined,
 17  ): ThinkingLevel | null {
 18    if (
 19      value === 'off' ||
 20      value === 'minimal' ||
 21      value === 'low' ||
 22      value === 'medium' ||
 23      value === 'high'
 24    ) {
 25      return value
 26    }
 27    return null
 28  }
 29  
 30  export function normalizeReasoningLevel(
 31    value: string | null | undefined,
 32  ): ReasoningLevel | null {
 33    if (value === 'off' || value === 'on' || value === 'stream') {
 34      return value
 35    }
 36    return null
 37  }
 38  
 39  export function normalizeProviderEndpointMode(
 40    value: string | null | undefined,
 41  ): ProviderEndpointMode | null {
 42    if (
 43      value === 'auto' ||
 44      value === 'responses' ||
 45      value === 'chat_completions'
 46    ) {
 47      return value
 48    }
 49    return null
 50  }
 51  
 52  export function normalizeRetentionDays(value: number | null | undefined): number {
 53    const fallback = 30
 54    if (value == null || !Number.isFinite(value)) {
 55      return fallback
 56    }
 57    const rounded = Math.round(value)
 58    return Math.min(3650, Math.max(1, rounded))
 59  }
 60  
 61  export function normalizeWebSearchProvider(
 62    rawValue: string | null | undefined,
 63  ): WebSearchProvider | null {
 64    if (!rawValue) return null
 65    const validProviders = [
 66      'brave',
 67      'perplexity',
 68      'grok',
 69      'gemini',
 70      'kimi',
 71      'openrouter',
 72    ]
 73    return validProviders.includes(rawValue)
 74      ? (rawValue as WebSearchProvider)
 75      : null
 76  }
 77  
 78  export function normalizeAgentBackendsEnabled(
 79    value: string | null | undefined,
 80  ): Partial<Record<string, boolean>> {
 81    if (!value) return {}
 82    try {
 83      const parsed = JSON.parse(value) as unknown
 84      if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
 85        return {}
 86      }
 87      return normalizeAgentBackendsEnabledPatch(
 88        parsed as Record<string, unknown>,
 89      )
 90    } catch {
 91      return {}
 92    }
 93  }
 94  
 95  export function normalizeAgentBackendsEnabledPatch(
 96    value: Record<string, unknown> | null | undefined,
 97  ): Partial<Record<string, boolean>> {
 98    if (!value) return {}
 99    const next: Partial<Record<string, boolean>> = {}
100    for (const [key, raw] of Object.entries(value)) {
101      const backendId = key.trim().toLowerCase()
102      if (!backendId || typeof raw !== 'boolean') {
103        continue
104      }
105      next[backendId] = raw
106    }
107    return next
108  }