package config import ( "log" "os" "strconv" "strings" "github.com/joho/godotenv" ) // Config holds all runtime configuration for the GoClaw Gateway. // // LLM Provider selection (via LLM_BASE_URL env): // // Cloud (default): https://ollama.com/v1 — Ollama Cloud API (requires OLLAMA_API_KEY) // OpenAI-compat: https://api.openai.com/v1 — OpenAI or any OpenAI-compatible endpoint // Local GPU node: http://localhost:11434/v1 — Local Ollama (only on machines with GPU) // // To use a local Ollama instance on a GPU-equipped agent node, set: // // LLM_BASE_URL=http://:11434/v1 // LLM_API_KEY= # (empty for local Ollama) type Config struct { // Server Port string // LLM / Ollama Cloud // LLM_BASE_URL — OpenAI-compatible base URL (without trailing slash, /v1 suffix included). // Default: https://ollama.com/v1 (Ollama Cloud). // For local GPU agents set LLM_BASE_URL=http://:11434/v1 LLMBaseURL string LLMAPIKey string // Deprecated aliases kept for backward-compat (mapped to LLMBaseURL / LLMAPIKey) OllamaBaseURL string OllamaAPIKey string // Database (MySQL/TiDB) DatabaseURL string // Project root (for file tools) ProjectRoot string // Gateway defaults DefaultModel string MaxToolIterations int RequestTimeoutSecs int } func Load() *Config { // Try to load .env from parent directory (project root) or current dir _ = godotenv.Load("../.env") _ = godotenv.Load(".env") maxIter, _ := strconv.Atoi(getEnv("GATEWAY_MAX_TOOL_ITERATIONS", "10")) timeout, _ := strconv.Atoi(getEnv("GATEWAY_REQUEST_TIMEOUT_SECS", "120")) // Resolve LLM base URL — priority: LLM_BASE_URL > OLLAMA_BASE_URL > default cloud rawLLMURL := getEnvFirst( "LLM_BASE_URL", // preferred new name "OLLAMA_BASE_URL", // legacy alias ) if rawLLMURL == "" { // Default: Ollama Cloud (OpenAI-compatible, requires OLLAMA_API_KEY) rawLLMURL = "https://ollama.com/v1" } // Normalise: strip trailing slash, ensure /v1 suffix for bare Ollama hosts llmBaseURL := normaliseLLMURL(rawLLMURL) // Resolve API key — priority: LLM_API_KEY > OLLAMA_API_KEY llmAPIKey := getEnvFirst("LLM_API_KEY", "OLLAMA_API_KEY") cfg := &Config{ Port: getEnv("GATEWAY_PORT", "18789"), LLMBaseURL: llmBaseURL, LLMAPIKey: llmAPIKey, OllamaBaseURL: llmBaseURL, // backward-compat alias OllamaAPIKey: llmAPIKey, // backward-compat alias DatabaseURL: getEnv("DATABASE_URL", ""), ProjectRoot: getEnv("PROJECT_ROOT", "/home/ubuntu/goclaw-control-center"), DefaultModel: getEnv("DEFAULT_MODEL", "qwen2.5:7b"), MaxToolIterations: maxIter, RequestTimeoutSecs: timeout, } if cfg.LLMAPIKey == "" { log.Println("[Config] WARNING: LLM_API_KEY / OLLAMA_API_KEY is not set — cloud API calls will fail") } if cfg.DatabaseURL == "" { log.Println("[Config] WARNING: DATABASE_URL is not set — agent config will use defaults") } log.Printf("[Config] LLM endpoint: %s", cfg.LLMBaseURL) return cfg } // normaliseLLMURL ensures the URL has a /v1 suffix for bare Ollama hosts. // Examples: // // "http://localhost:11434" → "http://localhost:11434/v1" // "http://localhost:11434/" → "http://localhost:11434/v1" // "https://ollama.com/v1" → "https://ollama.com/v1" (unchanged) // "https://api.openai.com/v1" → unchanged func normaliseLLMURL(raw string) string { u := strings.TrimRight(raw, "/") if !strings.HasSuffix(u, "/v1") { u += "/v1" } return u } // getEnvFirst returns the value of the first non-empty env variable from the list. func getEnvFirst(keys ...string) string { for _, k := range keys { if v := os.Getenv(k); v != "" { return v } } return "" } func getEnv(key, fallback string) string { if v := os.Getenv(key); v != "" { return v } return fallback }