summaryrefslogtreecommitdiff
path: root/internal/config/config.go
diff options
context:
space:
mode:
authorChia <Chia@93.nz>2026-08-05 00:26:25 +1200
committerChia <Chia@93.nz>2026-08-05 00:33:31 +1200
commit1a3d7f9a8a181df48f0e911cbe17a3fad3ab9ac9 (patch)
tree8c92e1e7326fc67ed077a0a878697f1be14b43da /internal/config/config.go
parent5b651488b081b65fda8a323f228e139adb79a35d (diff)
add some scriptsmain
Diffstat (limited to '')
-rw-r--r--internal/config/config.go89
1 files changed, 86 insertions, 3 deletions
diff --git a/internal/config/config.go b/internal/config/config.go
index 5e1e518..8c21c6c 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -21,6 +21,7 @@ type Config struct {
UpstreamHTTP UpstreamHTTPConfig `json:"upstream_http"`
Providers []ProviderConfig `json:"providers"`
Models []ModelConfig `json:"models"`
+ Billing BillingConfig `json:"billing"`
Observability ObservabilityConfig `json:"observability"`
}
@@ -74,9 +75,13 @@ type ProviderConfig struct {
}
type ModelConfig struct {
- ID string `json:"id"`
- OwnedBy string `json:"owned_by"`
- Routes []RouteConfig `json:"routes"`
+ ID string `json:"id"`
+ OwnedBy string `json:"owned_by"`
+ InputPriceMicrosPerMillion int64 `json:"input_price_micros_per_million"`
+ OutputPriceMicrosPerMillion int64 `json:"output_price_micros_per_million"`
+ CacheReadPriceMicrosPerMillion int64 `json:"cache_read_price_micros_per_million"`
+ CacheWritePriceMicrosPerMillion int64 `json:"cache_write_price_micros_per_million"`
+ Routes []RouteConfig `json:"routes"`
}
type RouteConfig struct {
@@ -91,6 +96,25 @@ type ObservabilityConfig struct {
ExposeMetrics bool `json:"expose_metrics"`
}
+type BillingConfig struct {
+ Enabled bool `json:"enabled"`
+ Currency string `json:"currency"`
+ DefaultMaxOutputTokens int64 `json:"default_max_output_tokens"`
+ MinTopUpMinor int64 `json:"min_top_up_minor"`
+ MaxTopUpMinor int64 `json:"max_top_up_minor"`
+ Stripe StripeConfig `json:"stripe"`
+}
+
+type StripeConfig struct {
+ Enabled bool `json:"enabled"`
+ APIKeyEnv string `json:"api_key_env"`
+ WebhookSecretEnv string `json:"webhook_secret_env"`
+ SuccessURL string `json:"success_url"`
+ CancelURL string `json:"cancel_url"`
+ APIKey string `json:"-"`
+ WebhookSecret string `json:"-"`
+}
+
func Load(path string) (Config, error) {
f, err := os.Open(path)
if err != nil {
@@ -179,6 +203,24 @@ func applyDefaults(cfg *Config) {
if cfg.Observability.UsageBuffer == 0 {
cfg.Observability.UsageBuffer = 8192
}
+ if cfg.Billing.Currency == "" {
+ cfg.Billing.Currency = "usd"
+ }
+ if cfg.Billing.DefaultMaxOutputTokens == 0 {
+ cfg.Billing.DefaultMaxOutputTokens = 4096
+ }
+ if cfg.Billing.MaxTopUpMinor == 0 {
+ cfg.Billing.MaxTopUpMinor = 1000000
+ }
+ if cfg.Billing.MinTopUpMinor == 0 {
+ cfg.Billing.MinTopUpMinor = 500
+ }
+ if cfg.Billing.Stripe.APIKeyEnv == "" {
+ cfg.Billing.Stripe.APIKeyEnv = "AIGW_STRIPE_API_KEY"
+ }
+ if cfg.Billing.Stripe.WebhookSecretEnv == "" {
+ cfg.Billing.Stripe.WebhookSecretEnv = "AIGW_STRIPE_WEBHOOK_SECRET"
+ }
for i := range cfg.Models {
for j := range cfg.Models[i].Routes {
if cfg.Models[i].Routes[j].Weight == 0 {
@@ -197,6 +239,10 @@ func resolveSecrets(cfg *Config) error {
if cfg.Admin.Enabled {
cfg.Admin.Token = os.Getenv(cfg.Admin.TokenEnv)
}
+ if cfg.Billing.Enabled && cfg.Billing.Stripe.Enabled {
+ cfg.Billing.Stripe.APIKey = os.Getenv(cfg.Billing.Stripe.APIKeyEnv)
+ cfg.Billing.Stripe.WebhookSecret = os.Getenv(cfg.Billing.Stripe.WebhookSecretEnv)
+ }
for i := range cfg.Providers {
provider := &cfg.Providers[i]
if provider.APIKeyEnv == "" {
@@ -240,6 +286,40 @@ func Validate(cfg Config) error {
return errors.New("admin.base_path must start with / and cannot be /")
}
}
+ if cfg.Billing.Enabled {
+ if !cfg.ControlPlane.Enabled {
+ return errors.New("billing requires control_plane.enabled")
+ }
+ if cfg.Auth.AllowAnonymous {
+ return errors.New("billing cannot be enabled with auth.allow_anonymous")
+ }
+ if len(cfg.Billing.Currency) != 3 || strings.ToLower(cfg.Billing.Currency) != cfg.Billing.Currency {
+ return errors.New("billing.currency must be a lowercase ISO 4217 code")
+ }
+ if cfg.Billing.DefaultMaxOutputTokens < 1 {
+ return errors.New("billing.default_max_output_tokens must be positive")
+ }
+ if cfg.Billing.MinTopUpMinor < 1 || cfg.Billing.MaxTopUpMinor < cfg.Billing.MinTopUpMinor {
+ return errors.New("billing top-up bounds are invalid")
+ }
+ if cfg.Billing.Stripe.Enabled {
+ if cfg.Billing.Stripe.APIKey == "" {
+ return fmt.Errorf("billing.stripe: environment variable %s is empty", cfg.Billing.Stripe.APIKeyEnv)
+ }
+ if cfg.Billing.Stripe.WebhookSecret == "" {
+ return fmt.Errorf("billing.stripe: environment variable %s is empty", cfg.Billing.Stripe.WebhookSecretEnv)
+ }
+ if strings.TrimSpace(cfg.Billing.Stripe.SuccessURL) == "" || strings.TrimSpace(cfg.Billing.Stripe.CancelURL) == "" {
+ return errors.New("billing.stripe.success_url and cancel_url are required")
+ }
+ for name, value := range map[string]string{"success_url": cfg.Billing.Stripe.SuccessURL, "cancel_url": cfg.Billing.Stripe.CancelURL} {
+ parsed, err := url.Parse(value)
+ if err != nil || parsed.Host == "" || (parsed.Scheme != "http" && parsed.Scheme != "https") {
+ return fmt.Errorf("billing.stripe.%s must be an absolute http(s) URL", name)
+ }
+ }
+ }
+ }
providers := make(map[string]ProviderConfig, len(cfg.Providers))
for _, provider := range cfg.Providers {
@@ -274,6 +354,9 @@ func Validate(cfg Config) error {
return fmt.Errorf("duplicate model id %q", model.ID)
}
models[model.ID] = struct{}{}
+ if model.InputPriceMicrosPerMillion < 0 || model.OutputPriceMicrosPerMillion < 0 || model.CacheReadPriceMicrosPerMillion < 0 || model.CacheWritePriceMicrosPerMillion < 0 {
+ return fmt.Errorf("model %q: prices cannot be negative", model.ID)
+ }
if len(model.Routes) == 0 {
return fmt.Errorf("model %q: at least one route is required", model.ID)
}