package session import ( "fmt" "strings" "github.com/sipeed/picoclaw/pkg/bus" "github.com/sipeed/picoclaw/pkg/routing" ) // Allocation contains the concrete session keys selected for a routed turn. // The current implementation intentionally preserves the legacy session-key // layout while moving key construction out of the router. type Allocation struct { Scope SessionScope SessionKey string SessionAliases []string MainSessionKey string MainAliases []string } // AllocationInput contains the routing result and peer context needed to // derive the session keys for a turn. type AllocationInput struct { AgentID string Context bus.InboundContext SessionPolicy routing.SessionPolicy } // AllocateRouteSession maps a route decision onto a structured scope and the // current opaque session-key format. func AllocateRouteSession(input AllocationInput) Allocation { scope := buildSessionScope(input) legacySessionAliases := buildLegacySessionAliases(input) legacyMainSessionKey := strings.ToLower(BuildLegacyMainAlias(input.AgentID)) return Allocation{ Scope: scope, SessionKey: BuildSessionKey(scope), SessionAliases: legacySessionAliases, MainSessionKey: BuildOpaqueSessionKey(legacyMainSessionKey), MainAliases: []string{legacyMainSessionKey}, } } func buildSessionScope(input AllocationInput) SessionScope { inbound := input.Context scope := SessionScope{ Version: ScopeVersionV1, AgentID: routing.NormalizeAgentID(input.AgentID), Channel: strings.ToLower(strings.TrimSpace(inbound.Channel)), Account: routing.NormalizeAccountID(inbound.Account), } if scope.Channel == "" { scope.Channel = "unknown" } dimensions := make([]string, 0, len(input.SessionPolicy.Dimensions)) values := make(map[string]string, len(input.SessionPolicy.Dimensions)) for _, dimension := range input.SessionPolicy.Dimensions { switch dimension { case "space": if spaceID := strings.TrimSpace(inbound.SpaceID); spaceID != "" { spaceType := strings.ToLower(strings.TrimSpace(inbound.SpaceType)) if spaceType == "" { spaceType = "space" } dimensions = append(dimensions, "space") values["space"] = fmt.Sprintf("%s:%s", spaceType, strings.ToLower(spaceID)) } case "chat": chatID := strings.TrimSpace(inbound.ChatID) if chatID == "" { continue } chatType := strings.ToLower(strings.TrimSpace(inbound.ChatType)) if chatType == "" { chatType = "direct" } dimensions = append(dimensions, "chat") values["chat"] = fmt.Sprintf("%s:%s", chatType, strings.ToLower(chatID)) case "topic": if topicID := strings.TrimSpace(inbound.TopicID); topicID != "" { dimensions = append(dimensions, "topic") values["topic"] = "topic:" + strings.ToLower(topicID) } case "sender": senderID := CanonicalSessionIdentityID( inbound.Channel, inbound.SenderID, input.SessionPolicy.IdentityLinks, ) if senderID == "" { continue } dimensions = append(dimensions, "sender") values["sender"] = senderID } } if len(dimensions) > 0 { scope.Dimensions = dimensions scope.Values = values } return scope } func buildLegacySessionAliases(input AllocationInput) []string { aliases := []string{strings.ToLower(BuildLegacyMainAlias(input.AgentID))} inbound := input.Context if strings.EqualFold(strings.TrimSpace(inbound.ChatType), "direct") { senderID := CanonicalSessionIdentityID( inbound.Channel, inbound.SenderID, input.SessionPolicy.IdentityLinks, ) if senderID == "" { return uniqueAliases(aliases) } aliases = append( aliases, BuildLegacyDirectAliases(input.AgentID, inbound.Channel, inbound.Account, senderID)..., ) return uniqueAliases(aliases) } peerID := strings.TrimSpace(inbound.ChatID) if peerID == "" { return uniqueAliases(aliases) } if topicID := strings.TrimSpace(inbound.TopicID); topicID != "" { peerID = peerID + "/" + topicID } aliases = append(aliases, BuildLegacyPeerAlias( input.AgentID, inbound.Channel, strings.ToLower(strings.TrimSpace(inbound.ChatType)), peerID, )) return uniqueAliases(aliases) } func uniqueAliases(aliases []string) []string { if len(aliases) == 0 { return nil } normalized := make([]string, 0, len(aliases)) seen := make(map[string]struct{}, len(aliases)) for _, alias := range aliases { alias = strings.TrimSpace(strings.ToLower(alias)) if alias == "" { continue } if _, ok := seen[alias]; ok { continue } seen[alias] = struct{}{} normalized = append(normalized, alias) } if len(normalized) == 0 { return nil } return normalized }