2026-10-14 09:40:12 -07:00
package store
import (
"context"
"database/sql"
2026-10-14 20:07:26 -07:00
"encoding/json"
2026-10-14 09:40:12 -07:00
"errors"
"time"
"g.fc.run/misc/byre/internal/api"
)
type QueuedTurn struct {
SessionID string
2026-10-14 21:06:30 -07:00
TaskID string
2026-10-14 09:40:12 -07:00
Vendor api . AgentSessionVendor
Actor string
Text string
2026-10-14 20:07:26 -07:00
Images [] string
2026-10-14 09:40:12 -07:00
CreatedAt time . Time
}
func ( s * Store ) SessionStarted ( ctx context . Context , sessionID string ) ( bool , error ) {
started := false
err := s . db . QueryRowContext ( ctx , `SELECT EXISTS (SELECT 1 FROM events WHERE agent_session_id = ? AND kind = 'user.message')` , sessionID ). Scan ( & started )
return started , err
}
func ( s * Store ) CountRunning ( ctx context . Context ) ( map [ api . AgentSessionVendor ] int , error ) {
2026-10-15 09:40:30 -07:00
rows , err := s . db . QueryContext ( ctx , `SELECT vendor, COUNT(*) FROM agent_sessions WHERE state = ? AND owner = '' GROUP BY vendor` , string ( api . AgentSessionStateRunning ))
2026-10-14 09:40:12 -07:00
if err != nil {
return nil , err
}
defer rows . Close ()
counts := map [ api . AgentSessionVendor ] int {}
for rows . Next () {
vendor := ""
n := 0
err = rows . Scan ( & vendor , & n )
if err != nil {
return nil , err
}
counts [ api . AgentSessionVendor ( vendor )] = n
}
return counts , rows . Err ()
}
func ( s * Store ) EnqueueTurn ( ctx context . Context , q QueuedTurn ) error {
2026-10-14 20:07:26 -07:00
images , err := json . Marshal ( append ([] string {}, q . Images ... ))
if err != nil {
return err
}
2026-10-14 09:40:12 -07:00
return s . write ( ctx , func ( tx * sql . Tx , emit emitFunc ) error {
2026-10-14 20:07:26 -07:00
_ , err := tx . ExecContext ( ctx , `INSERT INTO turn_queue (session_id, actor, text, images, created_at) VALUES (?, ?, ?, ?, ?)` ,
q . SessionID , q . Actor , q . Text , string ( images ), formatTime ( q . CreatedAt ))
2026-10-14 09:40:12 -07:00
return err
})
}
func ( s * Store ) QueuedTurns ( ctx context . Context ) ([] QueuedTurn , error ) {
rows , err := s . db . QueryContext ( ctx , `
2026-10-14 21:06:30 -07:00
SELECT q.session_id, s.task_id, s.vendor, q.actor, q.text, q.images, q.created_at
2026-10-14 09:40:12 -07:00
FROM turn_queue q JOIN agent_sessions s ON s.id = q.session_id
ORDER BY q.created_at
` )
if err != nil {
return nil , err
}
defer rows . Close ()
turns := [] QueuedTurn {}
for rows . Next () {
q := QueuedTurn {}
vendor := ""
2026-10-14 20:07:26 -07:00
images := ""
2026-10-14 09:40:12 -07:00
createdAt := ""
2026-10-14 21:06:30 -07:00
err = rows . Scan ( & q . SessionID , & q . TaskID , & vendor , & q . Actor , & q . Text , & images , & createdAt )
2026-10-14 20:07:26 -07:00
if err != nil {
return nil , err
}
err = json . Unmarshal ([] byte ( images ), & q . Images )
2026-10-14 09:40:12 -07:00
if err != nil {
return nil , err
}
q . Vendor = api . AgentSessionVendor ( vendor )
q . CreatedAt , err = parseTime ( createdAt )
if err != nil {
return nil , err
}
turns = append ( turns , q )
}
return turns , rows . Err ()
}
func ( s * Store ) DeleteQueuedTurn ( ctx context . Context , sessionID string ) error {
return s . write ( ctx , func ( tx * sql . Tx , emit emitFunc ) error {
res , err := tx . ExecContext ( ctx , `DELETE FROM turn_queue WHERE session_id = ?` , sessionID )
if err != nil {
return err
}
n , err := res . RowsAffected ()
if err != nil {
return err
}
if n == 0 {
return errors . Join ( ErrNotFound , errors . New ( "no queued turn for session " + sessionID ))
}
return nil
})
}