/** * Centralized environment variable access with validation. * * All runtime environment variables should be accessed through this module * instead of reading process.env directly. This provides: * - Single source of truth for env var names and defaults * - Type-safe accessors (string, number, boolean) * - Fail-fast validation via validateEnv() at startup * - Shared derived values (e.g. gateway base URL) instead of duplicated logic * * Bootstrap-level env vars (IS_CONTAINERIZED, DEBUG_STDOUT_LOGS) are defined * in config/env-registry.ts which has no internal dependencies and can be * imported from platform/logger without circular imports. */ import { getLogger } from "../util/logger.js"; import { checkUnrecognizedEnvVars } from "./env-registry.js"; import { getConfig } from "./loader.js"; const log = getLogger("env"); // ── Helpers ────────────────────────────────────────────────────────────────── /** Read an env var as a trimmed non-empty string, or undefined. */ function str(name: string): string | undefined { const v = process.env[name]?.trim(); return v || undefined; } /** Read an env var as an integer with fallback. Returns undefined if not set and no fallback given. */ function int(name: string, fallback: number): number; function int(name: string): number | undefined; function int(name: string, fallback?: number): number | undefined { const raw = str(name); if (!raw) { return fallback; } const n = parseInt(raw, 10); if (isNaN(n)) { throw new Error( `Invalid integer for ${name}: "${raw}"${ fallback !== undefined ? ` (fallback: ${fallback})` : "" }`, ); } return n; } // ── Gateway ────────────────────────────────────────────────────────────────── const DEFAULT_GATEWAY_PORT = 7830; function getGatewayPort(): number { return int("GATEWAY_PORT", DEFAULT_GATEWAY_PORT); } /** * Resolve the gateway base URL for internal service-to-service calls. * * In containerized deployments the gateway runs in a separate container, * reachable via `GATEWAY_INTERNAL_URL` (e.g. `http://gateway:7822`). * Falls back to `http://127.0.0.1:` for local deployments. */ export function getGatewayInternalBaseUrl(): string { return str("GATEWAY_INTERNAL_URL") ?? `http://127.0.0.1:${getGatewayPort()}`; } // ── Ingress ────────────────────────────────────────────────────────────────── let _ingressPublicBaseUrl: string | undefined; /** Read the ingress public base URL (module-level state, mutated at runtime by config handlers). */ export function getIngressPublicBaseUrl(): string | undefined { return _ingressPublicBaseUrl; } /** Set or clear the ingress public base URL (used by config handlers). */ export function setIngressPublicBaseUrl(value: string | undefined): void { _ingressPublicBaseUrl = value; } // ── Runtime HTTP ───────────────────────────────────────────────────────────── export function getRuntimeHttpPort(): number { return int("RUNTIME_HTTP_PORT") ?? 7821; } export function getRuntimeHttpHost(): string { return str("RUNTIME_HTTP_HOST") || "127.0.0.1"; } /** * True when HTTP API auth is disabled via DISABLE_HTTP_AUTH=true. * Used in platform-managed deployments where the platform handles auth. */ export function isHttpAuthDisabled(): boolean { return str("DISABLE_HTTP_AUTH")?.toLowerCase() === "true"; } // ── Qdrant ─────────────────────────────────────────────────────────────────── export function getQdrantUrlEnv(): string | undefined { return str("QDRANT_URL"); } export function getQdrantHttpPortEnv(): number | undefined { return int("QDRANT_HTTP_PORT"); } export function getQdrantReadyzTimeoutMs(): number | undefined { return int("QDRANT_READYZ_TIMEOUT_MS"); } // ── Ollama ─────────────────────────────────────────────────────────────────── export function getOllamaBaseUrlEnv(): string | undefined { return str("OLLAMA_BASE_URL"); } // ── Platform ───────────────────────────────────────────────────────────────── let _platformBaseUrlOverride: string | undefined; export function setPlatformBaseUrl(value: string | undefined): void { _platformBaseUrlOverride = value; } export function getPlatformBaseUrl(): string { let configUrl: string | undefined; try { const val = getConfig().platform.baseUrl; if (val) { configUrl = val; } } catch { // Config not yet available (early bootstrap) — fall through } // Resolve the default platform URL from VELLUM_ENVIRONMENT. // `production`, `staging`, and `test` map to their respective hosted // platforms, `local` points at a developer's locally running platform, // and everything else (including unset) falls back to dev-platform. const env = str("VELLUM_ENVIRONMENT")?.trim(); let defaultUrl: string; if (env === "production") { defaultUrl = "https://platform.vellum.ai"; } else if (env === "staging") { defaultUrl = "https://staging-platform.vellum.ai"; } else if (env === "test") { defaultUrl = "https://test-platform.vellum.ai"; } else if (env === "local") { defaultUrl = "http://localhost:8000"; } else { defaultUrl = "https://dev-platform.vellum.ai"; } return ( configUrl || str("VELLUM_PLATFORM_URL") || _platformBaseUrlOverride || defaultUrl ); } /** * Returns the environment-level apex domain (e.g. "vellum.me", * "dev.vellum.me", "staging.vellum.me"). Never includes the * assistant-specific subdomain. */ export function getApexDomain(): string { try { const url = getPlatformBaseUrl(); const host = new URL(url).hostname; if (host.endsWith("platform.vellum.ai")) { const prefix = host.replace(/[-.]?platform\.vellum\.ai$/, ""); if (prefix) { return `${prefix}.vellum.me`; } return "vellum.me"; } const env = str("VELLUM_ENVIRONMENT")?.trim(); if (env && env !== "production") { return `${env}.vellum.me`; } return "local.vellum.me"; } catch { // Fall through to default } return "vellum.me"; } export function getAssistantDomain(): string { const subdomain = (() => { try { return getConfig().platform?.subdomain; } catch { return undefined; } })(); const apex = getApexDomain(); if (subdomain) { return `${subdomain}.${apex}`; } return apex; } let _platformAssistantIdOverride: string | undefined; export function setPlatformAssistantId(value: string | undefined): void { _platformAssistantIdOverride = value; } /** * Platform assistant ID — UUID of this assistant on the platform. * * Resolved from the in-memory override (populated by providers-setup * rehydration from the credential store at daemon startup, or by * secret-routes when the platform pushes the value). */ export function getPlatformAssistantId(): string { return _platformAssistantIdOverride ?? ""; } let _platformOrganizationIdOverride: string | undefined; export function setPlatformOrganizationId(value: string | undefined): void { _platformOrganizationIdOverride = value; } /** * PLATFORM_ORGANIZATION_ID — UUID of the organization this assistant belongs to. * Used for Sentry tagging and platform API calls. */ export function getPlatformOrganizationId(): string { return ( str("PLATFORM_ORGANIZATION_ID") ?? _platformOrganizationIdOverride ?? "" ); } let _platformUserIdOverride: string | undefined; export function setPlatformUserId(value: string | undefined): void { _platformUserIdOverride = value; } /** * PLATFORM_USER_ID — UUID of the user who owns this assistant. * Used for telemetry and platform API calls. */ export function getPlatformUserId(): string { return str("PLATFORM_USER_ID") ?? _platformUserIdOverride ?? ""; } // ── Startup validation ────────────────────────────────────────────────────── /** * Validate environment at startup. Call early in daemon lifecycle * (after dotenv loads). Throws on invalid required values; warns on * deprecated vars. */ export function validateEnv(): void { const gatewayPort = getGatewayPort(); if (gatewayPort < 1 || gatewayPort > 65535) { throw new Error(`Invalid GATEWAY_PORT: ${gatewayPort} (must be 1-65535)`); } const httpPort = getRuntimeHttpPort(); if (httpPort < 1 || httpPort > 65535) { throw new Error(`Invalid RUNTIME_HTTP_PORT: ${httpPort} (must be 1-65535)`); } for (const warning of checkUnrecognizedEnvVars()) { log.warn(warning); } }