packages feed

baikai-0.2.0.0: src/Baikai/Response.hs

-- | The provider response envelope.
--
-- A 'Response' wraps the model's assistant payload ('message') with
-- metadata baikai owns: the originating 'Api' tag (now typed via
-- 'Baikai.Api'), the input 'Model' echoed back, the provider name,
-- an optional 'responseId', and the measured 'latencyMs'. The
-- 'message' carries only assistant content blocks, 'Usage' (with
-- 'Cost' embedded), 'StopReason', optional error text, and timestamp.
--
-- 'flattenAssistantBlocks' is a convenience accessor that flattens
-- the message's content blocks.
module Baikai.Response
  ( Response (..),
    _Response,
    responseMessage,
    flattenAssistantBlocks,
  )
where

import Baikai.Api (Api (..))
import Baikai.Content (AssistantContent)
import Baikai.Error (BaikaiError)
import Baikai.Message (AssistantPayload (..), Message (..))
import Baikai.Model (Model, _Model)
import Baikai.StopReason (StopReason (..))
import Baikai.Usage (_Usage)
import Data.Text (Text)
import Data.Time (UTCTime)
import Data.Vector (Vector)
import Data.Vector qualified as V
import GHC.Generics (Generic)

data Response = Response
  { -- | Assistant-only payload returned by the provider.
    message :: !AssistantPayload,
    model :: !Model,
    api :: !Api,
    provider :: !Text,
    responseId :: !(Maybe Text),
    latencyMs :: !Integer,
    -- | Structured error detail when the call failed in-band
    -- (@stopReason = ErrorReason@): the category, HTTP status, and any
    -- retry-after hint. 'Nothing' on success or when the provider could
    -- not classify the failure. Lets a caller branch on
    -- 'Baikai.Error.category' / 'Baikai.Error.isRetryable' without
    -- parsing 'errorMessage' text.
    errorInfo :: !(Maybe BaikaiError)
  }
  deriving stock (Eq, Show, Generic)

-- | A blank assistant turn at epoch start. Useful as a fixture base
-- for tests and as the default in error paths where no message was
-- received.
_Response :: Response
_Response =
  Response
    { message =
        AssistantPayload
          { content = V.empty,
            usage = _Usage,
            stopReason = Stop,
            errorMessage = Nothing,
            timestamp = read "2000-01-01 00:00:00 UTC" :: UTCTime
          },
      model = _Model,
      api = Custom "",
      provider = "",
      responseId = Nothing,
      latencyMs = 0,
      errorInfo = Nothing
    }

-- | Wrap the response payload as a conversation 'AssistantMessage'.
responseMessage :: Response -> Message
responseMessage r = AssistantMessage (message r)

-- | Pull the response's assistant content blocks out.
flattenAssistantBlocks :: Response -> Vector AssistantContent
flattenAssistantBlocks Response {message = AssistantPayload {content = c}} = c