// Package chatbot holds the FastAPI agent service client configuration. // // The struct, env tags, and validation rules are sourced directly from // plans/chatbot_plan_codex.v10.md §8 (Configuration). Required string // fields use the standard `required,notEmpty` env tag pair so the // caarlos0/env loader rejects empty values before Validate runs; the // numeric fields default to plan-mandated values and are checked by // Validate. package chatbot import "fmt" // StateMaxBytes is the per-session state cap enforced when persisting // updated_session_state from a FastAPI response. Plan §8 derived value. // 64 KiB matches the plan literal `64 * 1024`. const StateMaxBytes = 64 * 1024 // MaxTurnCharsCeiling is the largest legal value for MaxTurnChars. // It mirrors the OpenAPI schema constraint on the prompt body at // serviceAPIs/queryAPI.yaml (BotTurnAddRequest.prompt.maxLength=65536). // Setting CHATBOT_MAX_TURN_CHARS above this ceiling would create a // silent inconsistency: the schema validator would reject oversized // prompts before the service-layer length guard ran, leaving operators // with no signal that the configured cap is unreachable. Any change to // this value must be made in lockstep with the OpenAPI spec. const MaxTurnCharsCeiling = 65536 // ChatbotConfig is the queryAPI-side configuration block for the FastAPI // chatbot agent service. Fields, env-var names, defaults, and the // notEmpty/required tags are dictated verbatim by plan §8. type ChatbotConfig struct { // ServiceURL is the base URL of the FastAPI agent service. No // envDefault: production must not have a silent default. ServiceURL string `env:"CHATBOT_SERVICE_URL,required,notEmpty"` // APIKey is the bearer credential the backend presents on each call. // No envDefault for the same reason as ServiceURL. APIKey string `env:"CHATBOT_API_KEY,required,notEmpty"` // RequestTimeoutSeconds is the per-call wall-clock budget for the // FastAPI client. Default 60s per plan §8. RequestTimeoutSeconds int `env:"CHATBOT_REQUEST_TIMEOUT_SECONDS" envDefault:"60"` // MaxTurnChars caps prompt and completion length in runes. Default // 2000 per plan §8. MaxTurnChars int `env:"CHATBOT_MAX_TURN_CHARS" envDefault:"2000"` // RecentTurns is the conversation-history length sent to FastAPI on // each request and the upper bound on session-list preview turns. // Plan §8 mandates the range [1, 5]; default is 5. RecentTurns int `env:"CHATBOT_RECENT_TURNS" envDefault:"5"` } // Validate enforces the numeric range constraints from plan §8. // ServiceURL/APIKey emptiness is enforced by the env loader's notEmpty // tag; duplicating it here would mask loader-time rejection. The error // message names the offending field so test-eng's table assertions match // on substring. func (c ChatbotConfig) Validate() error { if c.RequestTimeoutSeconds < 1 { return fmt.Errorf("chatbot config: RequestTimeoutSeconds must be >= 1, got %d", c.RequestTimeoutSeconds) } if c.MaxTurnChars < 1 { return fmt.Errorf("chatbot config: MaxTurnChars must be >= 1, got %d", c.MaxTurnChars) } if c.MaxTurnChars > MaxTurnCharsCeiling { return fmt.Errorf( "chatbot config: MaxTurnChars must be <= %d (OpenAPI prompt.maxLength), got %d", MaxTurnCharsCeiling, c.MaxTurnChars) } if c.RecentTurns < 1 || c.RecentTurns > 5 { return fmt.Errorf("chatbot config: RecentTurns must be in [1, 5], got %d", c.RecentTurns) } return nil } // InflightGraceSeconds is the grace window used by AddTurn to abandon a // stuck in_flight turn. Plan §8 sets it to twice the request timeout so a // single retry cycle that exhausts the timeout is not abandoned mid-flight. func (c ChatbotConfig) InflightGraceSeconds() int { return 2 * c.RequestTimeoutSeconds }