// Package subsession manages parallel sub-session providers for dialogue enrichment. package subsession import ( "context" "fmt" "strings" "sync" "time" "git.yeij.top/AskaEth/Cyrene_v2/services/ai-core/model" ) // Provider executes a specific sub-session task in parallel with others. type Provider interface { Name() string Execute(ctx context.Context, input ProviderInput) (*model.SubSessionResult, error) } // ProviderInput holds the context for sub-session execution. type ProviderInput struct { UserMessage string History []model.Message Intent *model.Intent } // Manager orchestrates parallel sub-session execution. type Manager struct { providers []Provider } // NewManager creates a sub-session manager. func NewManager(providers ...Provider) *Manager { return &Manager{providers: providers} } // Dispatch runs all providers in parallel and returns results within the timeout. func (m *Manager) Dispatch(ctx context.Context, input ProviderInput, timeout time.Duration) []model.SubSessionResult { ctx, cancel := context.WithTimeout(ctx, timeout) defer cancel() var wg sync.WaitGroup results := make([]model.SubSessionResult, 0, len(m.providers)) var mu sync.Mutex for _, p := range m.providers { wg.Add(1) go func(provider Provider) { defer wg.Done() result, err := provider.Execute(ctx, input) mu.Lock() defer mu.Unlock() if err != nil { results = append(results, model.SubSessionResult{ Provider: provider.Name(), Error: err.Error(), }) } else if result != nil { results = append(results, *result) } }(p) } wg.Wait() return results } // --- Built-in Providers --- // GeneralProvider handles general conversation intent. type GeneralProvider struct{} func (p *GeneralProvider) Name() string { return "general" } func (p *GeneralProvider) Execute(_ context.Context, input ProviderInput) (*model.SubSessionResult, error) { return &model.SubSessionResult{ Provider: "general", Content: fmt.Sprintf("Intent: %s (confidence: %.2f)", input.Intent.Type, input.Intent.Confidence), }, nil } // MemoryProvider retrieves relevant memories (placeholder — wired to Memory Service later). type MemoryProvider struct{} func (p *MemoryProvider) Name() string { return "memory" } func (p *MemoryProvider) Execute(_ context.Context, _ ProviderInput) (*model.SubSessionResult, error) { return &model.SubSessionResult{Provider: "memory", Content: "no relevant memories"}, nil } // IoTProvider executes IoT device queries and commands. type IoTProvider struct { // GetDeviceState is injected by the main app to query IoT devices. GetDeviceState func(ctx context.Context) (string, error) // ControlDevice is injected to execute IoT commands. ControlDevice func(ctx context.Context, device, action string) (string, error) } func (p *IoTProvider) Name() string { return "iot" } func (p *IoTProvider) Execute(ctx context.Context, input ProviderInput) (*model.SubSessionResult, error) { if input.Intent == nil || (input.Intent.Type != model.IntentIoTControl && input.Intent.Type != model.IntentIoTQuery) { return &model.SubSessionResult{Provider: "iot", Content: ""}, nil } if p.GetDeviceState != nil { state, err := p.GetDeviceState(ctx) if err == nil { return &model.SubSessionResult{Provider: "iot", Content: state}, nil } } return &model.SubSessionResult{Provider: "iot", Content: "iot devices available"}, nil } // ReviewProvider parses LLM output into action/chat segments. type ReviewProvider struct{} func (p *ReviewProvider) Name() string { return "review" } func (p *ReviewProvider) Execute(_ context.Context, _ ProviderInput) (*model.SubSessionResult, error) { return &model.SubSessionResult{Provider: "review", Content: ""}, nil } // ParseReviewMessages splits a message with action markers into segments. // Input: "(歪着头看) 客厅灯已经开啦♪" // Output: [{type:"action", content:"歪着头看"}, {type:"chat", content:"客厅灯已经开啦♪"}] func ParseReviewMessages(content string) []model.ReviewSegment { var segments []model.ReviewSegment var current strings.Builder inAction := false runes := []rune(content) for i := 0; i < len(runes); i++ { r := runes[i] // Chinese bracket detection if r == '(' || r == '(' { if current.Len() > 0 && !inAction { segments = append(segments, model.ReviewSegment{Type: "chat", Content: current.String()}) current.Reset() } inAction = true continue } if r == ')' || r == ')' { if inAction && current.Len() > 0 { segments = append(segments, model.ReviewSegment{Type: "action", Content: current.String()}) current.Reset() } inAction = false continue } current.WriteRune(r) } if current.Len() > 0 { segType := "chat" if inAction { segType = "action" } segments = append(segments, model.ReviewSegment{Type: segType, Content: current.String()}) } return segments }