packages feed

kioku-cli-0.1.0.0: src/Kioku/Cli/Commands/DemoSession.hs

module Kioku.Cli.Commands.DemoSession
  ( DemoSessionOptions (..),
    demoSessionOptionsParser,
    runDemoSession,
  )
where

import Data.Text qualified as Text
import Data.Time (getCurrentTime)
import Kioku.App (runAppIO, withNoopAppEnv)
import Kioku.Cli.Commands.Demo (demoScope)
import Kioku.Cli.Options (redactConnectionString, yesWriteEventsFlag)
import Kioku.Id (genSessionId, idText)
import Kioku.Session qualified as Session
import Kioku.Session.Domain (CompleteSessionData (..), RecordTurnData (..), StartSessionData (..))
import Kioku.Session.ReadModel (SessionRow (..), TurnRow (..))
import Kiroku.Store.Connection (defaultConnectionSettings)
import Options.Applicative
import System.Environment (lookupEnv)

data DemoSessionOptions = DemoSessionOptions
  deriving stock (Eq, Show)

demoSessionOptionsParser :: Parser DemoSessionOptions
demoSessionOptionsParser = DemoSessionOptions <$ yesWriteEventsFlag

runDemoSession :: DemoSessionOptions -> IO ()
runDemoSession DemoSessionOptions = do
  connStr <- requireEnv "PG_CONNECTION_STRING"
  putStrLn "kioku demo-session appends permanent session events (kioku has no delete)."
  putStrLn ("Target: " <> Text.unpack (redactConnectionString (Text.pack connStr)))
  putStrLn "Scope:  kioku_demo/demo/demo"
  putStrLn "Note:   completing this session schedules a distillation timer; a running worker will process it (an LLM call)."
  withNoopAppEnv (defaultConnectionSettings (Text.pack connStr)) \env -> do
    sid <- genSessionId
    now <- getCurrentTime
    let scope = demoScope
        startPayload =
          StartSessionData
            { sessionId = sid,
              agentId = "demo-agent",
              focus = "demo",
              scope = scope,
              subjectRef = Just "demo",
              previousSessionId = Nothing,
              parentSessionId = Nothing,
              delegationDepth = 0,
              startedAt = now
            }
        turnPayload =
          RecordTurnData
            { sessionId = sid,
              turnId = idText sid <> "-turn-1",
              turnIndex = 1,
              role = "user",
              content = "Please remember I prefer concise answers.",
              toolSummary = Nothing,
              promptTokens = Just 7,
              outputTokens = Nothing,
              recordedAt = now
            }
        completePayload =
          CompleteSessionData
            { sessionId = sid,
              completedAt = now,
              modelUsed = Just "demo-model",
              summary = Just "Demo session completed"
            }
    result <- runAppIO env do
      startResult <- Session.start startPayload
      turnResult <- Session.recordTurn turnPayload
      completeResult <- Session.complete completePayload
      rowResult <- Session.getById sid
      turnsResult <- Session.getTurns sid
      pure (startResult, turnResult, completeResult, rowResult, turnsResult)
    case result of
      Left storeErr -> ioError (userError ("kioku session demo store error: " <> show storeErr))
      Right (Left writeErr, _, _, _, _) -> ioError (userError ("kioku session demo start error: " <> show writeErr))
      Right (_, Left writeErr, _, _, _) -> ioError (userError ("kioku session demo turn error: " <> show writeErr))
      Right (_, _, Left writeErr, _, _) -> ioError (userError ("kioku session demo complete error: " <> show writeErr))
      Right (_, _, _, Left readErr, _) -> ioError (userError ("kioku session demo read error: " <> show readErr))
      Right (_, _, _, _, Left readErr) -> ioError (userError ("kioku session demo turns read error: " <> show readErr))
      Right (_, _, _, Right Nothing, Right _) -> ioError (userError "kioku session demo did not project the session row")
      Right (_, _, _, Right (Just row), Right turns) -> do
        putStrLn ("Recorded session " <> Text.unpack (idText sid) <> " with status " <> Text.unpack row.status)
        mapM_ printTurn turns

requireEnv :: String -> IO String
requireEnv name = do
  found <- lookupEnv name
  case found of
    Just value -> pure value
    Nothing -> ioError (userError (name <> " is not set"))

printTurn :: TurnRow -> IO ()
printTurn turn =
  putStrLn $
    "- turn "
      <> show turn.turnIndex
      <> " "
      <> Text.unpack turn.role
      <> ": "
      <> Text.unpack turn.content