mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
186 lines
5.5 KiB
Go
186 lines
5.5 KiB
Go
package session
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/sipeed/picoclaw/pkg/routing"
|
|
)
|
|
|
|
const (
|
|
sessionKeyV1Prefix = "sk_v1_"
|
|
legacyAgentSessionKeyPrefix = "agent:"
|
|
)
|
|
|
|
type ParsedLegacySessionKey struct {
|
|
AgentID string
|
|
Rest string
|
|
}
|
|
|
|
// BuildOpaqueSessionKey returns a stable opaque session key derived from a
|
|
// canonical alias string. The alias remains available through metadata for
|
|
// compatibility and migration purposes.
|
|
func BuildOpaqueSessionKey(alias string) string {
|
|
normalized := strings.TrimSpace(strings.ToLower(alias))
|
|
if normalized == "" {
|
|
return ""
|
|
}
|
|
sum := sha256.Sum256([]byte(normalized))
|
|
return sessionKeyV1Prefix + hex.EncodeToString(sum[:])
|
|
}
|
|
|
|
// IsOpaqueSessionKey returns true when the key matches the current opaque
|
|
// session-key format.
|
|
func IsOpaqueSessionKey(key string) bool {
|
|
return strings.HasPrefix(strings.ToLower(strings.TrimSpace(key)), sessionKeyV1Prefix)
|
|
}
|
|
|
|
func IsLegacyAgentSessionKey(key string) bool {
|
|
return strings.HasPrefix(strings.ToLower(strings.TrimSpace(key)), legacyAgentSessionKeyPrefix)
|
|
}
|
|
|
|
func IsExplicitSessionKey(key string) bool {
|
|
return IsOpaqueSessionKey(key) || IsLegacyAgentSessionKey(key)
|
|
}
|
|
|
|
func ParseLegacyAgentSessionKey(sessionKey string) *ParsedLegacySessionKey {
|
|
raw := strings.TrimSpace(sessionKey)
|
|
if raw == "" {
|
|
return nil
|
|
}
|
|
parts := strings.SplitN(raw, ":", 3)
|
|
if len(parts) < 3 || parts[0] != "agent" {
|
|
return nil
|
|
}
|
|
agentID := strings.TrimSpace(parts[1])
|
|
rest := parts[2]
|
|
if agentID == "" || rest == "" {
|
|
return nil
|
|
}
|
|
return &ParsedLegacySessionKey{AgentID: agentID, Rest: rest}
|
|
}
|
|
|
|
func BuildLegacyMainAlias(agentID string) string {
|
|
return fmt.Sprintf("agent:%s:main", routing.NormalizeAgentID(agentID))
|
|
}
|
|
|
|
// BuildMainSessionKey returns the canonical opaque main-session key for an
|
|
// agent. The corresponding legacy alias remains available via
|
|
// BuildLegacyMainAlias for compatibility and migration logic.
|
|
func BuildMainSessionKey(agentID string) string {
|
|
return BuildOpaqueSessionKey(BuildLegacyMainAlias(agentID))
|
|
}
|
|
|
|
func BuildLegacyDirectAliases(agentID, channel, account, peerID string) []string {
|
|
agentID = routing.NormalizeAgentID(agentID)
|
|
channel = normalizeLegacyChannel(channel)
|
|
account = routing.NormalizeAccountID(account)
|
|
peerID = strings.ToLower(strings.TrimSpace(peerID))
|
|
if peerID == "" {
|
|
return nil
|
|
}
|
|
return []string{
|
|
fmt.Sprintf("agent:%s:direct:%s", agentID, peerID),
|
|
fmt.Sprintf("agent:%s:%s:direct:%s", agentID, channel, peerID),
|
|
fmt.Sprintf("agent:%s:%s:%s:direct:%s", agentID, channel, account, peerID),
|
|
}
|
|
}
|
|
|
|
func BuildLegacyPeerAlias(agentID, channel, peerKind, peerID string) string {
|
|
agentID = routing.NormalizeAgentID(agentID)
|
|
channel = normalizeLegacyChannel(channel)
|
|
peerKind = strings.ToLower(strings.TrimSpace(peerKind))
|
|
if peerKind == "" {
|
|
peerKind = "unknown"
|
|
}
|
|
peerID = strings.ToLower(strings.TrimSpace(peerID))
|
|
if peerID == "" {
|
|
peerID = "unknown"
|
|
}
|
|
return fmt.Sprintf("agent:%s:%s:%s:%s", agentID, channel, peerKind, peerID)
|
|
}
|
|
|
|
// CanonicalSessionIdentityID collapses an identity using identity_links when
|
|
// possible, then returns a normalized lowercase identifier.
|
|
func CanonicalSessionIdentityID(channel, rawID string, identityLinks map[string][]string) string {
|
|
normalizedID := strings.TrimSpace(rawID)
|
|
if normalizedID == "" {
|
|
return ""
|
|
}
|
|
if linked := resolveLinkedPeerID(identityLinks, channel, normalizedID); linked != "" {
|
|
normalizedID = linked
|
|
}
|
|
return strings.ToLower(normalizedID)
|
|
}
|
|
|
|
func normalizeLegacyChannel(channel string) string {
|
|
channel = strings.ToLower(strings.TrimSpace(channel))
|
|
if channel == "" {
|
|
return "unknown"
|
|
}
|
|
return channel
|
|
}
|
|
|
|
func resolveLinkedPeerID(identityLinks map[string][]string, channel, peerID string) string {
|
|
if len(identityLinks) == 0 {
|
|
return ""
|
|
}
|
|
peerID = strings.TrimSpace(peerID)
|
|
if peerID == "" {
|
|
return ""
|
|
}
|
|
|
|
candidates := make(map[string]bool)
|
|
rawCandidate := strings.ToLower(peerID)
|
|
if rawCandidate != "" {
|
|
candidates[rawCandidate] = true
|
|
}
|
|
channel = strings.ToLower(strings.TrimSpace(channel))
|
|
if channel != "" {
|
|
candidates[fmt.Sprintf("%s:%s", channel, rawCandidate)] = true
|
|
}
|
|
if idx := strings.Index(rawCandidate, ":"); idx > 0 && idx < len(rawCandidate)-1 {
|
|
candidates[rawCandidate[idx+1:]] = true
|
|
}
|
|
|
|
for canonical, ids := range identityLinks {
|
|
canonicalName := strings.TrimSpace(canonical)
|
|
if canonicalName == "" {
|
|
continue
|
|
}
|
|
for _, id := range ids {
|
|
normalized := strings.ToLower(strings.TrimSpace(id))
|
|
if normalized != "" && candidates[normalized] {
|
|
return canonicalName
|
|
}
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// CanonicalScopeSignature returns a stable serialized representation of scope.
|
|
func CanonicalScopeSignature(scope SessionScope) string {
|
|
parts := []string{
|
|
fmt.Sprintf("v=%d", scope.Version),
|
|
fmt.Sprintf("agent=%s", strings.TrimSpace(strings.ToLower(scope.AgentID))),
|
|
fmt.Sprintf("channel=%s", strings.TrimSpace(strings.ToLower(scope.Channel))),
|
|
fmt.Sprintf("account=%s", strings.TrimSpace(strings.ToLower(scope.Account))),
|
|
}
|
|
for _, dimension := range scope.Dimensions {
|
|
dimension = strings.TrimSpace(strings.ToLower(dimension))
|
|
if dimension == "" {
|
|
continue
|
|
}
|
|
value := strings.TrimSpace(strings.ToLower(scope.Values[dimension]))
|
|
parts = append(parts, fmt.Sprintf("%s=%s", dimension, value))
|
|
}
|
|
return strings.Join(parts, "|")
|
|
}
|
|
|
|
// BuildSessionKey returns the current opaque key for a structured session scope.
|
|
func BuildSessionKey(scope SessionScope) string {
|
|
return BuildOpaqueSessionKey(CanonicalScopeSignature(scope))
|
|
}
|