packages feed

langchain-hs-gemini-0.0.6.0: src/Gemini/Client/Types.hs

{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# OPTIONS_GHC -Wno-partial-fields #-}

{- |
Module      : Gemini.Client.Types
Description : Typed data models for Google Gemini Interactions v1 API and Embeddings
Copyright   : (c) 2025-2026 Tushar Adhatrao
License     : MIT
Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>
Stability   : experimental

Fully-typed OpenAPI data models corresponding to the Google Gemini Interactions v1 API
(<https://ai.google.dev/static/api/interactions-v1.openapi.json>) and Embeddings endpoints.
-}
module Gemini.Client.Types
  ( -- * Interactions
    Interaction (..)
  , InteractionStatus (..)
  , CreateInteractionRequest (..)
  , defaultCreateInteractionRequest
  , createModelInteraction
  , createAgentInteraction

    -- * Inputs and Contents
  , InteractionsInput (..)
  , Content (..)

    -- * Steps
  , Step (..)
  , GoogleSearchResultItem (..)

    -- * Tools
  , Tool (..)
  , functionTool

    -- * Generation and Output
  , GenerationConfig (..)
  , defaultGenerationConfig
  , ResponseFormat (..)
  , jsonResponseFormat
  , Usage (..)
  , ErrorDetail (..)

    -- * Streaming Events
  , InteractionSseEvent (..)
  , StepDeltaData (..)

    -- * Embeddings
  , EmbedContentRequest (..)
  , EmbedContentPayload (..)
  , EmbedPart (..)
  , EmbedContentResponse (..)
  , EmbeddingValues (..)
  , BatchEmbedContentsRequest (..)
  , BatchEmbedRequestItem (..)
  , BatchEmbedContentsResponse (..)
  ) where

import Control.Applicative ((<|>))
import Data.Aeson
import qualified Data.Aeson.KeyMap as KM
import Data.Maybe (catMaybes)
import Data.Text (Text)
import qualified Data.Text as T
import GHC.Generics (Generic)

-- ---------------------------------------------------------------------------
-- Interaction & Status
-- ---------------------------------------------------------------------------

-- | Lifecycle status of an Interaction.
data InteractionStatus
  = StatusInProgress
  | StatusRequiresAction
  | StatusCompleted
  | StatusFailed
  | StatusCancelled
  | StatusIncomplete
  deriving (Eq, Show, Generic)

instance ToJSON InteractionStatus where
  toJSON = \case
    StatusInProgress -> "in_progress"
    StatusRequiresAction -> "requires_action"
    StatusCompleted -> "completed"
    StatusFailed -> "failed"
    StatusCancelled -> "cancelled"
    StatusIncomplete -> "incomplete"

instance FromJSON InteractionStatus where
  parseJSON = withText "InteractionStatus" $ \case
    "in_progress" -> pure StatusInProgress
    "requires_action" -> pure StatusRequiresAction
    "completed" -> pure StatusCompleted
    "failed" -> pure StatusFailed
    "cancelled" -> pure StatusCancelled
    "incomplete" -> pure StatusIncomplete
    other -> fail $ "Unknown InteractionStatus: " ++ T.unpack other

-- | An Interaction resource generated by Gemini.
data Interaction = Interaction
  { interactionId :: !Text
  , interactionObject :: !(Maybe Text)
  , interactionModel :: !(Maybe Text)
  , interactionAgent :: !(Maybe Text)
  , interactionStatus :: !InteractionStatus
  , interactionSteps :: ![Step]
  , interactionUsage :: !(Maybe Usage)
  , interactionCreated :: !(Maybe Text)
  , interactionUpdated :: !(Maybe Text)
  , interactionBackground :: !(Maybe Bool)
  , interactionStore :: !(Maybe Bool)
  , interactionStream :: !(Maybe Bool)
  , interactionSystemInstruction :: !(Maybe Text)
  , interactionTools :: !(Maybe [Tool])
  , interactionGenerationConfig :: !(Maybe GenerationConfig)
  , interactionResponseFormat :: !(Maybe ResponseFormat)
  , interactionPreviousInteractionId :: !(Maybe Text)
  , interactionErrors :: !(Maybe [ErrorDetail])
  }
  deriving (Eq, Show, Generic)

instance FromJSON Interaction where
  parseJSON = withObject "Interaction" $ \o -> do
    interactionId <- o .:? "id" .!= ""
    interactionObject <- o .:? "object"
    interactionModel <- o .:? "model"
    interactionAgent <- o .:? "agent"
    interactionStatus <- o .:? "status" .!= StatusCompleted
    interactionSteps <- o .:? "steps" .!= []
    interactionUsage <- o .:? "usage"
    interactionCreated <- o .:? "created"
    interactionUpdated <- o .:? "updated"
    interactionBackground <- o .:? "background"
    interactionStore <- o .:? "store"
    interactionStream <- o .:? "stream"
    interactionSystemInstruction <- o .:? "system_instruction"
    interactionTools <- o .:? "tools"
    interactionGenerationConfig <- o .:? "generation_config"
    interactionResponseFormat <- o .:? "response_format"
    interactionPreviousInteractionId <- o .:? "previous_interaction_id"
    interactionErrors <- o .:? "errors"
    pure Interaction {..}

instance ToJSON Interaction where
  toJSON Interaction {..} =
    object $
      catMaybes
        [ Just ("id" .= interactionId)
        , ("object" .=) <$> interactionObject
        , ("model" .=) <$> interactionModel
        , ("agent" .=) <$> interactionAgent
        , Just ("status" .= interactionStatus)
        , Just ("steps" .= interactionSteps)
        , ("usage" .=) <$> interactionUsage
        , ("created" .=) <$> interactionCreated
        , ("updated" .=) <$> interactionUpdated
        , ("background" .=) <$> interactionBackground
        , ("store" .=) <$> interactionStore
        , ("stream" .=) <$> interactionStream
        , ("system_instruction" .=) <$> interactionSystemInstruction
        , ("tools" .=) <$> interactionTools
        , ("generation_config" .=) <$> interactionGenerationConfig
        , ("response_format" .=) <$> interactionResponseFormat
        , ("previous_interaction_id" .=) <$> interactionPreviousInteractionId
        , ("errors" .=) <$> interactionErrors
        ]

-- | Diagnostics and error records.
data ErrorDetail = ErrorDetail
  { errorCode :: !(Maybe Text)
  , errorMessage :: !Text
  }
  deriving (Eq, Show, Generic, ToJSON, FromJSON)

-- ---------------------------------------------------------------------------
-- Contents
-- ---------------------------------------------------------------------------

-- | Multimodal content part.
data Content
  = TextContent !Text
  | -- | mime_type, base64 data
    ImageContent !Text !Text
  | -- | mime_type, base64 data
    AudioContent !Text !Text
  | -- | mime_type, base64 data
    DocumentContent !Text !Text
  deriving (Eq, Show, Generic)

instance ToJSON Content where
  toJSON = \case
    TextContent txt ->
      object ["type" .= ("text" :: Text), "text" .= txt]
    ImageContent mime b64 ->
      object ["type" .= ("image" :: Text), "mime_type" .= mime, "data" .= b64]
    AudioContent mime b64 ->
      object ["type" .= ("audio" :: Text), "mime_type" .= mime, "data" .= b64]
    DocumentContent mime b64 ->
      object ["type" .= ("document" :: Text), "mime_type" .= mime, "data" .= b64]

instance FromJSON Content where
  parseJSON = withObject "Content" $ \o -> do
    t <- o .: "type"
    case (t :: Text) of
      "text" -> TextContent <$> o .: "text"
      "image" -> ImageContent <$> o .: "mime_type" <*> o .: "data"
      "audio" -> AudioContent <$> o .: "mime_type" <*> o .: "data"
      "document" -> DocumentContent <$> o .: "mime_type" <*> o .: "data"
      other -> fail $ "Unknown Content type: " ++ T.unpack other

-- ---------------------------------------------------------------------------
-- Steps
-- ---------------------------------------------------------------------------

-- | Single step within an interaction turn.
data Step
  = UserInputStep ![Content]
  | ModelOutputStep ![Content]
  | FunctionCallStep
      { callName :: !Text
      , callArguments :: !Value
      , callId :: !Text
      }
  | FunctionResultStep
      { resultCallId :: !Text
      , resultName :: !(Maybe Text)
      , resultPayload :: !Value
      , resultIsError :: !(Maybe Bool)
      }
  | ThoughtStep
      { thoughtSignature :: !(Maybe Text)
      , thoughtSummary :: !(Maybe [Content])
      }
  | CodeExecutionCallStep
      { codeCallId :: !Text
      , codeCallCode :: !Text
      , codeCallSignature :: !(Maybe Text)
      }
  | CodeExecutionResultStep
      { codeResultCallId :: !Text
      , codeResultOutput :: !Text
      , codeResultIsError :: !(Maybe Bool)
      }
  | GoogleSearchCallStep
      { searchCallId :: !Text
      , searchQuery :: !Text
      , searchType :: !(Maybe Text)
      }
  | GoogleSearchResultStep
      { searchResultCallId :: !Text
      , searchResultItems :: ![GoogleSearchResultItem]
      }
  | GoogleMapsCallStep
      { mapsCallId :: !Text
      , mapsQuery :: !(Maybe Text)
      }
  | GoogleMapsResultStep
      { mapsResultCallId :: !Text
      , mapsResultPayload :: !(Maybe Value)
      }
  | FileSearchCallStep
      { fileSearchCallId :: !Text
      , fileSearchQuery :: !(Maybe Text)
      }
  | FileSearchResultStep
      { fileSearchResultCallId :: !Text
      , fileSearchResultPayload :: !(Maybe Value)
      }
  | UrlContextCallStep
      { urlCallId :: !Text
      , urlQuery :: !(Maybe Text)
      }
  | UrlContextResultStep
      { urlResultCallId :: !Text
      , urlResultPayload :: !(Maybe Value)
      }
  deriving (Eq, Show, Generic)

-- | Google Search result snippet.
data GoogleSearchResultItem = GoogleSearchResultItem
  { searchTitle :: !(Maybe Text)
  , searchUrl :: !(Maybe Text)
  , searchSnippet :: !(Maybe Text)
  }
  deriving (Eq, Show, Generic)

instance FromJSON GoogleSearchResultItem where
  parseJSON = withObject "GoogleSearchResultItem" $ \o -> do
    searchTitle <- o .:? "title"
    searchUrl <- o .:? "url"
    searchSnippet <- o .:? "snippet"
    pure GoogleSearchResultItem {..}

instance ToJSON GoogleSearchResultItem where
  toJSON GoogleSearchResultItem {..} =
    object $
      catMaybes
        [ ("title" .=) <$> searchTitle
        , ("url" .=) <$> searchUrl
        , ("snippet" .=) <$> searchSnippet
        ]

instance ToJSON Step where
  toJSON = \case
    UserInputStep cs ->
      object ["type" .= ("user_input" :: Text), "content" .= cs]
    ModelOutputStep cs ->
      object ["type" .= ("model_output" :: Text), "content" .= cs]
    FunctionCallStep {..} ->
      object
        [ "type" .= ("function_call" :: Text)
        , "name" .= callName
        , "arguments" .= callArguments
        , "id" .= callId
        ]
    FunctionResultStep {..} ->
      object $
        [ "type" .= ("function_result" :: Text)
        , "call_id" .= resultCallId
        , "result" .= resultPayload
        ]
          ++ catMaybes
            [ ("name" .=) <$> resultName
            , ("is_error" .=) <$> resultIsError
            ]
    ThoughtStep {..} ->
      object $
        ("type" .= ("thought" :: Text))
          : catMaybes
            [ ("signature" .=) <$> thoughtSignature
            , ("summary" .=) <$> thoughtSummary
            ]
    CodeExecutionCallStep {..} ->
      object $
        [ "type" .= ("code_execution_call" :: Text)
        , "id" .= codeCallId
        , "arguments" .= object ["code" .= codeCallCode]
        ]
          ++ catMaybes [("signature" .=) <$> codeCallSignature]
    CodeExecutionResultStep {..} ->
      object $
        [ "type" .= ("code_execution_result" :: Text)
        , "call_id" .= codeResultCallId
        , "result" .= codeResultOutput
        ]
          ++ catMaybes [("is_error" .=) <$> codeResultIsError]
    GoogleSearchCallStep {..} ->
      object $
        [ "type" .= ("google_search_call" :: Text)
        , "id" .= searchCallId
        , "arguments" .= object ["query" .= searchQuery]
        ]
          ++ catMaybes [("search_type" .=) <$> searchType]
    GoogleSearchResultStep {..} ->
      object
        [ "type" .= ("google_search_result" :: Text)
        , "call_id" .= searchResultCallId
        , "result" .= searchResultItems
        ]
    GoogleMapsCallStep {..} ->
      object $
        ("type" .= ("google_maps_call" :: Text))
          : ("id" .= mapsCallId)
          : catMaybes [("query" .=) <$> mapsQuery]
    GoogleMapsResultStep {..} ->
      object $
        ("type" .= ("google_maps_result" :: Text))
          : ("call_id" .= mapsResultCallId)
          : catMaybes [("result" .=) <$> mapsResultPayload]
    FileSearchCallStep {..} ->
      object $
        ("type" .= ("file_search_call" :: Text))
          : ("id" .= fileSearchCallId)
          : catMaybes [("query" .=) <$> fileSearchQuery]
    FileSearchResultStep {..} ->
      object $
        ("type" .= ("file_search_result" :: Text))
          : ("call_id" .= fileSearchResultCallId)
          : catMaybes [("result" .=) <$> fileSearchResultPayload]
    UrlContextCallStep {..} ->
      object $
        ("type" .= ("url_context_call" :: Text))
          : ("id" .= urlCallId)
          : catMaybes [("query" .=) <$> urlQuery]
    UrlContextResultStep {..} ->
      object $
        ("type" .= ("url_context_result" :: Text))
          : ("call_id" .= urlResultCallId)
          : catMaybes [("result" .=) <$> urlResultPayload]

instance FromJSON Step where
  parseJSON = withObject "Step" $ \o -> do
    t <- o .: "type"
    case (t :: Text) of
      "user_input" -> UserInputStep <$> (o .:? "content" .!= [])
      "model_output" -> ModelOutputStep <$> (o .:? "content" .!= [])
      "function_call" -> do
        callName <- o .: "name"
        callArguments <- o .:? "arguments" .!= object []
        callId <- o .:? "id" .!= ""
        pure FunctionCallStep {..}
      "function_result" -> do
        resultCallId <- o .: "call_id"
        resultName <- o .:? "name"
        resultPayload <- o .: "result"
        resultIsError <- o .:? "is_error"
        pure FunctionResultStep {..}
      "thought" -> do
        thoughtSignature <- o .:? "signature"
        thoughtSummary <- o .:? "summary"
        pure ThoughtStep {..}
      "code_execution_call" -> do
        codeCallId <- o .:? "id" .!= ""
        args <- o .:? "arguments" .!= object []
        codeCallCode <- case args of
          Object ao -> ao .:? "code" .!= ""
          _ -> pure ""
        codeCallSignature <- o .:? "signature"
        pure CodeExecutionCallStep {..}
      "code_execution_result" -> do
        codeResultCallId <- o .: "call_id"
        codeResultOutput <- o .:? "result" .!= ""
        codeResultIsError <- o .:? "is_error"
        pure CodeExecutionResultStep {..}
      "google_search_call" -> do
        searchCallId <- o .:? "id" .!= ""
        args <- o .:? "arguments" .!= object []
        searchQuery <- case args of
          Object ao -> ao .:? "query" .!= ""
          _ -> pure ""
        searchType <- o .:? "search_type"
        pure GoogleSearchCallStep {..}
      "google_search_result" -> do
        searchResultCallId <- o .: "call_id"
        searchResultItems <- o .:? "result" .!= []
        pure GoogleSearchResultStep {..}
      "google_maps_call" -> do
        mapsCallId <- o .:? "id" .!= ""
        mapsQuery <- o .:? "query"
        pure GoogleMapsCallStep {..}
      "google_maps_result" -> do
        mapsResultCallId <- o .: "call_id"
        mapsResultPayload <- o .:? "result"
        pure GoogleMapsResultStep {..}
      "file_search_call" -> do
        fileSearchCallId <- o .:? "id" .!= ""
        fileSearchQuery <- o .:? "query"
        pure FileSearchCallStep {..}
      "file_search_result" -> do
        fileSearchResultCallId <- o .: "call_id"
        fileSearchResultPayload <- o .:? "result"
        pure FileSearchResultStep {..}
      "url_context_call" -> do
        urlCallId <- o .:? "id" .!= ""
        urlQuery <- o .:? "query"
        pure UrlContextCallStep {..}
      "url_context_result" -> do
        urlResultCallId <- o .: "call_id"
        urlResultPayload <- o .:? "result"
        pure UrlContextResultStep {..}
      other -> fail $ "Unknown Step type: " ++ T.unpack other

-- ---------------------------------------------------------------------------
-- InteractionsInput
-- ---------------------------------------------------------------------------

-- | Flexible input format for Interaction creation.
data InteractionsInput
  = TextInput !Text
  | ContentInput !Content
  | ContentListInput ![Content]
  | StepListInput ![Step]
  deriving (Eq, Show, Generic)

instance ToJSON InteractionsInput where
  toJSON = \case
    TextInput t -> toJSON t
    ContentInput c -> toJSON c
    ContentListInput cs -> toJSON cs
    StepListInput ss -> toJSON ss

instance FromJSON InteractionsInput where
  parseJSON v = case v of
    String s -> pure $ TextInput s
    Array arr ->
      (StepListInput <$> parseJSON (Array arr))
        <|> (ContentListInput <$> parseJSON (Array arr))
    Object _ -> ContentInput <$> parseJSON v
    _ -> fail "Invalid InteractionsInput format"

-- ---------------------------------------------------------------------------
-- Tools
-- ---------------------------------------------------------------------------

-- | Built-in or custom tool declared for the model.
data Tool
  = FunctionTool
      { functionName :: !Text
      , functionDescription :: !(Maybe Text)
      , functionParameters :: !(Maybe Value)
      }
  | GoogleSearchTool
  | GoogleMapsTool
      { mapsLatitude :: !(Maybe Double)
      , mapsLongitude :: !(Maybe Double)
      }
  | CodeExecutionTool
  | FileSearchTool
      { fileSearchStoreNames :: !(Maybe [Text])
      }
  | UrlContextTool
  deriving (Eq, Show, Generic)

-- | Construct a standard function tool.
functionTool :: Text -> Maybe Text -> Maybe Value -> Tool
functionTool = FunctionTool

instance ToJSON Tool where
  toJSON = \case
    FunctionTool {..} ->
      object $
        [ "type" .= ("function" :: Text)
        , "name" .= functionName
        ]
          ++ catMaybes
            [ ("description" .=) <$> functionDescription
            , ("parameters" .=) <$> functionParameters
            ]
    GoogleSearchTool ->
      object ["type" .= ("google_search" :: Text)]
    GoogleMapsTool {..} ->
      object $
        ("type" .= ("google_maps" :: Text))
          : catMaybes
            [ ("latitude" .=) <$> mapsLatitude
            , ("longitude" .=) <$> mapsLongitude
            ]
    CodeExecutionTool ->
      object ["type" .= ("code_execution" :: Text)]
    FileSearchTool {..} ->
      object $
        ("type" .= ("file_search" :: Text))
          : catMaybes [("file_search_store_names" .=) <$> fileSearchStoreNames]
    UrlContextTool ->
      object ["type" .= ("url_context" :: Text)]

instance FromJSON Tool where
  parseJSON = withObject "Tool" $ \o -> do
    t <- o .: "type"
    case (t :: Text) of
      "function" -> do
        functionName <- o .: "name"
        functionDescription <- o .:? "description"
        functionParameters <- o .:? "parameters"
        pure FunctionTool {..}
      "google_search" -> pure GoogleSearchTool
      "google_maps" -> do
        mapsLatitude <- o .:? "latitude"
        mapsLongitude <- o .:? "longitude"
        pure GoogleMapsTool {..}
      "code_execution" -> pure CodeExecutionTool
      "file_search" -> do
        fileSearchStoreNames <- o .:? "file_search_store_names"
        pure FileSearchTool {..}
      "url_context" -> pure UrlContextTool
      other -> fail $ "Unknown Tool type: " ++ T.unpack other

-- ---------------------------------------------------------------------------
-- GenerationConfig & ResponseFormat
-- ---------------------------------------------------------------------------

-- | Model generation parameters.
data GenerationConfig = GenerationConfig
  { maxOutputTokens :: !(Maybe Int)
  , seed :: !(Maybe Int)
  , stopSequences :: !(Maybe [Text])
  , thinkingLevel :: !(Maybe Text)
  , thinkingSummaries :: !(Maybe Bool)
  , toolChoice :: !(Maybe Text)
  }
  deriving (Eq, Show, Generic)

-- | Default generation configuration with all options unset.
defaultGenerationConfig :: GenerationConfig
defaultGenerationConfig = GenerationConfig Nothing Nothing Nothing Nothing Nothing Nothing

instance ToJSON GenerationConfig where
  toJSON GenerationConfig {..} =
    object $
      catMaybes
        [ ("max_output_tokens" .=) <$> maxOutputTokens
        , ("seed" .=) <$> seed
        , ("stop_sequences" .=) <$> stopSequences
        , ("thinking_level" .=) <$> thinkingLevel
        , ("thinking_summaries" .=) <$> thinkingSummaries
        , ("tool_choice" .=) <$> toolChoice
        ]

instance FromJSON GenerationConfig where
  parseJSON = withObject "GenerationConfig" $ \o -> do
    maxOutputTokens <- o .:? "max_output_tokens"
    seed <- o .:? "seed"
    stopSequences <- o .:? "stop_sequences"
    thinkingLevel <- o .:? "thinking_level"
    thinkingSummaries <- o .:? "thinking_summaries"
    toolChoice <- o .:? "tool_choice"
    pure GenerationConfig {..}

-- | Structured output format specification.
data ResponseFormat
  = TextResponseFormat
  { responseMimeType :: !(Maybe Text)
  , responseSchema :: !(Maybe Value)
  }
  deriving (Eq, Show, Generic)

-- | Construct a JSON Schema response format constraint.
jsonResponseFormat :: Value -> ResponseFormat
jsonResponseFormat s = TextResponseFormat (Just "application/json") (Just s)

instance ToJSON ResponseFormat where
  toJSON (TextResponseFormat mime sch) =
    object $
      ("type" .= ("text" :: Text))
        : catMaybes
          [ ("mime_type" .=) <$> mime
          , ("schema" .=) <$> sch
          ]

instance FromJSON ResponseFormat where
  parseJSON = withObject "ResponseFormat" $ \o -> do
    responseMimeType <- o .:? "mime_type"
    responseSchema <- o .:? "schema"
    pure TextResponseFormat {..}

-- ---------------------------------------------------------------------------
-- Usage
-- ---------------------------------------------------------------------------

-- | Token usage statistics.
data Usage = Usage
  { totalInputTokens :: !(Maybe Int)
  , totalOutputTokens :: !(Maybe Int)
  , totalTokens :: !(Maybe Int)
  , totalThoughtTokens :: !(Maybe Int)
  , totalToolUseTokens :: !(Maybe Int)
  , totalCachedTokens :: !(Maybe Int)
  }
  deriving (Eq, Show, Generic)

instance FromJSON Usage where
  parseJSON = withObject "Usage" $ \o -> do
    totalInputTokens <- o .:? "total_input_tokens"
    totalOutputTokens <- o .:? "total_output_tokens"
    totalTokens <- o .:? "total_tokens"
    totalThoughtTokens <- o .:? "total_thought_tokens"
    totalToolUseTokens <- o .:? "total_tool_use_tokens"
    totalCachedTokens <- o .:? "total_cached_tokens"
    pure Usage {..}

instance ToJSON Usage where
  toJSON Usage {..} =
    object $
      catMaybes
        [ ("total_input_tokens" .=) <$> totalInputTokens
        , ("total_output_tokens" .=) <$> totalOutputTokens
        , ("total_tokens" .=) <$> totalTokens
        , ("total_thought_tokens" .=) <$> totalThoughtTokens
        , ("total_tool_use_tokens" .=) <$> totalToolUseTokens
        , ("total_cached_tokens" .=) <$> totalCachedTokens
        ]

-- ---------------------------------------------------------------------------
-- CreateInteractionRequest
-- ---------------------------------------------------------------------------

-- | Request envelope for POST \/v1\/interactions.
data CreateInteractionRequest = CreateInteractionRequest
  { reqModel :: !(Maybe Text)
  , reqAgent :: !(Maybe Text)
  , reqInput :: !InteractionsInput
  , reqSystemInstruction :: !(Maybe Text)
  , reqTools :: !(Maybe [Tool])
  , reqGenerationConfig :: !(Maybe GenerationConfig)
  , reqResponseFormat :: !(Maybe ResponseFormat)
  , reqPreviousInteractionId :: !(Maybe Text)
  , reqBackground :: !(Maybe Bool)
  , reqStore :: !(Maybe Bool)
  , reqStream :: !(Maybe Bool)
  }
  deriving (Eq, Show, Generic)

-- | Construct a base request.
defaultCreateInteractionRequest :: InteractionsInput -> CreateInteractionRequest
defaultCreateInteractionRequest input =
  CreateInteractionRequest
    { reqModel = Nothing
    , reqAgent = Nothing
    , reqInput = input
    , reqSystemInstruction = Nothing
    , reqTools = Nothing
    , reqGenerationConfig = Nothing
    , reqResponseFormat = Nothing
    , reqPreviousInteractionId = Nothing
    , reqBackground = Nothing
    , reqStore = Nothing
    , reqStream = Nothing
    }

-- | Helper to create a model-based interaction.
createModelInteraction :: Text -> InteractionsInput -> CreateInteractionRequest
createModelInteraction modelName input =
  (defaultCreateInteractionRequest input) {reqModel = Just modelName}

-- | Helper to create an agent-based interaction.
createAgentInteraction :: Text -> InteractionsInput -> CreateInteractionRequest
createAgentInteraction agentName input =
  (defaultCreateInteractionRequest input) {reqAgent = Just agentName}

instance ToJSON CreateInteractionRequest where
  toJSON CreateInteractionRequest {..} =
    object $
      ("input" .= reqInput)
        : catMaybes
          [ ("model" .=) <$> reqModel
          , ("agent" .=) <$> reqAgent
          , ("system_instruction" .=) <$> reqSystemInstruction
          , ("tools" .=) <$> reqTools
          , ("generation_config" .=) <$> reqGenerationConfig
          , ("response_format" .=) <$> reqResponseFormat
          , ("previous_interaction_id" .=) <$> reqPreviousInteractionId
          , ("background" .=) <$> reqBackground
          , ("store" .=) <$> reqStore
          , ("stream" .=) <$> reqStream
          ]

instance FromJSON CreateInteractionRequest where
  parseJSON = withObject "CreateInteractionRequest" $ \o -> do
    reqModel <- o .:? "model"
    reqAgent <- o .:? "agent"
    reqInput <- o .: "input"
    reqSystemInstruction <- o .:? "system_instruction"
    reqTools <- o .:? "tools"
    reqGenerationConfig <- o .:? "generation_config"
    reqResponseFormat <- o .:? "response_format"
    reqPreviousInteractionId <- o .:? "previous_interaction_id"
    reqBackground <- o .:? "background"
    reqStore <- o .:? "store"
    reqStream <- o .:? "stream"
    pure CreateInteractionRequest {..}

-- ---------------------------------------------------------------------------
-- Server-Sent Events (SSE) Streaming
-- ---------------------------------------------------------------------------

-- | Incremental delta updates emitted during streaming.
data StepDeltaData
  = TextDelta !Text
  | ArgumentsDelta !Text
  | ThoughtSignatureDelta !Text
  | OtherDelta !Text !Value
  deriving (Eq, Show, Generic)

instance FromJSON StepDeltaData where
  parseJSON = withObject "StepDeltaData" $ \o -> do
    t <- o .: "type"
    case (t :: Text) of
      "text" -> TextDelta <$> o .: "text"
      "arguments" -> ArgumentsDelta <$> o .: "arguments"
      "thought_signature" -> ThoughtSignatureDelta <$> o .: "signature"
      other -> pure $ OtherDelta other (Object o)

instance ToJSON StepDeltaData where
  toJSON = \case
    TextDelta txt -> object ["type" .= ("text" :: Text), "text" .= txt]
    ArgumentsDelta args -> object ["type" .= ("arguments" :: Text), "arguments" .= args]
    ThoughtSignatureDelta sig -> object ["type" .= ("thought_signature" :: Text), "signature" .= sig]
    OtherDelta _ val -> val

-- | SSE event types dispatched on the interaction stream.
data InteractionSseEvent
  = SseInteractionCreated !Interaction
  | SseInteractionStatusUpdate !Text !InteractionStatus
  | SseStepStart !Int !Step
  | SseStepDelta !Int !StepDeltaData
  | SseStepStop !Int
  | SseInteractionCompleted !Interaction
  | SseError !Text !Text
  | SseOther !Text !Value
  deriving (Eq, Show, Generic)

instance FromJSON InteractionSseEvent where
  parseJSON = withObject "InteractionSseEvent" $ \o -> do
    evType <- o .: "event_type"
    case (evType :: Text) of
      "interaction.created" -> SseInteractionCreated <$> o .: "interaction"
      "interaction.completed" -> SseInteractionCompleted <$> o .: "interaction"
      "interaction.status_update" ->
        SseInteractionStatusUpdate <$> o .: "interaction_id" <*> o .: "status"
      "step.start" -> SseStepStart <$> o .: "index" <*> o .: "step"
      "step.delta" -> SseStepDelta <$> o .: "index" <*> o .: "delta"
      "step.stop" -> SseStepStop <$> o .: "index"
      "error" -> do
        errObj <- o .: "error"
        c <- errObj .:? "code" .!= ""
        m <- errObj .:? "message" .!= ""
        pure $ SseError c m
      other -> pure $ SseOther other (Object o)

instance ToJSON InteractionSseEvent where
  toJSON = \case
    SseInteractionCreated inter ->
      object ["event_type" .= ("interaction.created" :: Text), "interaction" .= inter]
    SseInteractionCompleted inter ->
      object ["event_type" .= ("interaction.completed" :: Text), "interaction" .= inter]
    SseInteractionStatusUpdate iid st ->
      object
        ["event_type" .= ("interaction.status_update" :: Text), "interaction_id" .= iid, "status" .= st]
    SseStepStart idx step ->
      object ["event_type" .= ("step.start" :: Text), "index" .= idx, "step" .= step]
    SseStepDelta idx d ->
      object ["event_type" .= ("step.delta" :: Text), "index" .= idx, "delta" .= d]
    SseStepStop idx ->
      object ["event_type" .= ("step.stop" :: Text), "index" .= idx]
    SseError code msg ->
      object ["event_type" .= ("error" :: Text), "error" .= object ["code" .= code, "message" .= msg]]
    SseOther ev val -> case val of
      Object o -> Object (KM.insert "event_type" (String ev) o)
      _ -> object ["event_type" .= ev, "data" .= val]

-- ---------------------------------------------------------------------------
-- Embeddings
-- ---------------------------------------------------------------------------

-- | Single text part within embedding payload.
newtype EmbedPart = EmbedPart {partText :: Text}
  deriving (Eq, Show, Generic)

instance ToJSON EmbedPart where
  toJSON (EmbedPart t) = object ["text" .= t]

instance FromJSON EmbedPart where
  parseJSON = withObject "EmbedPart" $ \o -> EmbedPart <$> o .: "text"

-- | Content payload container for embedding.
newtype EmbedContentPayload = EmbedContentPayload {payloadParts :: [EmbedPart]}
  deriving (Eq, Show, Generic)

instance ToJSON EmbedContentPayload where
  toJSON (EmbedContentPayload ps) = object ["parts" .= ps]

instance FromJSON EmbedContentPayload where
  parseJSON = withObject "EmbedContentPayload" $ \o -> EmbedContentPayload <$> o .: "parts"

-- | Single embedContent request body.
newtype EmbedContentRequest = EmbedContentRequest {requestContent :: EmbedContentPayload}
  deriving (Eq, Show, Generic)

instance ToJSON EmbedContentRequest where
  toJSON (EmbedContentRequest p) = object ["content" .= p]

instance FromJSON EmbedContentRequest where
  parseJSON = withObject "EmbedContentRequest" $ \o -> EmbedContentRequest <$> o .: "content"

-- | Embedding vector result.
newtype EmbeddingValues = EmbeddingValues {embeddingValues :: [Float]}
  deriving (Eq, Show, Generic)

instance FromJSON EmbeddingValues where
  parseJSON = withObject "EmbeddingValues" $ \o -> EmbeddingValues <$> o .: "values"

instance ToJSON EmbeddingValues where
  toJSON (EmbeddingValues vs) = object ["values" .= vs]

-- | Response from embedContent.
newtype EmbedContentResponse = EmbedContentResponse {responseEmbedding :: EmbeddingValues}
  deriving (Eq, Show, Generic)

instance FromJSON EmbedContentResponse where
  parseJSON = withObject "EmbedContentResponse" $ \o -> EmbedContentResponse <$> o .: "embedding"

instance ToJSON EmbedContentResponse where
  toJSON (EmbedContentResponse ev) = object ["embedding" .= ev]

-- | Single item in batchEmbedContents.
data BatchEmbedRequestItem = BatchEmbedRequestItem
  { itemModel :: !Text
  , itemContent :: !EmbedContentPayload
  }
  deriving (Eq, Show, Generic)

instance ToJSON BatchEmbedRequestItem where
  toJSON BatchEmbedRequestItem {..} =
    object ["model" .= itemModel, "content" .= itemContent]

instance FromJSON BatchEmbedRequestItem where
  parseJSON = withObject "BatchEmbedRequestItem" $ \o -> do
    itemModel <- o .: "model"
    itemContent <- o .: "content"
    pure BatchEmbedRequestItem {..}

-- | Batch embed request body.
newtype BatchEmbedContentsRequest = BatchEmbedContentsRequest
  { batchRequests :: [BatchEmbedRequestItem]
  }
  deriving (Eq, Show, Generic)

instance ToJSON BatchEmbedContentsRequest where
  toJSON (BatchEmbedContentsRequest reqs) = object ["requests" .= reqs]

instance FromJSON BatchEmbedContentsRequest where
  parseJSON = withObject "BatchEmbedContentsRequest" $ \o ->
    BatchEmbedContentsRequest <$> o .: "requests"

-- | Response from batchEmbedContents.
newtype BatchEmbedContentsResponse = BatchEmbedContentsResponse
  { batchEmbeddings :: [EmbeddingValues]
  }
  deriving (Eq, Show, Generic)

instance FromJSON BatchEmbedContentsResponse where
  parseJSON = withObject "BatchEmbedContentsResponse" $ \o ->
    BatchEmbedContentsResponse <$> o .: "embeddings"

instance ToJSON BatchEmbedContentsResponse where
  toJSON (BatchEmbedContentsResponse evs) = object ["embeddings" .= evs]