import { config as loadDotenv } from "dotenv"; import { z } from "zod"; loadDotenv(); const emptyToUndefined = z.preprocess((value) => { if (typeof value !== "string") { return value; } const trimmed = value.trim(); return trimmed.length === 0 ? undefined : trimmed; }, z.string().min(1).optional()); const envSchema = z.object({ DISCORD_BOT_TOKEN: emptyToUndefined, DISCORD_APPLICATION_ID: emptyToUndefined, DISCORD_COMMAND_GUILD_ID: emptyToUndefined, OLLAMA_BASE_URL: z.string().min(1).default("http://localhost:11434"), OLLAMA_MODEL: z.string().min(1).default("qwen3:0.6b"), OLLAMA_KEEP_ALIVE: z.string().min(1).default("5m"), OLLAMA_NUM_CTX: z.coerce.number().int().min(512).max(32768).default(4096), LOCAL_AI_VENV_PATH: z.string().min(1).default(".local-ai/.venv"), LOCAL_AI_CACHE_DIR: z.string().min(1).default(".local-ai/cache"), LOCAL_AI_PYTHON: emptyToUndefined, LOCAL_STT_MODEL: z.string().min(1).default("tiny"), LOCAL_STT_DEVICE: z.string().min(1).default("auto"), LOCAL_STT_COMPUTE_TYPE: z.string().min(1).default("auto"), LOCAL_STT_BEAM_SIZE: z.coerce.number().int().min(1).max(8).default(1), LOCAL_TTS_LANGUAGE: z.string().min(1).default("KR"), LOCAL_TTS_SPEAKER: z.string().min(1).default("KR"), LOCAL_TTS_DEVICE: z.string().min(1).default("auto"), LOCAL_TTS_SPEED: z.coerce.number().min(0.8).max(1.6).default(1.12), BOT_DEFAULT_LANGUAGE: z.string().min(2).default("ko"), MAX_CONVERSATION_TURNS: z.coerce.number().int().min(4).max(30).default(12), LOCAL_AUDIO_SOURCE: emptyToUndefined, LOCAL_AUDIO_SINK: emptyToUndefined, LOCAL_SPEAKER_NAME: z.string().min(1).default("local-user"), DEBUG_TEXT_EVENTS: z .string() .optional() .transform((value) => value === "true"), LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"), }); export type AppConfig = z.infer; export type AssistantRuntimeConfig = AppConfig; export type DiscordRuntimeConfig = AssistantRuntimeConfig & { DISCORD_BOT_TOKEN: string; DISCORD_APPLICATION_ID: string; }; export function loadConfig(): AppConfig { return envSchema.parse(process.env); } function requirePresent(value: string | undefined, name: string): string { if (!value) { throw new Error(`${name} 환경변수가 필요합니다.`); } return value; } export function requireAssistantRuntimeConfig(config: AppConfig): AssistantRuntimeConfig { return config; } export function requireDiscordRuntimeConfig(config: AppConfig): DiscordRuntimeConfig { const assistant = requireAssistantRuntimeConfig(config); return { ...assistant, DISCORD_BOT_TOKEN: requirePresent(config.DISCORD_BOT_TOKEN, "DISCORD_BOT_TOKEN"), DISCORD_APPLICATION_ID: requirePresent(config.DISCORD_APPLICATION_ID, "DISCORD_APPLICATION_ID"), }; }