Something went wrong. Try again.
sloproxy
Something went wrong. Try again.
26 kB · 771 lines
Go
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772// Package session derives stable conversation identities and extracts hierarchical session relationships.package session
import ( "crypto/sha256" "encoding/hex" "net/http" "strings" "unicode/utf8"
"github.com/router-for-me/CLIProxyAPI/v7/internal/util" cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" "github.com/tidwall/gjson")
// SessionInfo encapsulates incoming request details needed for session affinity and upstream reporting.type SessionInfo struct { SessionID string `json:"session_id"` ParentSessionID string `json:"parent_session_id,omitempty"` AgentName string `json:"agent_name,omitempty"` ClientType string `json:"client_type,omitempty"` CallerScope string `json:"caller_scope,omitempty"` AuthID string `json:"auth_id,omitempty"` Provider string `json:"provider,omitempty"` Model string `json:"model,omitempty"` IsFork bool `json:"is_fork,omitempty"` IsSubagent bool `json:"is_subagent,omitempty"` Metadata map[string]any `json:"metadata,omitempty"`}
// SessionTreeInfo is an alias for SessionInfo for backward compatibility.type SessionTreeInfo = SessionInfo
// ExtractSessionInfo extracts session hierarchy and client identification from request attributes.// Priority matches selector.go:// 1. X-Claude-Code-Session-Id// 2. Claude Code metadata.user_id session// 3. Session-Id / Session_id (Codex and compatible clients)// 4. X-Http-Session-Id (Antigravity CLI)// 5. X-Session-ID / X-Session-Affinity / X-Slot-Session-Id// 6. X-Conversation-Id / X-Thread-Id / X-Client-Request-Id// 7. Gemini cachedContent// 8. OpenAI thread_id// 9. session_id / sessionId// 10. prompt_cache_key (pck:), conversation.id (conv:), metadata.user_id (user:)// 11. conversation_id / chat_id// 12. execution_session_id metadatafunc ExtractSessionInfo(headers http.Header, payload []byte, metadata map[string]any) (SessionInfo, bool) { var info SessionInfo if metadata != nil { if scope, ok := metadata[cliproxyexecutor.CallerScopeMetadataKey].(string); ok { info.CallerScope = strings.TrimSpace(scope) } }
var root gjson.Result var reqRoot gjson.Result var hasNestedReq bool var parentCandidate string
if len(payload) > 0 { root = util.ParseGJSONBytesNoCopy(payload) reqRoot = root req := root.Get("request") hasNestedReq = req.Exists() && !root.Get("contents").Exists() if hasNestedReq { reqRoot = req } for _, p := range []string{ "parent_session_id", "parentSessionId", "parent_thread_id", "parentThreadId", "forked_from_thread_id", "forked_from_id", "parent_conversation_id", "parentConversationId", "metadata.parent_session_id", "metadata.parent_thread_id", "metadata.forked_from_thread_id", "metadata.forked_from_id", "extra_body.parent_session_id", "extra_body.parent_thread_id", "extra_body.forked_from_thread_id", "extra_body.forked_from_id", } { if val := normalizedSessionCandidate(root.Get(p).String()); val != "" { parentCandidate = val break } if hasNestedReq { if val := normalizedSessionCandidate(reqRoot.Get(p).String()); val != "" { parentCandidate = val break } } } if parentCandidate == "" { parentCandidate = ClaudeMetadataParentSessionID(payload) } }
// 1. Anthropic / Claude Code Headers if sid := sessionHeaderValue(headers, "X-Claude-Code-Session-Id"); sid != "" { info.ClientType = "claude" agentID := sessionHeaderValue(headers, "X-Claude-Code-Agent-Id") if agentID == "" && root.Exists() { agentID = normalizedSessionCandidate(root.Get("metadata.agent_id").String()) if agentID == "" { agentID = normalizedSessionCandidate(root.Get("metadata.subagent_id").String()) } if agentID == "" && hasNestedReq { agentID = normalizedSessionCandidate(reqRoot.Get("metadata.agent_id").String()) if agentID == "" { agentID = normalizedSessionCandidate(reqRoot.Get("metadata.subagent_id").String()) } } } if agentID == "" { _, _, agentID = ClaudeMetadataIdentities(payload) } parentAgentID := sessionHeaderValue(headers, "X-Claude-Code-Parent-Agent-Id") if agentID != "" && agentID != "main" { info.AgentName = agentID info.ParentSessionID = "claude:" + sid if parentAgentID != "" && parentAgentID != "main" && parentAgentID != agentID { info.ParentSessionID = "claude:" + sid + ":agent:" + parentAgentID } else if parentCandidate != "" && parentCandidate != sid { info.ParentSessionID = "claude:" + parentCandidate } info.SessionID = "claude:" + sid + ":agent:" + agentID } else { info.AgentName = "main" info.SessionID = "claude:" + sid if parentCandidate != "" && parentCandidate != sid { info.ParentSessionID = "claude:" + parentCandidate info.AgentName = "subagent" } } return finalizeSessionInfo(info) }
// 2. Claude Code metadata.user_id in payload (outranks generic headers) if len(payload) > 0 { if sid, parentSID, agentID := ClaudeMetadataIdentities(payload); sid != "" { info.ClientType = "claude" if agentID == "" { agentID = sessionHeaderValue(headers, "X-Claude-Code-Agent-Id") } if agentID == "" && root.Exists() { agentID = normalizedSessionCandidate(root.Get("metadata.agent_id").String()) if agentID == "" { agentID = normalizedSessionCandidate(root.Get("metadata.subagent_id").String()) } if agentID == "" && hasNestedReq { agentID = normalizedSessionCandidate(reqRoot.Get("metadata.agent_id").String()) if agentID == "" { agentID = normalizedSessionCandidate(reqRoot.Get("metadata.subagent_id").String()) } } } parentAgentID := sessionHeaderValue(headers, "X-Claude-Code-Parent-Agent-Id") if agentID != "" && agentID != "main" { info.SessionID = "claude:" + sid + ":agent:" + agentID info.ParentSessionID = "claude:" + sid if parentAgentID != "" && parentAgentID != "main" && parentAgentID != agentID { info.ParentSessionID = "claude:" + sid + ":agent:" + parentAgentID } else if parentSID != "" && parentSID != sid { info.ParentSessionID = "claude:" + parentSID } else if parentCandidate != "" && parentCandidate != sid { info.ParentSessionID = "claude:" + parentCandidate } info.AgentName = agentID } else { info.SessionID = "claude:" + sid if parentSID != "" && parentSID != sid { info.ParentSessionID = "claude:" + parentSID info.AgentName = "subagent" } else if parentCandidate != "" && parentCandidate != sid { info.ParentSessionID = "claude:" + parentCandidate info.AgentName = "subagent" } else { info.AgentName = "main" } } return finalizeSessionInfo(info) } }
// 3. OpenAI / Codex CLI Headers sid := sessionHeaderValue(headers, "Session-Id") if sid == "" { sid = sessionHeaderValue(headers, "Session_id") } tid := sessionHeaderValue(headers, "Thread-Id") if tid == "" { tid = sessionHeaderValue(headers, "Thread_id") }
var codexTurnMeta string for k, v := range headers { if strings.EqualFold(k, "X-Codex-Turn-Metadata") && len(v) > 0 { codexTurnMeta = strings.TrimSpace(v[0]) break } } var codexTurnMetaJSON gjson.Result if codexTurnMeta != "" { codexTurnMetaJSON = gjson.Parse(codexTurnMeta) }
if sid == "" && codexTurnMetaJSON.Exists() { sid = normalizedSessionCandidate(codexTurnMetaJSON.Get("session_id").String()) } if tid == "" && codexTurnMetaJSON.Exists() { tid = normalizedSessionCandidate(codexTurnMetaJSON.Get("thread_id").String()) } if tid == "" && sid != "" && root.Exists() { for _, path := range []string{"thread_id", "threadId", "metadata.thread_id"} { if tid = normalizedSessionCandidate(root.Get(path).String()); tid != "" { break } if hasNestedReq { if tid = normalizedSessionCandidate(reqRoot.Get(path).String()); tid != "" { break } } } }
if sid != "" || tid != "" { info.ClientType = "codex" parentThread := sessionHeaderValue(headers, "x-codex-parent-thread-id") if parentThread == "" { parentThread = sessionHeaderValue(headers, "X-Codex-Parent-Thread-Id") } if parentThread == "" && codexTurnMetaJSON.Exists() { parentThread = normalizedSessionCandidate(codexTurnMetaJSON.Get("parent_thread_id").String()) }
forkedFrom := "" if codexTurnMetaJSON.Exists() { forkedFrom = normalizedSessionCandidate(codexTurnMetaJSON.Get("forked_from_thread_id").String()) if forkedFrom == "" { forkedFrom = normalizedSessionCandidate(codexTurnMetaJSON.Get("forked_from_id").String()) } } if forkedFrom == "" && root.Exists() { for _, forkPath := range []string{ "forked_from_thread_id", "forked_from_id", "metadata.forked_from_thread_id", "metadata.forked_from_id", "extra_body.forked_from_thread_id", "extra_body.forked_from_id", } { if forkedFrom = normalizedSessionCandidate(root.Get(forkPath).String()); forkedFrom != "" { break } if hasNestedReq { if forkedFrom = normalizedSessionCandidate(reqRoot.Get(forkPath).String()); forkedFrom != "" { break } } } }
cleanAgentName := "" if codexTurnMetaJSON.Exists() { rawName := codexTurnMetaJSON.Get("agent_name").String() rawName = strings.TrimPrefix(rawName, "/root/") rawName = strings.TrimPrefix(rawName, "/") rawName = strings.TrimSpace(rawName) rawName = normalizedSessionCandidate(rawName) if rawName != "" && rawName != "root" && rawName != "main" { cleanAgentName = rawName } }
subVal := sessionHeaderValue(headers, "X-Openai-Subagent") subagentSignal := subVal != "" && !strings.EqualFold(subVal, "false") && subVal != "0" if codexTurnMetaJSON.Exists() && codexTurnMetaJSON.Get("subagent_kind").String() == "thread_spawn" { subagentSignal = true }
// 1. Fork detection if forkedFrom != "" { forkSessionID := tid if forkSessionID == "" { forkSessionID = sid } if forkSessionID == forkedFrom && sid != "" && sid != forkedFrom { forkSessionID = sid } info.SessionID = "codex:" + forkSessionID info.ParentSessionID = "codex:" + forkedFrom info.AgentName = "main" info.IsFork = true info.IsSubagent = false return finalizeSessionInfo(info) }
// 2. Subagent detection (Multi-Agent v2) if subagentSignal || (tid != "" && sid != "" && tid != sid) || (parentThread != "" && parentThread != tid && parentThread != sid) { childSessionID := tid if childSessionID == "" { childSessionID = sid } parentSID := parentThread if parentSID == "" { parentSID = sid } if cleanAgentName != "" && sid != "" { info.SessionID = "codex:" + sid + ":agent:" + cleanAgentName info.AgentName = cleanAgentName if parentSID != "" { info.ParentSessionID = "codex:" + parentSID } else if parentCandidate != "" && parentCandidate != sid { info.ParentSessionID = "codex:" + parentCandidate } } else { info.SessionID = "codex:" + childSessionID if cleanAgentName != "" { info.AgentName = cleanAgentName } else { info.AgentName = "subagent" } if parentSID != "" && parentSID != childSessionID { info.ParentSessionID = "codex:" + parentSID } else if parentCandidate != "" && parentCandidate != childSessionID { info.ParentSessionID = "codex:" + parentCandidate } } info.IsSubagent = true return finalizeSessionInfo(info) }
// 3. Normal interactive session sessionID := sid if sessionID == "" { sessionID = tid } info.SessionID = "codex:" + sessionID if parentThread != "" && parentThread != sessionID { info.ParentSessionID = "codex:" + parentThread info.AgentName = "subagent" info.IsSubagent = true } else if parentCandidate != "" && parentCandidate != sessionID { info.ParentSessionID = "codex:" + parentCandidate info.AgentName = "subagent" info.IsSubagent = true } else { info.AgentName = "main" } return finalizeSessionInfo(info) }
// 4. Antigravity CLI (agy) Headers if sid := sessionHeaderValue(headers, "X-Http-Session-Id"); sid != "" { info.ClientType = "agy" info.SessionID = "agy:" + sid parentSID := sessionHeaderValue(headers, "X-Parent-Session-ID") if parentSID == "" { parentSID = sessionHeaderValue(headers, "X-Parent-Session-Id") } if parentSID != "" && parentSID != sid { info.ParentSessionID = "agy:" + parentSID info.AgentName = "subagent" } else if parentCandidate != "" && parentCandidate != sid { info.ParentSessionID = "agy:" + parentCandidate info.AgentName = "subagent" } else { info.AgentName = "main" } return finalizeSessionInfo(info) }
// 5. OpenCode / Pi Slot / Generic Headers if sid := sessionHeaderValue(headers, "X-Session-ID"); sid != "" { info.ClientType = "generic" info.SessionID = "header:" + sid parentSID := sessionHeaderValue(headers, "X-Parent-Session-ID") if parentSID == "" { parentSID = sessionHeaderValue(headers, "X-Parent-Session-Id") } if parentSID != "" && parentSID != sid { info.ParentSessionID = "header:" + parentSID info.AgentName = "subagent" } else if parentCandidate != "" && parentCandidate != sid { info.ParentSessionID = "header:" + parentCandidate info.AgentName = "subagent" } else { info.AgentName = "main" } return finalizeSessionInfo(info) } if sid := sessionHeaderValue(headers, "X-Session-Affinity"); sid != "" { info.ClientType = "opencode" info.SessionID = "affinity:" + sid parentAffinity := sessionHeaderValue(headers, "X-Parent-Session-Affinity") if parentAffinity == "" { parentAffinity = sessionHeaderValue(headers, "X-Parent-Session-ID") } if parentAffinity != "" && parentAffinity != sid { info.ParentSessionID = "affinity:" + parentAffinity info.AgentName = "subagent" } else if parentCandidate != "" && parentCandidate != sid { info.ParentSessionID = "affinity:" + parentCandidate info.AgentName = "subagent" } else { info.AgentName = "main" } return finalizeSessionInfo(info) } if sid := sessionHeaderValue(headers, "X-Slot-Session-Id"); sid != "" { info.ClientType = "pi" info.SessionID = "slot:" + sid parentSID := sessionHeaderValue(headers, "X-Parent-Session-ID") if parentSID == "" { parentSID = sessionHeaderValue(headers, "X-Parent-Session-Id") } if parentSID != "" && parentSID != sid { info.ParentSessionID = "slot:" + parentSID info.AgentName = "subagent" } else if parentCandidate != "" && parentCandidate != sid { info.ParentSessionID = "slot:" + parentCandidate info.AgentName = "subagent" } else { info.AgentName = "slot" } return finalizeSessionInfo(info) } if sid := sessionHeaderValue(headers, "X-Conversation-Id"); sid != "" { info.ClientType = "conv" info.SessionID = "conv:" + sid if parentCandidate != "" && parentCandidate != sid { info.ParentSessionID = "conv:" + parentCandidate info.AgentName = "subagent" } else { info.AgentName = "main" } return finalizeSessionInfo(info) } if sid := sessionHeaderValue(headers, "X-Conversation-ID"); sid != "" { info.ClientType = "conv" info.SessionID = "conv:" + sid if parentCandidate != "" && parentCandidate != sid { info.ParentSessionID = "conv:" + parentCandidate info.AgentName = "subagent" } else { info.AgentName = "main" } return finalizeSessionInfo(info) } if sid := sessionHeaderValue(headers, "X-Thread-Id"); sid != "" { info.ClientType = "openai-thread" info.SessionID = "thread:" + sid if parentCandidate != "" && parentCandidate != sid { info.ParentSessionID = "thread:" + parentCandidate info.AgentName = "subagent" } else { info.AgentName = "main" } return finalizeSessionInfo(info) } if sid := sessionHeaderValue(headers, "X-Thread-ID"); sid != "" { info.ClientType = "openai-thread" info.SessionID = "thread:" + sid if parentCandidate != "" && parentCandidate != sid { info.ParentSessionID = "thread:" + parentCandidate info.AgentName = "subagent" } else { info.AgentName = "main" } return finalizeSessionInfo(info) } if sid := sessionHeaderValue(headers, "X-Client-Request-Id"); sid != "" { info.ClientType = "generic" info.SessionID = "clientreq:" + sid info.AgentName = "main" return finalizeSessionInfo(info) }
// 6. Payload inspection if len(payload) > 0 && root.Exists() { // Gemini context caching for _, cachePath := range []string{"cachedContent", "cached_content"} { cacheID := normalizedSessionCandidate(root.Get(cachePath).String()) if cacheID == "" && hasNestedReq { cacheID = normalizedSessionCandidate(reqRoot.Get(cachePath).String()) } if cacheID != "" { info.ClientType = "gemini" info.SessionID = "geminicache:" + cacheID if parentCandidate != "" && parentCandidate != cacheID { info.ParentSessionID = "geminicache:" + parentCandidate info.AgentName = "subagent" } else { info.AgentName = "main" } return finalizeSessionInfo(info) } }
// OpenAI thread in payload for _, threadPath := range []string{"thread_id", "threadId", "metadata.thread_id"} { tid := normalizedSessionCandidate(root.Get(threadPath).String()) if tid == "" && hasNestedReq { tid = normalizedSessionCandidate(reqRoot.Get(threadPath).String()) } if tid != "" { info.ClientType = "openai-thread" info.SessionID = "thread:" + tid if parentCandidate != "" && parentCandidate != tid { info.ParentSessionID = "thread:" + parentCandidate if isBodyForkCandidate(root, reqRoot, hasNestedReq) { info.IsFork = true info.IsSubagent = false info.AgentName = "main" } else { info.AgentName = "subagent" info.IsSubagent = true } } else { info.AgentName = "main" } return finalizeSessionInfo(info) } }
// Generic session in payload agentID := normalizedSessionCandidate(root.Get("metadata.agent_id").String()) if agentID == "" { agentID = normalizedSessionCandidate(root.Get("metadata.subagent_id").String()) } if agentID == "" { agentID = sessionHeaderValue(headers, "X-Claude-Code-Agent-Id") } if agentID == "" { agentID = sessionHeaderValue(headers, "x-agent-id") } if agentID == "" && hasNestedReq { agentID = normalizedSessionCandidate(reqRoot.Get("metadata.agent_id").String()) if agentID == "" { agentID = normalizedSessionCandidate(reqRoot.Get("metadata.subagent_id").String()) } }
for _, path := range []string{"session_id", "sessionId", "sessionID", "metadata.session_id", "extra_body.session_id"} { sid := normalizedSessionCandidate(root.Get(path).String()) if sid == "" && hasNestedReq { sid = normalizedSessionCandidate(reqRoot.Get(path).String()) } if sid != "" { info.ClientType = "generic" if agentID != "" && agentID != "main" { info.SessionID = "session:" + sid + ":agent:" + agentID info.ParentSessionID = "session:" + sid if parentCandidate != "" && parentCandidate != sid { info.ParentSessionID = "session:" + parentCandidate } info.AgentName = agentID } else { info.SessionID = "session:" + sid if parentCandidate != "" && parentCandidate != sid { info.ParentSessionID = "session:" + parentCandidate if isBodyForkCandidate(root, reqRoot, hasNestedReq) { info.IsFork = true info.IsSubagent = false info.AgentName = "main" } else { info.AgentName = "subagent" info.IsSubagent = true } } else { info.AgentName = "main" } } return finalizeSessionInfo(info) } }
// Prompt cache key & Conversation object var conversationID string conversation := root.Get("conversation") if !conversation.Exists() && hasNestedReq { conversation = reqRoot.Get("conversation") } if sid := normalizedSessionCandidate(conversation.Get("id").String()); sid != "" { conversationID = "conv:" + sid } else if conversation.Type == gjson.String { if sid := normalizedSessionCandidate(conversation.String()); sid != "" { conversationID = "conv:" + sid } } pck := normalizedSessionCandidate(root.Get("prompt_cache_key").String()) if pck == "" { pck = normalizedSessionCandidate(root.Get("promptCacheKey").String()) } if pck == "" && hasNestedReq { pck = normalizedSessionCandidate(reqRoot.Get("prompt_cache_key").String()) if pck == "" { pck = normalizedSessionCandidate(reqRoot.Get("promptCacheKey").String()) } } if pck != "" { info.ClientType = "generic" info.SessionID = "pck:" + pck if parentCandidate != "" && parentCandidate != pck { info.ParentSessionID = "pck:" + parentCandidate info.AgentName = "subagent" } else { info.AgentName = "main" } return finalizeSessionInfo(info) } if conversationID != "" { info.ClientType = "conv" info.SessionID = conversationID if parentCandidate != "" && ("conv:"+parentCandidate) != conversationID { info.ParentSessionID = "conv:" + parentCandidate info.AgentName = "subagent" } else { info.AgentName = "main" } return finalizeSessionInfo(info) }
// Plain metadata.user_id userID := normalizedSessionCandidate(root.Get("metadata.user_id").String()) if userID == "" && hasNestedReq { userID = normalizedSessionCandidate(reqRoot.Get("metadata.user_id").String()) } if userID != "" { info.ClientType = "generic" info.SessionID = "user:" + userID info.AgentName = "main" return finalizeSessionInfo(info) }
// Legacy conversation string paths for _, convPath := range []string{"conversation_id", "conversationId", "chat_id", "chatId", "metadata.conversation_id", "extra_body.conversation_id"} { cid := normalizedSessionCandidate(root.Get(convPath).String()) if cid == "" && hasNestedReq { cid = normalizedSessionCandidate(reqRoot.Get(convPath).String()) } if cid != "" { info.ClientType = "conv" info.SessionID = "conv:" + cid if parentCandidate != "" && ("conv:"+parentCandidate) != ("conv:"+cid) { info.ParentSessionID = "conv:" + parentCandidate info.AgentName = "subagent" } else { info.AgentName = "main" } return finalizeSessionInfo(info) } } }
// 7. ExecutionSessionMetadataKey if executionID, ok := metadata[cliproxyexecutor.ExecutionSessionMetadataKey].(string); ok { if executionID = normalizedSessionCandidate(executionID); executionID != "" { info.ClientType = "generic" info.SessionID = "execution:" + executionID info.AgentName = "main" return finalizeSessionInfo(info) } }
// 8. LCPAffinitySessionIDMetadataKey if lcpID, ok := metadata[cliproxyexecutor.LCPAffinitySessionIDMetadataKey].(string); ok { if lcpID = normalizedSessionCandidate(lcpID); lcpID != "" { info.ClientType = "lcp" info.SessionID = lcpID if parentID, okParent := metadata[cliproxyexecutor.ParentSessionIDMetadataKey].(string); okParent { if parentID = normalizedSessionCandidate(parentID); parentID != "" && parentID != lcpID { info.ParentSessionID = parentID info.AgentName = "subagent" info.IsFork = true } else { info.AgentName = "main" } } else { info.AgentName = "main" } return finalizeSessionInfo(info) } }
return SessionInfo{}, false}
func isBodyForkCandidate(root, reqRoot gjson.Result, hasNestedReq bool) bool { if !root.Exists() { return false } for _, k := range []string{ "forked_from_thread_id", "forked_from_id", "metadata.forked_from_thread_id", "metadata.forked_from_id", "extra_body.forked_from_thread_id", "extra_body.forked_from_id", } { if val := normalizedSessionCandidate(root.Get(k).String()); val != "" { return true } if hasNestedReq { if val := normalizedSessionCandidate(reqRoot.Get(k).String()); val != "" { return true } } } return false}
// BoundSessionIdentity bounds an identifier to <= 256 bytes safely,// preserving uniqueness via SHA256 and guarding against splitting multibyte UTF-8 characters.func BoundSessionIdentity(id string) string { if len(id) <= 256 { return id } sum := sha256.Sum256([]byte(id)) hashHex := hex.EncodeToString(sum[:]) // 64 bytes prefixLen := 255 - 1 - len(hashHex) // 190 bytes if prefixLen > len(id) { prefixLen = len(id) } prefix := id[:prefixLen] for !utf8.ValidString(prefix) && len(prefix) > 0 { prefix = prefix[:len(prefix)-1] } return prefix + "#" + hashHex}
func finalizeSessionInfo(info SessionInfo) (SessionInfo, bool) { if info.SessionID == "" { return SessionInfo{}, false } info.SessionID = BoundSessionIdentity(info.SessionID) if info.ParentSessionID != "" { info.ParentSessionID = BoundSessionIdentity(info.ParentSessionID) } if info.AgentName == "" { info.AgentName = "main" } if info.ClientType == "" { info.ClientType = "generic" } // Self-referential loop protection if info.ParentSessionID == info.SessionID { info.ParentSessionID = "" } return info, true}
// ExtractTreeInfo is an alias for ExtractSessionInfo for backward compatibility.func ExtractTreeInfo(headers http.Header, payload []byte, metadata map[string]any) (SessionInfo, bool) { return ExtractSessionInfo(headers, payload, metadata)}
func sessionHeaderValue(headers http.Header, name string) string { if headers == nil { return "" } if value := normalizedSessionCandidate(headers.Get(name)); value != "" { return value } for key, values := range headers { if !strings.EqualFold(key, name) { continue } for _, value := range values { if normalized := normalizedSessionCandidate(value); normalized != "" { return normalized } } } return ""}
func normalizedSessionCandidate(raw string) string { return NormalizeExplicitID(raw)}