Files
hyperguild/internal/config/routing.go
Mathias 72be87b4e7
All checks were successful
CI / Lint / Test / Vet (push) Successful in 13s
CI / Mirror to GitHub (push) Successful in 3s
chore(routing): flip LITELLM_BASE_URL default to https://llm-api.d-ma.be
Follow-up to infra#70. LiteLLM moved off piguard into k3s and the
public llm-api.d-ma.be hostname now upstreams to koala:30401. The
piguard:4000 default in the source bit-rots — works today because
piguard:4000 is still alive during the 7-day soak, breaks the moment
the compose comes down.

Pointing the default at the public hostname survives the cutover
without needing a follow-up. Production deploys via k3s already
override via env (in-cluster Service DNS) so this only affects local
dev shells without LITELLM_BASE_URL set.

- internal/config/routing.go: comment + envOr fallback
- internal/config/routing_test.go: expected value in defaults test
- scripts/smoke-routing.sh: shell default

task check: clean (tests + vet + govulncheck).
2026-05-24 15:06:23 +02:00

102 lines
3.6 KiB
Go

package config
import (
"fmt"
"os"
"strconv"
)
// RoutingConfig holds the runtime configuration for the routing pod.
// Separate from Config because the routing pod's surface differs from the supervisor's.
type RoutingConfig struct {
Port string // ROUTING_PORT, default 3210
MCPAuthToken string // ROUTING_MCP_TOKEN, optional bearer token
LiteLLMBaseURL string // LITELLM_BASE_URL, default https://llm-api.d-ma.be
LiteLLMAPIKey string // LITELLM_API_KEY
BrainURL string // BRAIN_URL, default http://ingestion.supervisor:3300
FastModel string // HYPERGUILD_FAST_MODEL, default koala/qwen35-9b-fast
ThinkingModel string // HYPERGUILD_THINKING_MODEL, default iguana/gemma4-26b
// RouteLocalFloor and RouteLocalCeil intentionally invert the usual
// floor < ceil mathematical convention: Floor (default 0.90) is the
// UPPER boundary — at/above it, always route local; Ceil (default 0.70)
// is the LOWER boundary — below it, always route Claude. The band in
// between is the 50/50 sample zone. The naming follows the spec's policy
// vocabulary; see internal/routing/policy.go for the consumer.
RouteLocalFloor float64 // HYPERGUILD_ROUTE_LOCAL_FLOOR, default 0.90
RouteLocalCeil float64 // HYPERGUILD_ROUTE_LOCAL_CEIL, default 0.70
PassRateTTLSeconds int // HYPERGUILD_PASS_RATE_TTL_SECONDS, default 60
// project_create configuration. Empty GiteaMCPURL disables the
// project_create tool registration so the routing pod still starts
// in environments where it's not wired up.
GiteaMCPURL string // GITEA_MCP_URL, e.g. http://koala:30340/mcp
GiteaMCPToken string // GITEA_MCP_TOKEN, bearer for gitea-mcp
GiteaOwner string // GITEA_OWNER, default mathias
GitHubOwner string // GITHUB_OWNER, default mathiasb
InfraRepo string // INFRA_REPO, default infra
GitHubPAT string // GITHUB_PAT, repo scope; never logged
}
func LoadRouting() (RoutingConfig, error) {
cfg := RoutingConfig{
Port: envOr("ROUTING_PORT", "3210"),
MCPAuthToken: os.Getenv("ROUTING_MCP_TOKEN"),
LiteLLMBaseURL: envOr("LITELLM_BASE_URL", "https://llm-api.d-ma.be"),
LiteLLMAPIKey: os.Getenv("LITELLM_API_KEY"),
BrainURL: envOr("BRAIN_URL", "http://ingestion.supervisor:3300"),
FastModel: envOr("HYPERGUILD_FAST_MODEL", "koala/qwen35-9b-fast"),
ThinkingModel: envOr("HYPERGUILD_THINKING_MODEL", "iguana/gemma4-26b"),
}
floor, err := parseFloatEnv("HYPERGUILD_ROUTE_LOCAL_FLOOR", 0.90)
if err != nil {
return RoutingConfig{}, err
}
cfg.RouteLocalFloor = floor
ceil, err := parseFloatEnv("HYPERGUILD_ROUTE_LOCAL_CEIL", 0.70)
if err != nil {
return RoutingConfig{}, err
}
cfg.RouteLocalCeil = ceil
ttl, err := parseIntEnv("HYPERGUILD_PASS_RATE_TTL_SECONDS", 60)
if err != nil {
return RoutingConfig{}, err
}
cfg.PassRateTTLSeconds = ttl
cfg.GiteaMCPURL = os.Getenv("GITEA_MCP_URL")
cfg.GiteaMCPToken = os.Getenv("GITEA_MCP_TOKEN")
cfg.GiteaOwner = envOr("GITEA_OWNER", "mathias")
cfg.GitHubOwner = envOr("GITHUB_OWNER", "mathiasb")
cfg.InfraRepo = envOr("INFRA_REPO", "infra")
cfg.GitHubPAT = os.Getenv("GITHUB_PAT")
return cfg, nil
}
func parseFloatEnv(key string, def float64) (float64, error) {
v := os.Getenv(key)
if v == "" {
return def, nil
}
f, err := strconv.ParseFloat(v, 64)
if err != nil {
return 0, fmt.Errorf("config: %s: %w", key, err)
}
return f, nil
}
func parseIntEnv(key string, def int) (int, error) {
v := os.Getenv(key)
if v == "" {
return def, nil
}
n, err := strconv.Atoi(v)
if err != nil {
return 0, fmt.Errorf("config: %s: %w", key, err)
}
return n, nil
}