read.ts
1 import type { 2 GlobalDefaults, 3 ProviderProfileId, 4 } from '@/lib/shared/chat' 5 import { getDb } from '@/server/storage/db' 6 import { 7 normalizeAgentBackendsEnabled, 8 normalizeNullableString, 9 normalizeProviderEndpointMode, 10 normalizeReasoningLevel, 11 normalizeRetentionDays, 12 normalizeThinkingLevel, 13 normalizeWebSearchProvider, 14 } from './validate' 15 16 interface AppSettingsRow { 17 id: number 18 default_chat_model_id: string | null 19 default_embedding_model_id: string | null 20 default_transcription_model_id: string | null 21 default_embedding_provider_id: string | null 22 default_transcription_provider_id: string | null 23 default_provider_id: string | null 24 default_provider_endpoint_mode: string | null 25 default_thinking_level: string | null 26 default_reasoning_level: string | null 27 web_search_provider: string | null 28 web_search_max_results: number | null 29 web_fetch_max_bytes: number | null 30 agent_backends_enabled_json: string | null 31 tool_call_log_retention_days: number | null 32 updated_at: string | null 33 } 34 35 export function ensureAppSettingsRow(): void { 36 const db = getDb() 37 const existing = db 38 .prepare('SELECT id FROM app_settings WHERE id = 1') 39 .get() as { id: number } | undefined 40 if (existing) return 41 42 db.prepare( 43 `INSERT INTO app_settings ( 44 id, 45 default_chat_model_id, 46 default_embedding_model_id, 47 default_transcription_model_id, 48 default_embedding_provider_id, 49 default_transcription_provider_id, 50 default_provider_id, 51 default_provider_endpoint_mode, 52 default_thinking_level, 53 default_reasoning_level, 54 web_search_provider, 55 web_search_max_results, 56 web_fetch_max_bytes, 57 agent_backends_enabled_json, 58 tool_call_log_retention_days, 59 updated_at 60 ) VALUES (1, NULL, NULL, NULL, NULL, NULL, NULL, 'auto', NULL, NULL, NULL, NULL, NULL, '{}', 30, ?)`, 61 ).run(new Date().toISOString()) 62 } 63 64 export function getAppSettings(): GlobalDefaults { 65 const db = getDb() 66 ensureAppSettingsRow() 67 68 const row = db 69 .prepare( 70 `SELECT 71 id, 72 default_chat_model_id, 73 default_embedding_model_id, 74 default_transcription_model_id, 75 default_embedding_provider_id, 76 default_transcription_provider_id, 77 default_provider_id, 78 default_provider_endpoint_mode, 79 default_thinking_level, 80 default_reasoning_level, 81 web_search_provider, 82 web_search_max_results, 83 web_fetch_max_bytes, 84 agent_backends_enabled_json, 85 tool_call_log_retention_days, 86 updated_at 87 FROM app_settings 88 WHERE id = 1`, 89 ) 90 .get() as AppSettingsRow | undefined 91 92 return { 93 chatModel: normalizeNullableString(row?.default_chat_model_id), 94 embeddingModel: normalizeNullableString(row?.default_embedding_model_id), 95 transcriptionModel: normalizeNullableString( 96 row?.default_transcription_model_id, 97 ), 98 embeddingProviderId: normalizeNullableString( 99 row?.default_embedding_provider_id, 100 ), 101 transcriptionProviderId: normalizeNullableString( 102 row?.default_transcription_provider_id, 103 ), 104 providerModelDefaults: readProviderModelDefaults(), 105 thinkingLevel: normalizeThinkingLevel(row?.default_thinking_level), 106 reasoningLevel: normalizeReasoningLevel(row?.default_reasoning_level), 107 defaultProviderId: normalizeNullableString(row?.default_provider_id), 108 providerEndpointMode: normalizeProviderEndpointMode( 109 row?.default_provider_endpoint_mode, 110 ), 111 webSearchProvider: normalizeWebSearchProvider(row?.web_search_provider), 112 webSearchMaxResults: row?.web_search_max_results ?? null, 113 webFetchMaxBytes: row?.web_fetch_max_bytes ?? null, 114 agentBackendsEnabled: normalizeAgentBackendsEnabled( 115 row?.agent_backends_enabled_json, 116 ), 117 toolCallLogRetentionDays: normalizeRetentionDays( 118 row?.tool_call_log_retention_days, 119 ), 120 updatedAt: normalizeNullableString(row?.updated_at), 121 } 122 } 123 124 function readProviderModelDefaults(): Partial<Record<ProviderProfileId, string>> { 125 const db = getDb() 126 const rows = db 127 .prepare( 128 `SELECT 129 pp.id AS provider_id, 130 pm.provider_model_ref AS default_model_ref 131 FROM provider_profiles pp 132 LEFT JOIN provider_models pm 133 ON pm.provider_id = pp.id 134 AND pm.model_id = pp.default_model_id`, 135 ) 136 .all() as Array<{ provider_id: string; default_model_ref: string | null }> 137 138 const next: Partial<Record<ProviderProfileId, string>> = {} 139 for (const row of rows) { 140 const providerId = row.provider_id.trim() 141 const modelRef = normalizeNullableString(row.default_model_ref) 142 if (!providerId || !modelRef) continue 143 next[providerId] = modelRef 144 } 145 return next 146 } 147 148 export function resolveDefaultProviderProfileId( 149 defaults: GlobalDefaults, 150 ): ProviderProfileId { 151 if (defaults.defaultProviderId) return defaults.defaultProviderId 152 return 'openai' 153 } 154 155 export function resolveProviderScopedDefaultChatModel( 156 defaults: GlobalDefaults, 157 profileId: ProviderProfileId, 158 ): string | null { 159 return normalizeNullableString( 160 defaults.providerModelDefaults[profileId] ?? null, 161 ) 162 } 163 164 export function resolveDefaultChatModelForProfile( 165 defaults: GlobalDefaults, 166 profileId: ProviderProfileId, 167 ): string | null { 168 return resolveProviderScopedDefaultChatModel(defaults, profileId) 169 }