import { z } from 'zod'; // BooCoder's config is a superset of the server's Config type so it can be // passed directly into the inference runner's InferenceContext. Fields the // inference loop reads: LLAMA_SWAP_URL, PROJECT_ROOT_WHITELIST. The rest // default to values that satisfy the server's Zod schema without BooCoder // needing to supply them in its environment. const ConfigSchema = z.object({ NODE_ENV: z.enum(['development', 'production', 'test']).default('development'), PORT: z.coerce.number().int().positive().default(3000), HOST: z.string().default('0.0.0.0'), DATABASE_URL: z.string().url(), LLAMA_SWAP_URL: z.string().url(), PROJECT_ROOT_WHITELIST: z.string().default('/opt'), BOOTSTRAP_ROOT: z.string().default('/opt/projects'), DEFAULT_MODEL: z.string().default('qwen3.6-35b-a3b-mxfp4'), LOG_LEVEL: z.string().default('info'), CONTAINER_GUIDANCE_FILE: z.string().optional(), // Fields needed to satisfy the server's Config type but unused by BooCoder: SEARXNG_URL: z.string().url().default('http://100.114.205.53:8888'), GITEA_BASE_URL: z.string().url().default('https://git.indifferentketchup.com'), GITEA_USER: z.string().default('indifferentketchup'), GITEA_TOKEN: z.string().optional(), GITEA_SSH_HOST: z.string().default('100.114.205.53:2222'), MCP_CONFIG_PATH: z.string().optional(), // v2.3: config-backed provider overrides/custom-ACP entries merged over the // hardcoded built-ins. Missing file = built-ins only (see provider-config.ts). CODER_PROVIDERS_PATH: z.string().default('/data/coder-providers.json'), // v2.3 phase 2: tier-2 (cold ACP probe) is skipped when available_agents was // probed more recently than this. 24h default — stale model lists self-heal // on the next snapshot; an explicit /refresh always re-probes. PROVIDER_PROBE_TTL_MS: z.coerce.number().int().positive().default(86_400_000), // v2.0.5: cheaper model for titles, summaries, labeling. FAST_MODEL: z.string().optional(), // SSH access to the host for external agent dispatch (Phase 5) BOOCODER_SSH_HOST: z.string().default('100.114.205.53'), BOOCODER_SSH_USER: z.string().default('samkintop'), }); export type Config = z.infer; let cached: Config | null = null; export function loadConfig(): Config { if (cached) return cached; const parsed = ConfigSchema.safeParse(process.env); if (!parsed.success) { console.error('Invalid environment configuration:'); console.error(parsed.error.flatten().fieldErrors); process.exit(1); } cached = parsed.data; return cached; }