Files
ai-operator/internal/handoff/manager.go
T

93 lines
3.3 KiB
Go

package handoff
import (
"context"
"fmt"
"time"
)
type Manager struct {
Config Config
Store *Store
Executor Executor
}
type RequestInput struct {
CallID string
AsteriskChannelID string
State string
Language string
RegionCode string
Route string
ReasonCode HandoffReasonCode
ReasonText string
Summary string
Metadata map[string]string
}
func NewManager(cfg Config, executor Executor) *Manager {
if cfg.Mode == "" {
cfg = DefaultConfig()
}
if executor == nil {
executor = SafeExecutor{}
}
return &Manager{Config: cfg, Store: NewStore(), Executor: executor}
}
func (m *Manager) Request(ctx context.Context, in RequestInput) (HandoffRequest, HandoffResult, error) {
if existing, ok := m.Store.GetByCall(in.CallID); ok {
result := HandoffResult{RequestID: existing.ID, Status: existing.Status, Mode: existing.Mode, MessageKey: "handoff.already_requested", Message: Message("handoff.already_requested", in.Language)}
return existing, result, nil
}
mode := m.Config.Mode
hasTarget := mode == HandoffModeHangupAfterMessage || mode == HandoffModeDisabledStub ||
(mode == HandoffModeARIRedirect && m.Config.TargetEndpoint != "") ||
(mode == HandoffModeDialplanContinue && m.Config.DialplanContext != "" && m.Config.DialplanExtension != "")
decision := Authorize(PolicyContext{State: in.State, Route: in.Route, HandoffEnabled: m.Config.Enabled, HandoffMode: mode, AllowInTestRouteOnly: m.Config.AllowInTestRouteOnly, HasTarget: hasTarget})
now := time.Now().UTC()
req := HandoffRequest{
ID: fmt.Sprintf("handoff-%d", now.UnixNano()),
CallID: in.CallID,
AsteriskChannelID: in.AsteriskChannelID,
Mode: mode,
Status: HandoffStatusRequested,
ReasonCode: in.ReasonCode,
ReasonText: SanitizeText(in.ReasonText, m.Config.MaxSummaryChars),
Summary: SanitizeText(in.Summary, m.Config.MaxSummaryChars),
Language: in.Language,
RegionCode: in.RegionCode,
Route: in.Route,
TargetEndpoint: m.Config.TargetEndpoint,
DialplanContext: m.Config.DialplanContext,
DialplanExtension: m.Config.DialplanExtension,
DialplanPriority: m.Config.DialplanPriority,
CreatedAt: now,
UpdatedAt: now,
Metadata: in.Metadata,
}
if !decision.Allowed {
req.Status = HandoffStatusFailed
result := HandoffResult{RequestID: req.ID, Status: req.Status, Mode: req.Mode, MessageKey: decision.MessageKey, Message: Message(decision.MessageKey, in.Language), Error: decision.ReasonCode}
m.Store.Put(req)
return req, result, fmt.Errorf(decision.ReasonCode)
}
if decision.StubOnly {
req.Mode = HandoffModeDisabledStub
req.Status = HandoffStatusStubbed
result := HandoffResult{RequestID: req.ID, Status: req.Status, Mode: req.Mode, MessageKey: decision.MessageKey, Message: Message(decision.MessageKey, in.Language)}
m.Store.Put(req)
return req, result, nil
}
result, err := m.Executor.Execute(ctx, req)
req.Status = result.Status
req.Error = result.Error
completed := time.Now().UTC()
if result.Status == HandoffStatusCompleted || result.Status == HandoffStatusFailed || result.Status == HandoffStatusStubbed {
req.CompletedAt = &completed
}
req.UpdatedAt = completed
m.Store.Put(req)
return req, result, err
}