|
|
@@ -1,255 +0,0 @@
|
|
|
-package session
|
|
|
-
|
|
|
-import (
|
|
|
- "context"
|
|
|
- "database/sql"
|
|
|
- "fmt"
|
|
|
- "sync"
|
|
|
- "time"
|
|
|
-
|
|
|
- "github.com/google/uuid"
|
|
|
- "github.com/sst/opencode/internal/db"
|
|
|
- "github.com/sst/opencode/internal/pubsub"
|
|
|
-)
|
|
|
-
|
|
|
-type Session struct {
|
|
|
- ID string
|
|
|
- ParentSessionID string
|
|
|
- Title string
|
|
|
- MessageCount int64
|
|
|
- PromptTokens int64
|
|
|
- CompletionTokens int64
|
|
|
- Cost float64
|
|
|
- Summary string
|
|
|
- SummarizedAt time.Time
|
|
|
- CreatedAt time.Time
|
|
|
- UpdatedAt time.Time
|
|
|
-}
|
|
|
-
|
|
|
-const (
|
|
|
- EventSessionCreated pubsub.EventType = "session_created"
|
|
|
- EventSessionUpdated pubsub.EventType = "session_updated"
|
|
|
- EventSessionDeleted pubsub.EventType = "session_deleted"
|
|
|
-)
|
|
|
-
|
|
|
-type Service interface {
|
|
|
- pubsub.Subscriber[Session]
|
|
|
-
|
|
|
- Create(ctx context.Context, title string) (Session, error)
|
|
|
- CreateTaskSession(ctx context.Context, toolCallID, parentSessionID, title string) (Session, error)
|
|
|
- Get(ctx context.Context, id string) (Session, error)
|
|
|
- List(ctx context.Context) ([]Session, error)
|
|
|
- Update(ctx context.Context, session Session) (Session, error)
|
|
|
- Delete(ctx context.Context, id string) error
|
|
|
-}
|
|
|
-
|
|
|
-type service struct {
|
|
|
- db *db.Queries
|
|
|
- broker *pubsub.Broker[Session]
|
|
|
- mu sync.RWMutex
|
|
|
-}
|
|
|
-
|
|
|
-var globalSessionService *service
|
|
|
-
|
|
|
-func InitService(dbConn *sql.DB) error {
|
|
|
- if globalSessionService != nil {
|
|
|
- return fmt.Errorf("session service already initialized")
|
|
|
- }
|
|
|
- queries := db.New(dbConn)
|
|
|
- broker := pubsub.NewBroker[Session]()
|
|
|
-
|
|
|
- globalSessionService = &service{
|
|
|
- db: queries,
|
|
|
- broker: broker,
|
|
|
- }
|
|
|
- return nil
|
|
|
-}
|
|
|
-
|
|
|
-func GetService() Service {
|
|
|
- if globalSessionService == nil {
|
|
|
- panic("session service not initialized. Call session.InitService() first.")
|
|
|
- }
|
|
|
- return globalSessionService
|
|
|
-}
|
|
|
-
|
|
|
-func (s *service) Create(ctx context.Context, title string) (Session, error) {
|
|
|
- s.mu.Lock()
|
|
|
- defer s.mu.Unlock()
|
|
|
-
|
|
|
- if title == "" {
|
|
|
- title = "New Session - " + time.Now().Format("2006-01-02 15:04:05")
|
|
|
- }
|
|
|
-
|
|
|
- dbSessParams := db.CreateSessionParams{
|
|
|
- ID: uuid.New().String(),
|
|
|
- Title: title,
|
|
|
- }
|
|
|
- dbSession, err := s.db.CreateSession(ctx, dbSessParams)
|
|
|
- if err != nil {
|
|
|
- return Session{}, fmt.Errorf("db.CreateSession: %w", err)
|
|
|
- }
|
|
|
-
|
|
|
- session := s.fromDBItem(dbSession)
|
|
|
- s.broker.Publish(EventSessionCreated, session)
|
|
|
- return session, nil
|
|
|
-}
|
|
|
-
|
|
|
-func (s *service) CreateTaskSession(ctx context.Context, toolCallID, parentSessionID, title string) (Session, error) {
|
|
|
- s.mu.Lock()
|
|
|
- defer s.mu.Unlock()
|
|
|
-
|
|
|
- if title == "" {
|
|
|
- title = "Task Session - " + time.Now().Format("2006-01-02 15:04:05")
|
|
|
- }
|
|
|
- if toolCallID == "" {
|
|
|
- toolCallID = uuid.New().String()
|
|
|
- }
|
|
|
-
|
|
|
- dbSessParams := db.CreateSessionParams{
|
|
|
- ID: toolCallID,
|
|
|
- ParentSessionID: sql.NullString{String: parentSessionID, Valid: parentSessionID != ""},
|
|
|
- Title: title,
|
|
|
- }
|
|
|
- dbSession, err := s.db.CreateSession(ctx, dbSessParams)
|
|
|
- if err != nil {
|
|
|
- return Session{}, fmt.Errorf("db.CreateTaskSession: %w", err)
|
|
|
- }
|
|
|
- session := s.fromDBItem(dbSession)
|
|
|
- s.broker.Publish(EventSessionCreated, session)
|
|
|
- return session, nil
|
|
|
-}
|
|
|
-
|
|
|
-func (s *service) Get(ctx context.Context, id string) (Session, error) {
|
|
|
- s.mu.RLock()
|
|
|
- defer s.mu.RUnlock()
|
|
|
- dbSession, err := s.db.GetSessionByID(ctx, id)
|
|
|
- if err != nil {
|
|
|
- if err == sql.ErrNoRows {
|
|
|
- return Session{}, fmt.Errorf("session ID '%s' not found", id)
|
|
|
- }
|
|
|
- return Session{}, fmt.Errorf("db.GetSessionByID: %w", err)
|
|
|
- }
|
|
|
- return s.fromDBItem(dbSession), nil
|
|
|
-}
|
|
|
-
|
|
|
-func (s *service) List(ctx context.Context) ([]Session, error) {
|
|
|
- s.mu.RLock()
|
|
|
- defer s.mu.RUnlock()
|
|
|
- dbSessions, err := s.db.ListSessions(ctx)
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("db.ListSessions: %w", err)
|
|
|
- }
|
|
|
- sessions := make([]Session, len(dbSessions))
|
|
|
- for i, dbSess := range dbSessions {
|
|
|
- sessions[i] = s.fromDBItem(dbSess)
|
|
|
- }
|
|
|
- return sessions, nil
|
|
|
-}
|
|
|
-
|
|
|
-func (s *service) Update(ctx context.Context, session Session) (Session, error) {
|
|
|
- s.mu.Lock()
|
|
|
- defer s.mu.Unlock()
|
|
|
-
|
|
|
- if session.ID == "" {
|
|
|
- return Session{}, fmt.Errorf("cannot update session with empty ID")
|
|
|
- }
|
|
|
-
|
|
|
- params := db.UpdateSessionParams{
|
|
|
- ID: session.ID,
|
|
|
- Title: session.Title,
|
|
|
- PromptTokens: session.PromptTokens,
|
|
|
- CompletionTokens: session.CompletionTokens,
|
|
|
- Cost: session.Cost,
|
|
|
- Summary: sql.NullString{String: session.Summary, Valid: session.Summary != ""},
|
|
|
- SummarizedAt: sql.NullString{String: session.SummarizedAt.UTC().Format(time.RFC3339Nano), Valid: !session.SummarizedAt.IsZero()},
|
|
|
- }
|
|
|
- dbSession, err := s.db.UpdateSession(ctx, params)
|
|
|
- if err != nil {
|
|
|
- return Session{}, fmt.Errorf("db.UpdateSession: %w", err)
|
|
|
- }
|
|
|
- updatedSession := s.fromDBItem(dbSession)
|
|
|
- s.broker.Publish(EventSessionUpdated, updatedSession)
|
|
|
- return updatedSession, nil
|
|
|
-}
|
|
|
-
|
|
|
-func (s *service) Delete(ctx context.Context, id string) error {
|
|
|
- s.mu.Lock()
|
|
|
- dbSess, err := s.db.GetSessionByID(ctx, id)
|
|
|
- if err != nil {
|
|
|
- s.mu.Unlock()
|
|
|
- if err == sql.ErrNoRows {
|
|
|
- return fmt.Errorf("session ID '%s' not found for deletion", id)
|
|
|
- }
|
|
|
- return fmt.Errorf("db.GetSessionByID before delete: %w", err)
|
|
|
- }
|
|
|
- sessionToPublish := s.fromDBItem(dbSess)
|
|
|
- s.mu.Unlock()
|
|
|
-
|
|
|
- s.mu.Lock()
|
|
|
- defer s.mu.Unlock()
|
|
|
- err = s.db.DeleteSession(ctx, id)
|
|
|
- if err != nil {
|
|
|
- return fmt.Errorf("db.DeleteSession: %w", err)
|
|
|
- }
|
|
|
- s.broker.Publish(EventSessionDeleted, sessionToPublish)
|
|
|
- return nil
|
|
|
-}
|
|
|
-
|
|
|
-func (s *service) Subscribe(ctx context.Context) <-chan pubsub.Event[Session] {
|
|
|
- return s.broker.Subscribe(ctx)
|
|
|
-}
|
|
|
-
|
|
|
-func (s *service) fromDBItem(item db.Session) Session {
|
|
|
- var summarizedAt time.Time
|
|
|
- if item.SummarizedAt.Valid {
|
|
|
- parsedTime, err := time.Parse(time.RFC3339Nano, item.SummarizedAt.String)
|
|
|
- if err == nil {
|
|
|
- summarizedAt = parsedTime
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- createdAt, _ := time.Parse(time.RFC3339Nano, item.CreatedAt)
|
|
|
- updatedAt, _ := time.Parse(time.RFC3339Nano, item.UpdatedAt)
|
|
|
-
|
|
|
- return Session{
|
|
|
- ID: item.ID,
|
|
|
- ParentSessionID: item.ParentSessionID.String,
|
|
|
- Title: item.Title,
|
|
|
- MessageCount: item.MessageCount,
|
|
|
- PromptTokens: item.PromptTokens,
|
|
|
- CompletionTokens: item.CompletionTokens,
|
|
|
- Cost: item.Cost,
|
|
|
- Summary: item.Summary.String,
|
|
|
- SummarizedAt: summarizedAt,
|
|
|
- CreatedAt: createdAt,
|
|
|
- UpdatedAt: updatedAt,
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func Create(ctx context.Context, title string) (Session, error) {
|
|
|
- return GetService().Create(ctx, title)
|
|
|
-}
|
|
|
-
|
|
|
-func CreateTaskSession(ctx context.Context, toolCallID, parentSessionID, title string) (Session, error) {
|
|
|
- return GetService().CreateTaskSession(ctx, toolCallID, parentSessionID, title)
|
|
|
-}
|
|
|
-
|
|
|
-func Get(ctx context.Context, id string) (Session, error) {
|
|
|
- return GetService().Get(ctx, id)
|
|
|
-}
|
|
|
-
|
|
|
-func List(ctx context.Context) ([]Session, error) {
|
|
|
- return GetService().List(ctx)
|
|
|
-}
|
|
|
-
|
|
|
-func Update(ctx context.Context, session Session) (Session, error) {
|
|
|
- return GetService().Update(ctx, session)
|
|
|
-}
|
|
|
-
|
|
|
-func Delete(ctx context.Context, id string) error {
|
|
|
- return GetService().Delete(ctx, id)
|
|
|
-}
|
|
|
-
|
|
|
-func Subscribe(ctx context.Context) <-chan pubsub.Event[Session] {
|
|
|
- return GetService().Subscribe(ctx)
|
|
|
-}
|