storage

package
v0.1.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 4, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package storage provides eyrie's persistence layer: virtual keys and an SQLite-backed budget store (BudgetStore / SQLiteStore) for tracking usage and spend limits.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrBudgetExceeded    = errors.New("eyrie: virtual key budget exceeded")
	ErrUnknownVirtualKey = errors.New("eyrie: unknown virtual key")
)

Budget sentinel errors. These mirror the client package's errors by message so callers can match on either; the BudgetProvider depends only on the structural method set, not on these specific values.

Functions

This section is empty.

Types

type Alias

type Alias struct {
	Alias  string `json:"alias"`
	NodeID string `json:"node_id"`
}

Alias maps a stable name to a node ID.

type AnalyticsStore

type AnalyticsStore interface {
	// RecordCost persists a cost record for a completed request.
	RecordCost(ctx context.Context, rec *CostRecord) error
	// GetUsageStats returns aggregated usage stats grouped by provider/model,
	// filtered to records created after the given time.
	GetUsageStats(ctx context.Context, since time.Time) ([]UsageStats, error)
	// GetCostSummary returns total cost grouped by provider/model since the given time.
	GetCostSummary(ctx context.Context, since time.Time) ([]UsageStats, error)
	// GetProviderHealth returns health metrics derived from actual request data.
	GetProviderHealth(ctx context.Context, since time.Time) ([]ProviderHealthRecord, error)
}

AnalyticsStore provides persistent cost tracking and usage analytics. It extends the base Store with analytics-specific tables and queries.

type BudgetStore

type BudgetStore struct {
	// contains filtered or unexported fields
}

BudgetStore is a SQLite-backed store for virtual keys, their budgets, and a per-request cost ledger. It satisfies the client.BudgetStore interface structurally (CheckBudget + RecordUsage) without importing the client package.

It is safe for concurrent use (single underlying connection, like SQLiteStore).

func OpenBudgetStore

func OpenBudgetStore(path string) (*BudgetStore, error)

OpenBudgetStore opens (or creates) a SQLite database at path and ensures the budget schema exists. The database file is set to 0o600 because the virtual_key_secrets table stores provider API keys.

func (*BudgetStore) CheckBudget

func (s *BudgetStore) CheckBudget(ctx context.Context, virtualKey string, estCostUSD float64) error

CheckBudget implements the client.BudgetStore contract: it returns ErrBudgetExceeded if charging estCostUSD would exceed the key's limit, ErrUnknownVirtualKey if the key is unknown, or nil otherwise.

func (*BudgetStore) Close

func (s *BudgetStore) Close() error

Close releases the underlying database.

func (*BudgetStore) CreateVirtualKey

func (s *BudgetStore) CreateVirtualKey(ctx context.Context, id, name, provider string, limitUSD float64) error

CreateVirtualKey inserts a virtual key with the given budget. A non-positive limitUSD means unlimited spend.

func (*BudgetStore) Get

func (s *BudgetStore) Get(ctx context.Context, virtualKey string) (*VirtualKey, error)

Get returns the current state of a virtual key.

func (*BudgetStore) ProviderSecret

func (s *BudgetStore) ProviderSecret(ctx context.Context, virtualKey, provider string) (string, error)

ProviderSecret returns the real API key for a virtual key + provider.

func (*BudgetStore) RecordUsage

func (s *BudgetStore) RecordUsage(ctx context.Context, virtualKey string, costUSD float64, tokensIn, tokensOut int) error

RecordUsage implements the client.BudgetStore contract: it appends a ledger row and increments the running totals atomically.

func (*BudgetStore) SetProviderSecret

func (s *BudgetStore) SetProviderSecret(ctx context.Context, virtualKey, provider, apiKey string) error

SetProviderSecret associates a real provider API key with a virtual key.

type CostRecord

type CostRecord struct {
	ID                  int64     `json:"id"`
	Provider            string    `json:"provider"`
	Model               string    `json:"model"`
	InputTokens         int       `json:"input_tokens"`
	OutputTokens        int       `json:"output_tokens"`
	CacheReadTokens     int       `json:"cache_read_tokens"`
	CacheCreationTokens int       `json:"cache_creation_tokens"`
	CostUSD             float64   `json:"cost_usd"`
	LatencyMs           int       `json:"latency_ms"`
	CreatedAt           time.Time `json:"created_at"`
}

CostRecord represents a single persisted cost entry for an API request.

type DAG

type DAG struct {
	// contains filtered or unexported fields
}

DAG is a conversation stored as a directed acyclic graph.

func NewDAG

func NewDAG(dbPath string, sessionID string) (*DAG, error)

NewDAG opens or creates a conversation DAG for the given session.

func NewDAGFromStore

func NewDAGFromStore(store Store, sessionID string) *DAG

NewDAGFromStore creates a DAG using an existing store, which is useful in tests and callers that manage store lifetime outside the DAG wrapper.

func (*DAG) Append

func (d *DAG) Append(ctx context.Context, parentID string, role string, content string) (*DAGNode, error)

Append adds a new node as a child of the given parent and advances the head.

func (*DAG) Branches

func (d *DAG) Branches(ctx context.Context, nodeID string) ([]*DAGNode, error)

Branches returns all child nodes. If nodeID is empty, returns session roots.

func (*DAG) Close

func (d *DAG) Close() error

Close closes the underlying store.

func (*DAG) Fork

func (d *DAG) Fork(ctx context.Context, nodeID string) (*DAGNode, error)

Fork creates a new branch from the given node.

func (*DAG) Head

func (d *DAG) Head(ctx context.Context) (*DAGNode, error)

Head returns the most recent node in the current branch.

func (*DAG) History

func (d *DAG) History(ctx context.Context, nodeID string) ([]*DAGNode, error)

History returns the linear path from root to the given node.

func (*DAG) Prune

func (d *DAG) Prune(ctx context.Context, nodeID string) error

Prune removes a node and all its descendants.

func (*DAG) SetHead

func (d *DAG) SetHead(ctx context.Context, nodeID string) error

SetHead moves the head pointer to a specific node.

type DAGNode

type DAGNode struct {
	ID        string
	ParentID  string
	Role      string
	Content   string
	Model     string
	CreatedAt time.Time
	Metadata  map[string]string
}

DAGNode is a single message in the conversation DAG.

type Node

type Node struct {
	ID                  string          `json:"id"`
	ParentID            string          `json:"parent_id,omitempty"`
	RootID              string          `json:"root_id,omitempty"`
	Sequence            int             `json:"sequence"`
	NodeType            NodeType        `json:"node_type"`
	Content             string          `json:"content"`
	Provider            string          `json:"provider,omitempty"`
	Model               string          `json:"model,omitempty"`
	TokensIn            int             `json:"tokens_in,omitempty"`
	TokensOut           int             `json:"tokens_out,omitempty"`
	TokensCacheRead     int             `json:"tokens_cache_read,omitempty"`
	TokensCacheCreation int             `json:"tokens_cache_creation,omitempty"`
	TokensReasoning     int             `json:"tokens_reasoning,omitempty"`
	LatencyMs           int             `json:"latency_ms,omitempty"`
	StopReason          string          `json:"stop_reason,omitempty"`
	OutputGroupID       string          `json:"output_group_id,omitempty"`
	Status              string          `json:"status,omitempty"`
	Title               string          `json:"title,omitempty"`
	SystemPrompt        string          `json:"system_prompt,omitempty"`
	CreatedAt           time.Time       `json:"created_at"`
	Metadata            json.RawMessage `json:"metadata,omitempty"`
}

Node is a single persisted entry in the conversation DAG.

type NodeType

type NodeType string

NodeType identifies the role of a persisted conversation node.

const (
	NodeTypeUser       NodeType = "user"
	NodeTypeAssistant  NodeType = "assistant"
	NodeTypeSystem     NodeType = "system"
	NodeTypeToolCall   NodeType = "tool_call"
	NodeTypeToolResult NodeType = "tool_result"
)

type Option

type Option func(*openConfig)

Option configures a SQLiteStore at Open time.

func WithMaxOpenConns

func WithMaxOpenConns(n int) Option

WithMaxOpenConns overrides the database connection pool size. The default (1) serializes all access, which is safe for single-agent use; raise it for concurrent (e.g. HTTP server) workloads. SQLite still serializes writes via the busy_timeout pragma, so concurrent access remains bounded by WAL readers plus a single writer.

type ProviderHealthRecord

type ProviderHealthRecord struct {
	Provider      string    `json:"provider"`
	RequestCount  int       `json:"request_count"`
	ErrorCount    int       `json:"error_count"`
	ErrorRate     float64   `json:"error_rate"`
	AvgLatencyMs  float64   `json:"avg_latency_ms"`
	P50LatencyMs  float64   `json:"p50_latency_ms"`
	P95LatencyMs  float64   `json:"p95_latency_ms"`
	P99LatencyMs  float64   `json:"p99_latency_ms"`
	LastRequestAt time.Time `json:"last_request_at"`
}

ProviderHealthRecord holds per-provider health data from the nodes table.

type SQLiteAnalyticsStore

type SQLiteAnalyticsStore struct {
	// contains filtered or unexported fields
}

SQLiteAnalyticsStore implements AnalyticsStore backed by SQLite.

func NewSQLiteAnalyticsStore

func NewSQLiteAnalyticsStore(s *SQLiteStore) *SQLiteAnalyticsStore

NewSQLiteAnalyticsStore wraps an existing SQLiteStore's database connection for analytics operations. The caller must ensure the underlying DB has been migrated (i.e. the SQLiteStore was opened via Open).

func NewSQLiteAnalyticsStoreFromDB

func NewSQLiteAnalyticsStoreFromDB(db *sql.DB) *SQLiteAnalyticsStore

NewSQLiteAnalyticsStoreFromDB creates an analytics store from a raw DB handle.

func (*SQLiteAnalyticsStore) GetCostSummary

func (a *SQLiteAnalyticsStore) GetCostSummary(ctx context.Context, since time.Time) ([]UsageStats, error)

GetCostSummary returns total cost grouped by provider/model from cost_records.

func (*SQLiteAnalyticsStore) GetProviderHealth

func (a *SQLiteAnalyticsStore) GetProviderHealth(ctx context.Context, since time.Time) ([]ProviderHealthRecord, error)

GetProviderHealth returns health metrics derived from actual request data in the nodes table, computed over the given time window.

func (*SQLiteAnalyticsStore) GetUsageStats

func (a *SQLiteAnalyticsStore) GetUsageStats(ctx context.Context, since time.Time) ([]UsageStats, error)

GetUsageStats returns aggregated usage statistics from the nodes table, grouped by provider and model, for nodes created after 'since'.

func (*SQLiteAnalyticsStore) MigrateAnalytics

func (a *SQLiteAnalyticsStore) MigrateAnalytics() error

MigrateAnalytics creates the analytics-specific tables. It is safe to call multiple times (uses IF NOT EXISTS).

func (*SQLiteAnalyticsStore) RecordCost

func (a *SQLiteAnalyticsStore) RecordCost(ctx context.Context, rec *CostRecord) error

RecordCost persists a cost record.

type SQLiteStore

type SQLiteStore struct {
	// contains filtered or unexported fields
}

func Open

func Open(path string, opts ...Option) (*SQLiteStore, error)

Open opens the SQLite conversation DAG store at path. The store is opened in WAL mode with foreign keys enabled. Without options the connection pool is limited to a single connection (serialized access); pass WithMaxOpenConns to raise the pool size for concurrent workloads.

func (*SQLiteStore) Close

func (s *SQLiteStore) Close() error

func (*SQLiteStore) CreateAlias

func (s *SQLiteStore) CreateAlias(ctx context.Context, alias, nodeID string) error

func (*SQLiteStore) CreateNode

func (s *SQLiteStore) CreateNode(ctx context.Context, node *Node) error

func (*SQLiteStore) DeleteAlias

func (s *SQLiteStore) DeleteAlias(ctx context.Context, alias string) error

func (*SQLiteStore) DeleteNode

func (s *SQLiteStore) DeleteNode(ctx context.Context, id string) error

func (*SQLiteStore) GetAncestors

func (s *SQLiteStore) GetAncestors(ctx context.Context, id string) ([]*Node, error)

func (*SQLiteStore) GetNode

func (s *SQLiteStore) GetNode(ctx context.Context, id string) (*Node, error)

func (*SQLiteStore) GetNodeByAlias

func (s *SQLiteStore) GetNodeByAlias(ctx context.Context, alias string) (*Node, error)

func (*SQLiteStore) GetNodeByPrefix

func (s *SQLiteStore) GetNodeByPrefix(ctx context.Context, prefix string) (*Node, error)

func (*SQLiteStore) GetNodeChildren

func (s *SQLiteStore) GetNodeChildren(ctx context.Context, id string) ([]*Node, error)

func (*SQLiteStore) GetOrphanedToolUses

func (s *SQLiteStore) GetOrphanedToolUses(ctx context.Context, ancestorIDs []string) (map[string][]string, error)

func (*SQLiteStore) GetSubtree

func (s *SQLiteStore) GetSubtree(ctx context.Context, id string) ([]*Node, error)

func (*SQLiteStore) IndexToolIDs

func (s *SQLiteStore) IndexToolIDs(ctx context.Context, nodeID string, toolIDs []string, role string) error

func (*SQLiteStore) ListAliases

func (s *SQLiteStore) ListAliases(ctx context.Context, nodeID string) ([]Alias, error)

func (*SQLiteStore) ListRootNodes

func (s *SQLiteStore) ListRootNodes(ctx context.Context) ([]*Node, error)

func (*SQLiteStore) UpdateNode

func (s *SQLiteStore) UpdateNode(ctx context.Context, node *Node) error

type Store

type Store interface {
	CreateNode(ctx context.Context, node *Node) error
	GetNode(ctx context.Context, id string) (*Node, error)
	GetNodeByPrefix(ctx context.Context, prefix string) (*Node, error)
	GetNodeChildren(ctx context.Context, id string) ([]*Node, error)
	GetSubtree(ctx context.Context, id string) ([]*Node, error)
	GetAncestors(ctx context.Context, id string) ([]*Node, error)
	ListRootNodes(ctx context.Context) ([]*Node, error)
	UpdateNode(ctx context.Context, node *Node) error
	DeleteNode(ctx context.Context, id string) error
	CreateAlias(ctx context.Context, alias, nodeID string) error
	DeleteAlias(ctx context.Context, alias string) error
	GetNodeByAlias(ctx context.Context, alias string) (*Node, error)
	ListAliases(ctx context.Context, nodeID string) ([]Alias, error)
	IndexToolIDs(ctx context.Context, nodeID string, toolIDs []string, role string) error
	// GetOrphanedToolUses returns tool_use IDs on ancestorIDs with no matching
	// tool_result on the same path. Map keys are node IDs.
	GetOrphanedToolUses(ctx context.Context, ancestorIDs []string) (map[string][]string, error)
	Close() error
}

Store captures the persistence operations used by the DAG wrapper and API layers so tests can swap implementations without changing call sites.

type UsageStats

type UsageStats struct {
	Provider           string  `json:"provider"`
	Model              string  `json:"model"`
	RequestCount       int     `json:"request_count"`
	TotalInputTokens   int     `json:"total_input_tokens"`
	TotalOutputTokens  int     `json:"total_output_tokens"`
	TotalCacheRead     int     `json:"total_cache_read_tokens"`
	TotalCacheCreation int     `json:"total_cache_creation_tokens"`
	ErrorCount         int     `json:"error_count"`
	ErrorRate          float64 `json:"error_rate"`
	AvgLatencyMs       float64 `json:"avg_latency_ms"`
	TotalCostUSD       float64 `json:"total_cost_usd"`
}

UsageStats holds aggregated usage statistics for a provider/model pair.

type VirtualKey

type VirtualKey struct {
	ID        string
	Name      string
	Provider  string
	LimitUSD  float64 // <= 0 means unlimited
	UsedUSD   float64
	TokensIn  int
	TokensOut int
	CreatedAt time.Time
}

VirtualKey is a logical key that maps to a real provider key and carries a spend budget.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL