Files
Duy /zuey/ 43049d3b3f feat(http): unblock CLI P6 backend APIs (#44)
* feat(api): unblock trace follow and provider reconnect

* test(cron): stabilize run log execution test

* feat(http): add CLI P6 backend endpoints
2026-05-22 20:59:47 +07:00

161 lines
7.8 KiB
Go

package store
import (
"context"
"errors"
"time"
"github.com/google/uuid"
"github.com/nextlevelbuilder/goclaw/internal/providers"
)
var (
ErrSessionNotFound = errors.New("session not found")
ErrSessionAlreadyExists = errors.New("session already exists")
ErrInvalidSessionBranch = errors.New("invalid session branch")
)
// SessionBranchOpts configures a durable session branch operation.
type SessionBranchOpts struct {
NewKey string
UpToIndex int
Label string
Metadata map[string]string
}
// SessionData holds conversation state for one session.
type SessionData struct {
Key string `json:"key" db:"key"`
Messages []providers.Message `json:"messages" db:"messages"`
Summary string `json:"summary,omitempty" db:"summary"`
Created time.Time `json:"created" db:"created_at"`
Updated time.Time `json:"updated" db:"updated_at"`
AgentUUID uuid.UUID `json:"agentUUID,omitempty" db:"agent_id"` // DB agent UUID
UserID string `json:"userID,omitempty" db:"user_id"` // External user ID (e.g. Telegram user ID)
TeamID *uuid.UUID `json:"teamID,omitempty" db:"team_id"` // Team UUID (set for team sessions)
Model string `json:"model,omitempty" db:"model"`
Provider string `json:"provider,omitempty" db:"provider"`
Channel string `json:"channel,omitempty" db:"channel"`
InputTokens int64 `json:"inputTokens,omitempty" db:"input_tokens"`
OutputTokens int64 `json:"outputTokens,omitempty" db:"output_tokens"`
CompactionCount int `json:"compactionCount,omitempty" db:"compaction_count"`
MemoryFlushCompactionCount int `json:"memoryFlushCompactionCount,omitempty" db:"memory_flush_compaction_count"`
MemoryFlushAt int64 `json:"memoryFlushAt,omitempty" db:"-"`
Label string `json:"label,omitempty" db:"label"`
SpawnedBy string `json:"spawnedBy,omitempty" db:"spawned_by"`
SpawnDepth int `json:"spawnDepth,omitempty" db:"spawn_depth"`
Metadata map[string]string `json:"metadata,omitempty" db:"metadata"`
// Adaptive throttle: cached per-session so scheduler reads without DB lookup.
ContextWindow int `json:"contextWindow,omitempty" db:"context_window"` // agent's context window (set on first run)
LastPromptTokens int `json:"lastPromptTokens,omitempty" db:"last_prompt_tokens"` // actual prompt tokens from last LLM response
LastMessageCount int `json:"lastMessageCount,omitempty" db:"last_message_count"` // message count at time of last LLM call
}
// SessionInfo is lightweight session metadata for listing.
type SessionInfo struct {
Key string `json:"key" db:"key"`
MessageCount int `json:"messageCount" db:"message_count"`
Created time.Time `json:"created" db:"created_at"`
Updated time.Time `json:"updated" db:"updated_at"`
Label string `json:"label,omitempty" db:"label"`
Channel string `json:"channel,omitempty" db:"channel"`
UserID string `json:"userID,omitempty" db:"user_id"`
Metadata map[string]string `json:"metadata,omitempty" db:"metadata"`
}
// SessionListOpts holds pagination options for ListPaged.
type SessionListOpts struct {
AgentID string `db:"-"`
Channel string `db:"-"` // optional: filter by channel prefix ("ws", "telegram", etc.)
UserID string `db:"-"` // optional: filter by user_id
TenantID uuid.UUID `db:"-"` // optional: filter by tenant (uuid.Nil = no filter)
Limit int `db:"-"`
Offset int `db:"-"`
}
// SessionListResult is the paginated result of ListPaged.
type SessionListResult struct {
Sessions []SessionInfo `json:"sessions" db:"-"`
Total int `json:"total" db:"-"`
}
// SessionInfoRich is an enriched session info for API responses (includes model, tokens, agent name).
type SessionInfoRich struct {
SessionInfo
Model string `json:"model,omitempty" db:"model"`
Provider string `json:"provider,omitempty" db:"provider"`
InputTokens int64 `json:"inputTokens,omitempty" db:"input_tokens"`
OutputTokens int64 `json:"outputTokens,omitempty" db:"output_tokens"`
AgentName string `json:"agentName,omitempty" db:"agent_name"`
EstimatedTokens int `json:"estimatedTokens,omitempty" db:"-"` // estimated current context tokens (messages bytes/4 + 12k system prompt)
ContextWindow int `json:"contextWindow,omitempty" db:"context_window"` // agent's context window size
CompactionCount int `json:"compactionCount,omitempty" db:"compaction_count"` // number of compactions performed
}
// SessionListRichResult is the paginated result of ListPagedRich.
type SessionListRichResult struct {
Sessions []SessionInfoRich `json:"sessions" db:"-"`
Total int `json:"total" db:"-"`
}
// SessionCoreStore manages session lifecycle, messages, and history.
type SessionCoreStore interface {
GetOrCreate(ctx context.Context, key string) *SessionData
// Get returns the session if it exists (cache or DB), nil otherwise. Never creates.
Get(ctx context.Context, key string) *SessionData
AddMessage(ctx context.Context, key string, msg providers.Message)
GetHistory(ctx context.Context, key string) []providers.Message
GetSummary(ctx context.Context, key string) string
SetSummary(ctx context.Context, key, summary string)
GetLabel(ctx context.Context, key string) string
SetLabel(ctx context.Context, key, label string)
SetAgentInfo(ctx context.Context, key string, agentUUID uuid.UUID, userID string)
TruncateHistory(ctx context.Context, key string, keepLast int)
SetHistory(ctx context.Context, key string, msgs []providers.Message)
Reset(ctx context.Context, key string)
Delete(ctx context.Context, key string) error
Save(ctx context.Context, key string) error
}
// SessionBranchStore is implemented by durable stores that can clone a session
// into a new branch key without overwriting an existing target.
type SessionBranchStore interface {
BranchSession(ctx context.Context, sourceKey string, opts SessionBranchOpts) (*SessionData, int, error)
}
// SessionMetadataStore manages session metadata, token tracking, and calibration.
type SessionMetadataStore interface {
UpdateMetadata(ctx context.Context, key, model, provider, channel string)
AccumulateTokens(ctx context.Context, key string, input, output int64)
IncrementCompaction(ctx context.Context, key string)
GetCompactionCount(ctx context.Context, key string) int
GetMemoryFlushCompactionCount(ctx context.Context, key string) int
SetMemoryFlushDone(ctx context.Context, key string)
GetSessionMetadata(ctx context.Context, key string) map[string]string
SetSessionMetadata(ctx context.Context, key string, metadata map[string]string)
SetSpawnInfo(ctx context.Context, key, spawnedBy string, depth int)
SetContextWindow(ctx context.Context, key string, cw int)
GetContextWindow(ctx context.Context, key string) int
SetLastPromptTokens(ctx context.Context, key string, tokens, msgCount int)
GetLastPromptTokens(ctx context.Context, key string) (tokens, msgCount int)
}
// SessionListingStore manages session listing, search, and discovery.
type SessionListingStore interface {
List(ctx context.Context, agentID string) []SessionInfo
ListPaged(ctx context.Context, opts SessionListOpts) SessionListResult
ListPagedRich(ctx context.Context, opts SessionListOpts) SessionListRichResult
LastUsedChannel(ctx context.Context, agentID string) (channel, chatID string)
}
// SessionStore composes all session sub-interfaces for backward compatibility.
// New code should depend on the specific sub-interface it needs.
type SessionStore interface {
SessionCoreStore
SessionMetadataStore
SessionListingStore
}