feat: add cost-safe AI growth configuration

This commit is contained in:
2026-08-21 00:25:59 +03:30
parent 1a00e897ca
commit 6df21d4fd0
2 changed files with 93 additions and 11 deletions

View File

@@ -8,21 +8,32 @@ import (
)
type Config struct {
HTTPAddr string
DatabaseURL string
RedisAddr string
RedisPassword string
ImportPath string
DemoUsername string
DemoPassword string
SessionSecret string
CursorSecret string
FrontendOrigin string
CookieSecure bool
AppEnv string
HTTPAddr string
DatabaseURL string
RedisAddr string
RedisPassword string
ImportPath string
DemoUsername string
DemoPassword string
SessionSecret string
CursorSecret string
FrontendOrigin string
CookieSecure bool
AIGrowthEnabled bool
AvalAIAPIKey string
AvalAIBaseURL string
AvalAIModel string
AIDailyRequestBudget int64
AIDailyTokenBudget int64
AIMaxConcurrent int
AICreatePerMinute int64
AIUncachedScopesPerDay int64
}
func Load() (Config, error) {
cfg := Config{
AppEnv: value("APP_ENV", "development"),
HTTPAddr: value("HTTP_ADDR", "127.0.0.1:8080"),
DatabaseURL: os.Getenv("DATABASE_URL"),
RedisAddr: value("REDIS_ADDR", "localhost:6379"),
@@ -33,6 +44,9 @@ func Load() (Config, error) {
SessionSecret: os.Getenv("SESSION_SECRET"),
CursorSecret: os.Getenv("CURSOR_SECRET"),
FrontendOrigin: value("FRONTEND_ORIGIN", "http://localhost:5173"),
AvalAIAPIKey: os.Getenv("AVALAI_API_KEY"),
AvalAIBaseURL: value("AVALAI_BASE_URL", "https://api.avalai.ir"),
AvalAIModel: value("AVALAI_MODEL", "gpt-5-mini-2025-08-07"),
}
var err error
@@ -40,6 +54,31 @@ func Load() (Config, error) {
if err != nil {
return Config{}, fmt.Errorf("parsing cookie secure: %w", err)
}
cfg.AIGrowthEnabled, err = strconv.ParseBool(value("AI_GROWTH_ENABLED", "false"))
if err != nil {
return Config{}, fmt.Errorf("parsing AI growth enabled: %w", err)
}
cfg.AIDailyRequestBudget, err = parseInt64("AI_DAILY_REQUEST_BUDGET", 0)
if err != nil {
return Config{}, err
}
cfg.AIDailyTokenBudget, err = parseInt64("AI_DAILY_TOKEN_BUDGET", 0)
if err != nil {
return Config{}, err
}
maxConcurrent, err := parseInt64("AI_MAX_CONCURRENT", 4)
if err != nil {
return Config{}, err
}
cfg.AIMaxConcurrent = int(maxConcurrent)
cfg.AICreatePerMinute, err = parseInt64("AI_CREATE_PER_MINUTE", 10)
if err != nil {
return Config{}, err
}
cfg.AIUncachedScopesPerDay, err = parseInt64("AI_UNCACHED_SCOPES_PER_DAY", 5)
if err != nil {
return Config{}, err
}
if cfg.DatabaseURL == "" {
return Config{}, errors.New("config: DATABASE_URL is required")
}
@@ -56,6 +95,17 @@ func (c Config) ValidateAPI() error {
if c.CursorSecret == c.SessionSecret {
return errors.New("config: CURSOR_SECRET must differ from SESSION_SECRET")
}
if c.AIMaxConcurrent < 1 || c.AICreatePerMinute < 1 || c.AIUncachedScopesPerDay < 1 {
return errors.New("config: AI limits must be positive")
}
if c.AppEnv == "production" && c.AIGrowthEnabled {
if c.AvalAIAPIKey == "" || c.AvalAIModel == "" {
return errors.New("config: enabled production AI requires AVALAI_API_KEY and AVALAI_MODEL")
}
if c.AIDailyRequestBudget < 1 || c.AIDailyTokenBudget < 1 {
return errors.New("config: enabled production AI requires positive daily budgets")
}
}
return nil
}
@@ -72,3 +122,12 @@ func value(name, fallback string) string {
}
return fallback
}
func parseInt64(name string, fallback int64) (int64, error) {
raw := value(name, strconv.FormatInt(fallback, 10))
parsed, err := strconv.ParseInt(raw, 10, 64)
if err != nil || parsed < 0 {
return 0, fmt.Errorf("config: %s must be a non-negative integer", name)
}
return parsed, nil
}