package handoff import "time" type FallbackCounters struct { LanguageFailures int RegionFailures int NoAnswerCount int KBUnavailable int AIProviderErrors int MediaErrors int ToolErrors int StartedAt time.Time } type FallbackConfig struct { MaxLanguageFailures int MaxRegionFailures int MaxNoAnswer int MaxKBUnavailable int MaxAIErrors int MaxMediaErrors int MaxToolErrors int CallTimeout time.Duration } type FallbackDecision struct { ShouldOfferHandoff bool ShouldClose bool ReasonCode HandoffReasonCode MessageKey string Message string } type FallbackManager struct { Config FallbackConfig } func NewFallbackManager(cfg FallbackConfig) *FallbackManager { if cfg.MaxLanguageFailures == 0 { cfg.MaxLanguageFailures = 3 } if cfg.MaxRegionFailures == 0 { cfg.MaxRegionFailures = 3 } if cfg.MaxNoAnswer == 0 { cfg.MaxNoAnswer = 2 } if cfg.MaxKBUnavailable == 0 { cfg.MaxKBUnavailable = 1 } if cfg.MaxAIErrors == 0 { cfg.MaxAIErrors = 1 } if cfg.MaxMediaErrors == 0 { cfg.MaxMediaErrors = 1 } if cfg.MaxToolErrors == 0 { cfg.MaxToolErrors = 2 } if cfg.CallTimeout == 0 { cfg.CallTimeout = 5 * time.Minute } return &FallbackManager{Config: cfg} } func (m *FallbackManager) Evaluate(c FallbackCounters, language string, now time.Time) FallbackDecision { if !c.StartedAt.IsZero() && now.Sub(c.StartedAt) >= m.Config.CallTimeout { return m.decision(true, HandoffReasonTimeout, "fallback.timeout", language) } if c.KBUnavailable >= m.Config.MaxKBUnavailable { return m.decision(false, HandoffReasonKBUnavailable, "fallback.kb_unavailable", language) } if c.NoAnswerCount >= m.Config.MaxNoAnswer { return m.decision(false, HandoffReasonRepeatedNoAnswer, "fallback.no_answer", language) } if c.LanguageFailures >= m.Config.MaxLanguageFailures { return m.decision(false, HandoffReasonLanguageFailures, "fallback.language_failures", language) } if c.RegionFailures >= m.Config.MaxRegionFailures { return m.decision(false, HandoffReasonRegionFailures, "fallback.region_failures", language) } if c.AIProviderErrors >= m.Config.MaxAIErrors { return m.decision(false, HandoffReasonAIProviderError, "fallback.ai_error", language) } if c.MediaErrors >= m.Config.MaxMediaErrors { return m.decision(false, HandoffReasonMediaError, "fallback.media_error", language) } if c.ToolErrors >= m.Config.MaxToolErrors { return m.decision(false, HandoffReasonToolError, "fallback.tool_error", language) } return FallbackDecision{} } func (m *FallbackManager) decision(close bool, reason HandoffReasonCode, key string, language string) FallbackDecision { return FallbackDecision{ShouldOfferHandoff: !close, ShouldClose: close, ReasonCode: reason, MessageKey: key, Message: Message(key, language)} }