mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
feat(session): persist scope metadata and aliases
This commit is contained in:
@@ -2,6 +2,7 @@ package session
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/memory"
|
||||
@@ -15,24 +16,82 @@ type JSONLBackend struct {
|
||||
store memory.Store
|
||||
}
|
||||
|
||||
type metaAwareStore interface {
|
||||
GetSessionMeta(ctx context.Context, sessionKey string) (memory.SessionMeta, error)
|
||||
UpsertSessionMeta(ctx context.Context, sessionKey string, scope json.RawMessage, aliases []string) error
|
||||
ResolveSessionKey(ctx context.Context, sessionKey string) (string, bool, error)
|
||||
}
|
||||
|
||||
// MetadataAwareSessionStore exposes structured session metadata operations.
|
||||
type MetadataAwareSessionStore interface {
|
||||
EnsureSessionMetadata(sessionKey string, scope *SessionScope, aliases []string)
|
||||
ResolveSessionKey(sessionKey string) string
|
||||
}
|
||||
|
||||
// NewJSONLBackend wraps a memory.Store for use as a SessionStore.
|
||||
func NewJSONLBackend(store memory.Store) *JSONLBackend {
|
||||
return &JSONLBackend{store: store}
|
||||
}
|
||||
|
||||
func (b *JSONLBackend) resolveSessionKey(sessionKey string) string {
|
||||
metaStore, ok := b.store.(metaAwareStore)
|
||||
if !ok {
|
||||
return sessionKey
|
||||
}
|
||||
resolved, found, err := metaStore.ResolveSessionKey(context.Background(), sessionKey)
|
||||
if err != nil {
|
||||
log.Printf("session: resolve session key: %v", err)
|
||||
return sessionKey
|
||||
}
|
||||
if found && resolved != "" {
|
||||
return resolved
|
||||
}
|
||||
return sessionKey
|
||||
}
|
||||
|
||||
// ResolveSessionKey maps aliases onto their canonical session key when the
|
||||
// underlying store supports structured metadata. Unknown aliases fall back to
|
||||
// the original input so existing callers remain compatible.
|
||||
func (b *JSONLBackend) ResolveSessionKey(sessionKey string) string {
|
||||
return b.resolveSessionKey(sessionKey)
|
||||
}
|
||||
|
||||
// EnsureSessionMetadata persists scope and alias metadata for a session.
|
||||
func (b *JSONLBackend) EnsureSessionMetadata(sessionKey string, scope *SessionScope, aliases []string) {
|
||||
metaStore, ok := b.store.(metaAwareStore)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var rawScope json.RawMessage
|
||||
if scope != nil {
|
||||
data, err := json.Marshal(scope)
|
||||
if err != nil {
|
||||
log.Printf("session: encode session scope: %v", err)
|
||||
return
|
||||
}
|
||||
rawScope = data
|
||||
}
|
||||
if err := metaStore.UpsertSessionMeta(context.Background(), sessionKey, rawScope, aliases); err != nil {
|
||||
log.Printf("session: upsert session metadata: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *JSONLBackend) AddMessage(sessionKey, role, content string) {
|
||||
sessionKey = b.resolveSessionKey(sessionKey)
|
||||
if err := b.store.AddMessage(context.Background(), sessionKey, role, content); err != nil {
|
||||
log.Printf("session: add message: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *JSONLBackend) AddFullMessage(sessionKey string, msg providers.Message) {
|
||||
sessionKey = b.resolveSessionKey(sessionKey)
|
||||
if err := b.store.AddFullMessage(context.Background(), sessionKey, msg); err != nil {
|
||||
log.Printf("session: add full message: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *JSONLBackend) GetHistory(key string) []providers.Message {
|
||||
key = b.resolveSessionKey(key)
|
||||
msgs, err := b.store.GetHistory(context.Background(), key)
|
||||
if err != nil {
|
||||
log.Printf("session: get history: %v", err)
|
||||
@@ -42,6 +101,7 @@ func (b *JSONLBackend) GetHistory(key string) []providers.Message {
|
||||
}
|
||||
|
||||
func (b *JSONLBackend) GetSummary(key string) string {
|
||||
key = b.resolveSessionKey(key)
|
||||
summary, err := b.store.GetSummary(context.Background(), key)
|
||||
if err != nil {
|
||||
log.Printf("session: get summary: %v", err)
|
||||
@@ -51,18 +111,21 @@ func (b *JSONLBackend) GetSummary(key string) string {
|
||||
}
|
||||
|
||||
func (b *JSONLBackend) SetSummary(key, summary string) {
|
||||
key = b.resolveSessionKey(key)
|
||||
if err := b.store.SetSummary(context.Background(), key, summary); err != nil {
|
||||
log.Printf("session: set summary: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *JSONLBackend) SetHistory(key string, history []providers.Message) {
|
||||
key = b.resolveSessionKey(key)
|
||||
if err := b.store.SetHistory(context.Background(), key, history); err != nil {
|
||||
log.Printf("session: set history: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *JSONLBackend) TruncateHistory(key string, keepLast int) {
|
||||
key = b.resolveSessionKey(key)
|
||||
if err := b.store.TruncateHistory(context.Background(), key, keepLast); err != nil {
|
||||
log.Printf("session: truncate history: %v", err)
|
||||
}
|
||||
@@ -72,6 +135,7 @@ func (b *JSONLBackend) TruncateHistory(key string, keepLast int) {
|
||||
// immediately, the data is already durable. Save runs compaction to reclaim
|
||||
// space from logically truncated messages (no-op when there are none).
|
||||
func (b *JSONLBackend) Save(key string) error {
|
||||
key = b.resolveSessionKey(key)
|
||||
return b.store.Compact(context.Background(), key)
|
||||
}
|
||||
|
||||
|
||||
@@ -177,3 +177,31 @@ func TestJSONLBackend_SummarizeFlow(t *testing.T) {
|
||||
t.Errorf("first message = %q, want %q", history[0].Content, "msg 16")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONLBackend_ResolveAliasAndPersistMetadata(t *testing.T) {
|
||||
b := newBackend(t)
|
||||
|
||||
b.EnsureSessionMetadata("canonical", &session.SessionScope{
|
||||
Version: session.ScopeVersionV1,
|
||||
AgentID: "main",
|
||||
Channel: "telegram",
|
||||
Account: "default",
|
||||
Dimensions: []string{"chat"},
|
||||
Values: map[string]string{
|
||||
"chat": "group:c1",
|
||||
},
|
||||
}, []string{"legacy"})
|
||||
|
||||
if got := b.ResolveSessionKey("legacy"); got != "canonical" {
|
||||
t.Fatalf("ResolveSessionKey() = %q, want %q", got, "canonical")
|
||||
}
|
||||
|
||||
b.AddMessage("legacy", "user", "hello through alias")
|
||||
history := b.GetHistory("canonical")
|
||||
if len(history) != 1 {
|
||||
t.Fatalf("len(history) = %d, want 1", len(history))
|
||||
}
|
||||
if history[0].Content != "hello through alias" {
|
||||
t.Fatalf("history[0].Content = %q, want %q", history[0].Content, "hello through alias")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user