package auth import ( "container/list" "strings" "sync" "time" internalconfig "github.com/router-for-me/CLIProxyAPI/v7/internal/config" cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" cliproxysession "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/session" ) const ( defaultHomeSessionAliasTTL = time.Hour homeSessionAliasCleanupOps = 256 homeSessionAliasSoftLimit = 4096 ) type homeSessionAliasEntry struct { canonical string expiresAt time.Time aliases []string } // homeSessionAliasCache reconciles multiple client identifiers for one Home // session without changing Home's single-session-ID protocol. type homeSessionAliasCache struct { mu sync.Mutex entries map[string]homeSessionAliasEntry groups map[string]homeSessionAliasEntry evictionOrder *list.List evictionElements map[string]*list.Element ops uint64 } func (c *homeSessionAliasCache) canonical(primary, fallback string, ttl time.Duration, now time.Time) string { primary = strings.TrimSpace(primary) fallback = strings.TrimSpace(fallback) if primary == "" { return "" } if ttl <= 0 { ttl = defaultHomeSessionAliasTTL } c.mu.Lock() defer c.mu.Unlock() c.ensureInitializedLocked() c.ops++ if c.ops%homeSessionAliasCleanupOps == 0 { c.cleanupLocked(now) } canonical := primary aliases := mergeSessionAliases(nil, primary, fallback) previousGroups := make(map[string]homeSessionAliasEntry, 2) remember := func(entry homeSessionAliasEntry) { previousGroups[entry.canonical] = entry } primaryFound := false canonicalFromLiveAlias := false if existing, ok := c.entryLocked(primary, now); ok { primaryFound = true canonicalFromLiveAlias = true canonical = existing.canonical remember(existing) aliases = mergeSessionAliases(aliases, existing.aliases...) } if fallback != "" && fallback != primary { if existing, ok := c.entryLocked(fallback, now); ok { canonicalFromLiveAlias = true if !primaryFound { canonical = existing.canonical } remember(existing) aliases = mergeSessionAliases(aliases, existing.aliases...) } } if canonicalFromLiveAlias { if existing, ok := c.groupLocked(canonical, now); ok { remember(existing) aliases = mergeSessionAliases(aliases, existing.aliases...) } } if !canonicalFromLiveAlias { if _, ok := c.groupLocked(canonical, now); ok { return canonical } } aliases = compactHomeSessionAliases(mergeSessionAliases(aliases, canonical)) for _, previous := range previousGroups { c.removeGroupLocked(previous) } c.setGroupLocked(homeSessionAliasEntry{ canonical: canonical, expiresAt: now.Add(ttl), aliases: aliases, }) c.enforceLimitLocked(homeSessionAliasSoftLimit) return canonical } func (c *homeSessionAliasCache) ensureInitializedLocked() { if c.entries == nil { c.entries = make(map[string]homeSessionAliasEntry) } if c.groups == nil { c.groups = make(map[string]homeSessionAliasEntry) } if c.evictionOrder == nil { c.evictionOrder = list.New() } if c.evictionElements == nil { c.evictionElements = make(map[string]*list.Element) } } func (c *homeSessionAliasCache) entryLocked(alias string, now time.Time) (homeSessionAliasEntry, bool) { entry, ok := c.entries[alias] if !ok { return homeSessionAliasEntry{}, false } if now.Before(entry.expiresAt) { return entry, true } if group, exists := c.groups[entry.canonical]; exists && sameHomeSessionAliasGroup(group, entry) { c.removeGroupLocked(group) } else { delete(c.entries, alias) } return homeSessionAliasEntry{}, false } func (c *homeSessionAliasCache) groupLocked(canonical string, now time.Time) (homeSessionAliasEntry, bool) { entry, ok := c.groups[canonical] if !ok { return homeSessionAliasEntry{}, false } if now.Before(entry.expiresAt) { return entry, true } c.removeGroupLocked(entry) return homeSessionAliasEntry{}, false } func (c *homeSessionAliasCache) setGroupLocked(entry homeSessionAliasEntry) { if existing, ok := c.groups[entry.canonical]; ok { c.removeGroupLocked(existing) } entry.aliases = append([]string(nil), entry.aliases...) c.groups[entry.canonical] = entry for _, alias := range entry.aliases { c.entries[alias] = entry } c.evictionElements[entry.canonical] = c.evictionOrder.PushBack(entry.canonical) } func (c *homeSessionAliasCache) removeGroupLocked(entry homeSessionAliasEntry) { current, ok := c.groups[entry.canonical] if !ok || !sameHomeSessionAliasGroup(current, entry) { return } for _, alias := range current.aliases { mapped, exists := c.entries[alias] if exists && sameHomeSessionAliasGroup(mapped, current) { delete(c.entries, alias) } } delete(c.groups, current.canonical) if element, exists := c.evictionElements[current.canonical]; exists { c.evictionOrder.Remove(element) delete(c.evictionElements, current.canonical) } } func sameHomeSessionAliasGroup(left, right homeSessionAliasEntry) bool { return left.canonical == right.canonical && left.expiresAt.Equal(right.expiresAt) && equalSessionAliases(left.aliases, right.aliases) } func (c *homeSessionAliasCache) enforceLimitLocked(limit int) { if limit <= 0 { return } for len(c.entries) > limit { oldest := c.evictionOrder.Front() if oldest == nil { return } canonical, _ := oldest.Value.(string) entry, ok := c.groups[canonical] if !ok { c.evictionOrder.Remove(oldest) delete(c.evictionElements, canonical) continue } c.removeGroupLocked(entry) } } func (c *homeSessionAliasCache) cleanupLocked(now time.Time) { for _, entry := range c.groups { if !now.Before(entry.expiresAt) { c.removeGroupLocked(entry) } } } func (c *homeSessionAliasCache) clear() { c.mu.Lock() c.entries = nil c.groups = nil c.evictionOrder = nil c.evictionElements = nil c.ops = 0 c.mu.Unlock() } func homeSessionAliasTTL(cfg *internalconfig.Config) time.Duration { if cfg == nil { return defaultHomeSessionAliasTTL } raw := strings.TrimSpace(cfg.Routing.SessionAffinityTTL) if raw == "" { return defaultHomeSessionAliasTTL } parsed, errParse := time.ParseDuration(raw) if errParse != nil || parsed <= 0 { return defaultHomeSessionAliasTTL } return parsed } func isHierarchyParent(primary, fallback string) bool { if fallback == "" || primary == "" || primary == fallback { return false } if strings.Contains(primary, ":agent:") { return true } idx1 := strings.Index(primary, ":") idx2 := strings.Index(fallback, ":") if idx1 > 0 && idx2 > 0 && primary[:idx1] == fallback[:idx2] { return true } return false } func (m *Manager) homeDispatchSessionIDs(opts cliproxyexecutor.Options) (string, string) { primary, fallback := extractExplicitSessionIDs(opts.Headers, opts.OriginalRequest, opts.Metadata) hasAuthoritativeInput := primary != "" if primary == "" { if canonicalID, ok := opts.Metadata[cliproxyexecutor.CanonicalSessionIDMetadataKey].(string); ok && strings.TrimSpace(canonicalID) != "" { primary = strings.TrimSpace(canonicalID) } else if lcpID, ok := opts.Metadata[cliproxyexecutor.LCPAffinitySessionIDMetadataKey].(string); ok && strings.TrimSpace(lcpID) != "" { primary = strings.TrimSpace(lcpID) } else { primary, fallback = extractSessionIDs(opts.Headers, opts.OriginalRequest, opts.Metadata) hasAuthoritativeInput = primary != "" } } if primary == "" || m == nil { return primary, "" } var parentSessionID string var aliasFallback string if fallback != "" && fallback != primary { if isHierarchyParent(primary, fallback) { parentSessionID = fallback } else { aliasFallback = fallback } } if !hasAuthoritativeInput && parentSessionID == "" && opts.Metadata != nil { if metaParent, ok := opts.Metadata[cliproxyexecutor.ParentSessionIDMetadataKey].(string); ok && strings.TrimSpace(metaParent) != "" { parentSessionID = strings.TrimSpace(metaParent) } } cfg, _ := m.runtimeConfig.Load().(*internalconfig.Config) ttl := homeSessionAliasTTL(cfg) now := time.Now() canonical := m.homeSessionAliases.canonical(primary, aliasFallback, ttl, now) if parentSessionID != "" { if parentSessionID == canonical || parentSessionID == primary || (aliasFallback != "" && parentSessionID == aliasFallback) { parentSessionID = "" } else { parentSessionID = m.homeSessionAliases.canonical(parentSessionID, "", ttl, now) if parentSessionID == canonical { parentSessionID = "" } } } canonical = cliproxysession.BoundSessionIdentity(canonical) if parentSessionID != "" { parentSessionID = cliproxysession.BoundSessionIdentity(parentSessionID) } if canonical == parentSessionID { parentSessionID = "" } return canonical, parentSessionID } func (m *Manager) homeDispatchSessionID(opts cliproxyexecutor.Options) string { sessionID, _ := m.homeDispatchSessionIDs(opts) return sessionID }