66 lines
3.2 KiB
TypeScript
66 lines
3.2 KiB
TypeScript
import { z } from 'zod';
|
|
|
|
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('sam-desktop/qwen3.6-35b-a3b'),
|
|
LOG_LEVEL: z.string().default('info'),
|
|
// v1.11.8: SearXNG JSON endpoint for web_search / web_fetch tools.
|
|
// Defaults to the internal Tailscale Fathom URL (bypasses Authelia).
|
|
// The public search.indifferentketchup.com URL would 302 to auth and
|
|
// is unusable from the server context — keep the internal one.
|
|
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(),
|
|
// v1.15.0-mcp-multi: path to the MCP config JSON file. Default /data/mcp.json
|
|
// (bind-mounted alongside AGENTS.md). File missing = no MCP (opt-in).
|
|
MCP_CONFIG_PATH: z.string().optional(),
|
|
// v2.0.5: cheaper model for titles, summaries, labeling. Falls back to
|
|
// session model (auto_name) or DEFAULT_MODEL when unset.
|
|
FAST_MODEL: z.string().optional(),
|
|
TASK_MODEL_URL: z.string().url().optional(),
|
|
// vDeepSeek: DeepSeek API key for direct API access. When set, models
|
|
// with IDs starting with 'deepseek-' route through DeepSeek's API instead
|
|
// of llama-swap. Defaults to empty (DeepSeek routing disabled).
|
|
DEEPSEEK_API_KEY: z.string().optional(),
|
|
// Optional base URL override for DeepSeek API. Defaults to api.deepseek.com.
|
|
DEEPSEEK_BASE_URL: z.string().url().default('https://api.deepseek.com'),
|
|
// Beta endpoint for experimental features (strict tools, prefix completion, etc.).
|
|
// Defaults to api.deepseek.com/beta. When set, deepseek calls with tools or
|
|
// prefix content route through this endpoint.
|
|
DEEPSEEK_BETA_BASE_URL: z.string().url().default('https://api.deepseek.com/beta'),
|
|
// Hosted Anthropic Claude. When set, models with provider id "anthropic"
|
|
// (or bare "claude-*" ids) route through the Anthropic Messages API via
|
|
// @ai-sdk/anthropic instead of llama-swap. Unset = Claude routing disabled.
|
|
ANTHROPIC_API_KEY: z.string().optional(),
|
|
ANTHROPIC_BASE_URL: z.string().url().optional(),
|
|
// vWhale hooks: path to hooks JSON config file. Missing file = no hooks.
|
|
HOOKS_CONFIG_PATH: z.string().default('/data/hooks.json'),
|
|
// vMultiProvider: path to the local providers config JSON file. Missing file
|
|
// = legacy synthesis from LLAMA_SWAP_URL.
|
|
LLAMA_PROVIDERS_PATH: z.string().optional(),
|
|
BOOCONTROL_URL: z.string().url().optional(),
|
|
});
|
|
|
|
export type Config = z.infer<typeof ConfigSchema>;
|
|
|
|
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;
|
|
}
|