92 lines
2.4 KiB
Go
92 lines
2.4 KiB
Go
package session
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"nub/internal/llm"
|
|
)
|
|
|
|
// Session ist der reine In-Memory-Baum, ab Tag 1 (E-03): jeder Zugriff auf
|
|
// "die Historie" geht über PathToHead(), nie über einen Slice.
|
|
type Session struct {
|
|
ID string
|
|
Nodes map[string]*Node
|
|
Head string
|
|
}
|
|
|
|
func New(id string) *Session {
|
|
return &Session{ID: id, Nodes: make(map[string]*Node)}
|
|
}
|
|
|
|
// HeadID gibt den aktuellen Head zurück (Methode statt nur Feld, damit
|
|
// Session und Store gemeinsam gegen ein schmales Interface programmierbar
|
|
// sind, siehe internal/agent.Sessioner).
|
|
func (s *Session) HeadID() string { return s.Head }
|
|
|
|
// Append hängt einen Node an. ParentID muss "" (Wurzel, nur beim allerersten
|
|
// Node zulässig) oder ein existierender Node sein. Setzt Head auf den neuen
|
|
// Node.
|
|
func (s *Session) Append(n *Node) error {
|
|
if n.ParentID != "" {
|
|
if _, ok := s.Nodes[n.ParentID]; !ok {
|
|
return fmt.Errorf("session: unknown parent %q", n.ParentID)
|
|
}
|
|
}
|
|
if _, exists := s.Nodes[n.ID]; exists {
|
|
return fmt.Errorf("session: node %q already exists", n.ID)
|
|
}
|
|
s.Nodes[n.ID] = n
|
|
s.Head = n.ID
|
|
return nil
|
|
}
|
|
|
|
// Branch setzt Head auf einen früheren Knoten. Weiterarbeit danach hängt
|
|
// neue Nodes an diesem Knoten an — ein zweiter Ast entsteht implizit, weil
|
|
// mehrere Nodes denselben ParentID referenzieren können.
|
|
func (s *Session) Branch(from string) error {
|
|
if from == "" {
|
|
s.Head = ""
|
|
return nil
|
|
}
|
|
if _, ok := s.Nodes[from]; !ok {
|
|
return fmt.Errorf("session: unknown node %q", from)
|
|
}
|
|
s.Head = from
|
|
return nil
|
|
}
|
|
|
|
// PathNodes läuft von Head Richtung Wurzel und stoppt am ersten Summary-Node
|
|
// (E-08); das Ergebnis ist in Wurzel→Head-Reihenfolge. Anders als
|
|
// PathToHead liefert es die Nodes selbst — Grundlage für Compaction, die
|
|
// Node-IDs für Replaces braucht.
|
|
func (s *Session) PathNodes() []*Node {
|
|
var nodes []*Node
|
|
id := s.Head
|
|
for id != "" {
|
|
n, ok := s.Nodes[id]
|
|
if !ok {
|
|
break
|
|
}
|
|
nodes = append(nodes, n)
|
|
if n.Kind == NodeSummary {
|
|
break
|
|
}
|
|
id = n.ParentID
|
|
}
|
|
|
|
for i, j := 0, len(nodes)-1; i < j; i, j = i+1, j-1 {
|
|
nodes[i], nodes[j] = nodes[j], nodes[i]
|
|
}
|
|
return nodes
|
|
}
|
|
|
|
// PathToHead läuft von Head Richtung Wurzel und stoppt am ersten
|
|
// Summary-Node (E-08); das Ergebnis ist in Wurzel→Head-Reihenfolge.
|
|
func (s *Session) PathToHead() []llm.Message {
|
|
nodes := s.PathNodes()
|
|
messages := make([]llm.Message, len(nodes))
|
|
for i, n := range nodes {
|
|
messages[i] = n.Message
|
|
}
|
|
return messages
|
|
}
|