packages feed

openai-2.0.0: src/OpenAI/V1/Responses.hs

-- | /v1/responses
--
-- Streaming is not implemented here; this covers JSON responses only.
module OpenAI.V1.Responses
    ( -- * Main types
      CreateResponse(..)
    , _CreateResponse
    , Input(..)
    , InputItem(..)
    , InputRole(..)
    , InputContent(..)
    , OutputItem(..)
    , OutputMessage(..)
    , OutputContent(..)
    , FunctionToolCall(..)
    , FunctionToolCallOutput(..)
    , WebSearchToolCall(..)
    , FileSearchToolCall(..)
    , FileSearchResult(..)
    , CodeInterpreterToolCall(..)
    , CodeInterpreterOutput(..)
    , WebSearchAction(..)
    , WebSearchSource(..)
    , Annotation(..)
    , ReasoningItem(..)
    , SummaryPart(..)
    , ReasoningText(..)
    , ResponseStreamEvent(..)
    , ResponseObject(..)
    , ResponseUsage(..)
    , InputTokensDetails(..)
    , OutputTokensDetails(..)
      -- * Constants
    , statusIncomplete
    , statusCompleted
      -- * Servant
    , API
    ) where

import Data.Aeson (Object)
import qualified Data.Aeson as Aeson
import OpenAI.Prelude hiding (Input(..))
-- no TH; inline JSON instances for payloads
import OpenAI.V1.ListOf (ListOf)
import OpenAI.V1.Models (Model)
import qualified Data.Aeson.Key as Key
import qualified Data.Aeson.KeyMap as KeyMap
import qualified Data.Vector as Vector
import OpenAI.V1.Tool
    ( Tool
    , ToolChoice
    , toolChoiceAutoText
    , toolChoiceNoneText
    , toolChoiceRequiredText
    , toolChoiceToResponsesValue
    , toolToResponsesValue
    , unflattenToolValue
    )

-- | Status constants for function call outputs
statusIncomplete, statusCompleted :: Text
statusIncomplete = "incomplete"
statusCompleted = "completed"

-- | Input for the Responses API: a list of input items
newtype Input = Input (Vector InputItem)
    deriving stock (Generic, Show)

instance ToJSON Input where
    toJSON (Input xs) = toJSON xs

instance FromJSON Input where
    parseJSON v = Input <$> parseJSON v

-- | Role of an input message
data InputRole = User | System | Developer
    deriving stock (Generic, Show)

instance FromJSON InputRole where
    parseJSON = genericParseJSON aesonOptions

instance ToJSON InputRole where
    toJSON = genericToJSON aesonOptions

-- | Content parts for input messages
data InputContent
    = Input_Text { text :: Text }
    | Input_Image { image_url :: Maybe Text, file_id :: Maybe Text, detail :: Maybe Text }
    | Input_File { file_id :: Maybe Text, filename :: Maybe Text, file_url :: Maybe Text, file_data :: Maybe Text }
    | Input_Audio { input_audio :: Object }
    deriving stock (Generic, Show)

inputContentOptions :: Options
inputContentOptions =
    aesonOptions
        { sumEncoding = TaggedObject{ tagFieldName = "type", contentsFieldName = "" }
        , tagSingleConstructors = True
        -- Keep constructor names like "Input_Text" -> "input_text"
        , constructorTagModifier = labelModifier
        }

instance FromJSON InputContent where
    parseJSON = genericParseJSON inputContentOptions

instance ToJSON InputContent where
    toJSON = genericToJSON inputContentOptions

-- | An input item
data InputItem
    = Item_Input_Message
        { role :: InputRole
        , content :: Vector InputContent
        , status :: Maybe Text
        }
    | Item_Input_Function_Call
        { id :: Maybe Text
        , call_id :: Text
        , name :: Text
        , arguments :: Text
        , status :: Maybe Text
        }
    | Item_Input_Function_Call_Output
        { id :: Maybe Text
        , call_id :: Text
        , output :: Text
        , status :: Maybe Text
        }
    | Item_Input_Item_Reference
        { id :: Maybe Text
        }
    deriving stock (Generic, Show)

inputItemOptions :: Options
inputItemOptions =
    aesonOptions
        { sumEncoding = TaggedObject{ tagFieldName = "type", contentsFieldName = "" }
        , tagSingleConstructors = True
        , constructorTagModifier = stripPrefix "Item_Input_"
        }

instance FromJSON InputItem where
    parseJSON = genericParseJSON inputItemOptions

instance ToJSON InputItem where
    toJSON = genericToJSON inputItemOptions

keyTools :: Key.Key
keyTools = Key.fromText "tools"

keyToolChoice :: Key.Key
keyToolChoice = Key.fromText "tool_choice"

flattenResponseToolFields
    :: Maybe (Vector Tool)
    -> Maybe ToolChoice
    -> KeyMap.KeyMap Value
    -> KeyMap.KeyMap Value
flattenResponseToolFields mTools mChoice o =
    let oWithTools = case mTools of
            Nothing -> o
            Just ts -> KeyMap.insert keyTools (Aeson.Array (Vector.map toolToResponsesValue ts)) o
        oWithChoice = case mChoice of
            Nothing -> oWithTools
            Just choice -> KeyMap.insert keyToolChoice (toolChoiceToResponsesValue choice) oWithTools
    in oWithChoice

unflattenResponseToolFields :: KeyMap.KeyMap Value -> KeyMap.KeyMap Value
unflattenResponseToolFields = adjustChoice . adjustTools
  where
    adjustTools = adjustKey keyTools (mapArrayValues unflattenToolValue)
    adjustChoice = adjustKey keyToolChoice unflattenChoice

    unflattenChoice (String s)
        | s `elem` ([toolChoiceNoneText, toolChoiceAutoText, toolChoiceRequiredText] :: [Text]) = String s
    unflattenChoice other = unflattenToolValue other

mapArrayValues :: (Value -> Value) -> Value -> Value
mapArrayValues f (Aeson.Array arr) = Aeson.Array (Vector.map f arr)
mapArrayValues _ other = other

adjustKey
    :: Key.Key
    -> (Value -> Value)
    -> KeyMap.KeyMap Value
    -> KeyMap.KeyMap Value
adjustKey key f obj =
    case KeyMap.lookup key obj of
        Nothing -> obj
        Just v -> KeyMap.insert key (f v) obj

-- | Output content from the model
data OutputContent
    = Output_Text
        { text :: Text
        , annotations :: Vector Value
        , logprobs :: Maybe (Vector Value)
        }
    | Refusal
        { refusal :: Text }
    deriving stock (Generic, Show)

outputContentOptions :: Options
outputContentOptions =
    aesonOptions
        { sumEncoding = TaggedObject{ tagFieldName = "type", contentsFieldName = "" }
        , tagSingleConstructors = True
        }

instance FromJSON OutputContent where
    parseJSON = genericParseJSON outputContentOptions

instance ToJSON OutputContent where
    toJSON = genericToJSON outputContentOptions

-- | An output message item
data OutputMessage = OutputMessage
    { id :: Text
    , role :: Text
    , content :: Vector OutputContent
    , status :: Text
    } deriving stock (Generic, Show)

instance FromJSON OutputMessage where
    parseJSON = genericParseJSON aesonOptions

instance ToJSON OutputMessage where
    toJSON = genericToJSON aesonOptions

-- | A generated output item.
data OutputItem
    = Item_OutputMessage
        { message_id :: Text
        , message_role :: Text
        , message_content :: Vector OutputContent
        , message_status :: Text
        }
    | Item_FunctionToolCall
        { function_id :: Maybe Text
        , function_call_id :: Text
        , function_name :: Text
        , function_arguments :: Text
        , function_status :: Maybe Text
        }
    | Item_WebSearchToolCall
        { web_search_id :: Text
        , web_search_status :: Text
        , web_search_action :: Maybe WebSearchAction
        }
    | Item_FunctionToolCallOutput
        { function_output_id :: Maybe Text
        , function_output_call_id :: Text
        , function_output_output :: Text
        , function_output_status :: Maybe Text
        }
    | Item_FileSearchToolCall
        { file_search_id :: Text
        , file_search_status :: Text
        , file_search_queries :: Vector Text
        , file_search_results :: Maybe (Vector FileSearchResult)
        }
    | Item_CodeInterpreterToolCall
        { code_interpreter_id :: Text
        , code_interpreter_status :: Text
        , code_interpreter_container_id :: Maybe Text
        , code_interpreter_code :: Maybe Text
        , code_interpreter_outputs :: Maybe (Vector CodeInterpreterOutput)
        }
    | Item_Reasoning
        { reasoning_id :: Text
        , reasoning_encrypted_content :: Maybe Text
        , reasoning_summary :: Maybe (Vector SummaryPart)
        , reasoning_content :: Maybe (Vector ReasoningText)
        , reasoning_status :: Maybe Text
        }
    deriving stock (Generic, Show)

outputItemOptions :: Options
outputItemOptions = aesonOptions
    { sumEncoding = TaggedObject{ tagFieldName = "type", contentsFieldName = "" }
    , tagSingleConstructors = True
    , fieldLabelModifier = \s -> case s of
        "message_id" -> "id"
        "message_role" -> "role"
        "message_content" -> "content"
        "message_status" -> "status"
        "function_id" -> "id"
        "function_call_id" -> "call_id"
        "function_name" -> "name"
        "function_arguments" -> "arguments"
        "function_status" -> "status"
        "web_search_id" -> "id"
        "web_search_status" -> "status"
        "web_search_action" -> "action"
        "function_output_id" -> "id"
        "function_output_call_id" -> "call_id"
        "function_output_output" -> "output"
        "function_output_status" -> "status"
        "file_search_id" -> "id"
        "file_search_status" -> "status"
        "file_search_queries" -> "queries"
        "file_search_results" -> "results"
        "code_interpreter_id" -> "id"
        "code_interpreter_status" -> "status"
        "code_interpreter_container_id" -> "container_id"
        "code_interpreter_code" -> "code"
        "code_interpreter_outputs" -> "outputs"
        "reasoning_id" -> "id"
        "reasoning_encrypted_content" -> "encrypted_content"
        "reasoning_summary" -> "summary"
        "reasoning_content" -> "content"
        "reasoning_status" -> "status"
        other -> other
    , constructorTagModifier = \s -> case s of
        "Item_OutputMessage" -> "message"
        "Item_FunctionToolCall" -> "function_call"
        "Item_WebSearchToolCall" -> "web_search_call"
        "Item_FunctionToolCallOutput" -> "function_call_output"
        "Item_FileSearchToolCall" -> "file_search_call"
        "Item_CodeInterpreterToolCall" -> "code_interpreter_call"
        "Item_Reasoning" -> "reasoning"
        _ -> Prelude.error "Unknown OutputItem constructor"
    }

instance FromJSON OutputItem where
    parseJSON = genericParseJSON outputItemOptions

instance ToJSON OutputItem where
    toJSON = genericToJSON outputItemOptions

-- | Function tool call output item
data FunctionToolCall = FunctionToolCall
    { id :: Maybe Text
    , call_id :: Text
    , name :: Text
    , arguments :: Text
    , status :: Maybe Text
    } deriving stock (Generic, Show)
      deriving anyclass (FromJSON, ToJSON)

-- | Function tool call output item
data FunctionToolCallOutput = FunctionToolCallOutput
    { id :: Maybe Text
    , call_id :: Text
    , output :: Text
    , status :: Maybe Text
    } deriving stock (Generic, Show)
      deriving anyclass (FromJSON, ToJSON)

-- | Web search tool call output item (action is left generic for now)
data WebSearchToolCall = WebSearchToolCall
    { id :: Text
    , status :: Text
    , action :: Maybe WebSearchAction
    } deriving stock (Generic, Show)

instance FromJSON WebSearchToolCall where
    parseJSON = genericParseJSON aesonOptions

instance ToJSON WebSearchToolCall where
    toJSON = genericToJSON aesonOptions

-- | File search result entry
data FileSearchResult = FileSearchResult
    { file_id :: Text
    , text :: Text
    , filename :: Text
    , score :: Maybe Double
    } deriving stock (Generic, Show)
      deriving anyclass (FromJSON, ToJSON)

-- | File search tool call output item
data FileSearchToolCall = FileSearchToolCall
    { id :: Text
    , status :: Text
    , queries :: Vector Text
    , results :: Maybe (Vector FileSearchResult)
    } deriving stock (Generic, Show)
      deriving anyclass (FromJSON, ToJSON)

-- | Code interpreter tool call outputs
data CodeInterpreterOutput
    = CodeInterpreterOutput_Logs{ logs :: Text }
    | CodeInterpreterOutput_Image{ url :: Text }
    deriving stock (Generic, Show)

codeInterpreterOutputOptions :: Options
codeInterpreterOutputOptions = aesonOptions
    { sumEncoding = TaggedObject{ tagFieldName = "type", contentsFieldName = "" }
    , tagSingleConstructors = True
    , constructorTagModifier = stripPrefix "CodeInterpreterOutput_"
    }

instance FromJSON CodeInterpreterOutput where
    parseJSON = genericParseJSON codeInterpreterOutputOptions

instance ToJSON CodeInterpreterOutput where
    toJSON = genericToJSON codeInterpreterOutputOptions

-- | Code interpreter tool call output item
data CodeInterpreterToolCall = CodeInterpreterToolCall
    { id :: Text
    , status :: Text
    , container_id :: Maybe Text
    , code :: Maybe Text
    , outputs :: Maybe (Vector CodeInterpreterOutput)
    } deriving stock (Generic, Show)
      deriving anyclass (FromJSON, ToJSON)

-- | Web search action sources
data WebSearchSource = WebSearchSource_URL{ url :: Text }
    deriving stock (Generic, Show)

webSearchSourceOptions :: Options
webSearchSourceOptions = aesonOptions
    { sumEncoding = TaggedObject{ tagFieldName = "type", contentsFieldName = "" }
    , tagSingleConstructors = True
    , constructorTagModifier = stripPrefix "WebSearchSource_"
    }

instance FromJSON WebSearchSource where
    parseJSON = genericParseJSON webSearchSourceOptions

instance ToJSON WebSearchSource where
    toJSON = genericToJSON webSearchSourceOptions

-- | Web search action
data WebSearchAction
    = WebSearchAction_Search
        { query :: Maybe Text
        , sources :: Maybe (Vector WebSearchSource)
        }
    | WebSearchAction_Open_Page
        { url :: Maybe Text }
    | WebSearchAction_Find
        { url :: Maybe Text
        , pattern :: Maybe Text
        }
    deriving stock (Generic, Show)

webSearchActionOptions :: Options
webSearchActionOptions = aesonOptions
    { sumEncoding = TaggedObject{ tagFieldName = "type", contentsFieldName = "" }
    , tagSingleConstructors = True
    , constructorTagModifier = stripPrefix "WebSearchAction_"
    }

instance FromJSON WebSearchAction where
    parseJSON = genericParseJSON webSearchActionOptions

instance ToJSON WebSearchAction where
    toJSON = genericToJSON webSearchActionOptions

-- | Output text annotation
data Annotation
    = Annotation_File_Citation
        { file_id :: Text
        , index :: Natural
        , filename :: Text
        }
    | Annotation_Url_Citation
        { url :: Text
        , start_index :: Natural
        , end_index :: Natural
        , title :: Text
        }
    | Annotation_Container_File_Citation
        { container_id :: Text
        , file_id :: Text
        , start_index :: Natural
        , end_index :: Natural
        , filename :: Text
        }
    | Annotation_File_Path
        { file_id :: Text
        , index :: Natural
        }
    deriving stock (Generic, Show)

annotationOptions :: Options
annotationOptions = aesonOptions
    { sumEncoding = TaggedObject{ tagFieldName = "type", contentsFieldName = "" }
    , tagSingleConstructors = True
    , constructorTagModifier = stripPrefix "Annotation_"
    }

instance FromJSON Annotation where
    parseJSON = genericParseJSON annotationOptions

instance ToJSON Annotation where
    toJSON = genericToJSON annotationOptions

-- | Reasoning summary part
data SummaryPart = Summary_Text{ text :: Text }
    deriving stock (Generic, Show)

summaryPartOptions :: Options
summaryPartOptions = aesonOptions
    { sumEncoding = TaggedObject{ tagFieldName = "type", contentsFieldName = "" }
    , tagSingleConstructors = True
    }

instance FromJSON SummaryPart where
    parseJSON = genericParseJSON summaryPartOptions

instance ToJSON SummaryPart where
    toJSON = genericToJSON summaryPartOptions

-- | Reasoning text part
data ReasoningText = Reasoning_Text{ text :: Text }
    deriving stock (Generic, Show)

reasoningTextOptions :: Options
reasoningTextOptions = aesonOptions
    { sumEncoding = TaggedObject{ tagFieldName = "type", contentsFieldName = "" }
    , tagSingleConstructors = True
    }

instance FromJSON ReasoningText where
    parseJSON = genericParseJSON reasoningTextOptions

instance ToJSON ReasoningText where
    toJSON = genericToJSON reasoningTextOptions

-- | Reasoning item produced by reasoning models
data ReasoningItem = ReasoningItem
    { id :: Text
    , encrypted_content :: Maybe Text
    , summary :: Maybe (Vector SummaryPart)
    , content :: Maybe (Vector ReasoningText)
    , status :: Maybe Text
    } deriving stock (Generic, Show)

instance FromJSON ReasoningItem where
    parseJSON = genericParseJSON aesonOptions

instance ToJSON ReasoningItem where
    toJSON = genericToJSON aesonOptions

-- | Streaming events for /v1/responses
data ResponseStreamEvent
    = ResponseCreatedEvent
        { response :: ResponseObject
        , sequence_number :: Natural
        }
    | ResponseInProgressEvent
        { response :: ResponseObject
        , sequence_number :: Natural
        }
    | ResponseCompletedEvent
        { response :: ResponseObject
        , sequence_number :: Natural
        }
    | ResponseFailedEvent
        { response :: ResponseObject
        , sequence_number :: Natural
        }
    | ResponseOutputItemAddedEvent
        { output_index :: Natural
        , item :: OutputItem
        , sequence_number :: Natural
        }
    | ResponseOutputItemDoneEvent
        { output_index :: Natural
        , sequence_number :: Natural
        }
    | ResponseContentPartAddedEvent
        { item_id :: Text
        , output_index :: Natural
        , content_index :: Natural
        , part :: OutputContent
        , sequence_number :: Natural
        }
    | ResponseContentPartDoneEvent
        { item_id :: Text
        , output_index :: Natural
        , content_index :: Natural
        , part :: OutputContent
        , sequence_number :: Natural
        }
    | ResponseTextDeltaEvent
        { item_id :: Text
        , output_index :: Natural
        , content_index :: Natural
        , delta :: Text
        , sequence_number :: Natural
        }
    | ResponseTextDoneEvent
        { item_id :: Text
        , output_index :: Natural
        , content_index :: Natural
        , text :: Text
        , sequence_number :: Natural
        }
    | ResponseOutputTextAnnotationAddedEvent
        { item_id :: Text
        , output_index :: Natural
        , content_index :: Natural
        , annotation_index :: Natural
        , annotation :: Annotation
        , sequence_number :: Natural
        }
    | ResponseWebSearchCallInProgressEvent
        { output_index :: Natural
        , item_id :: Text
        , sequence_number :: Natural
        }
    | ResponseWebSearchCallSearchingEvent
        { output_index :: Natural
        , item_id :: Text
        , sequence_number :: Natural
        }
    | ResponseWebSearchCallCompletedEvent
        { output_index :: Natural
        , item_id :: Text
        , sequence_number :: Natural
        }
    | ResponseFileSearchCallInProgressEvent
        { output_index :: Natural
        , item_id :: Text
        , sequence_number :: Natural
        }
    | ResponseFileSearchCallSearchingEvent
        { output_index :: Natural
        , item_id :: Text
        , sequence_number :: Natural
        }
    | ResponseFileSearchCallCompletedEvent
        { output_index :: Natural
        , item_id :: Text
        , sequence_number :: Natural
        }
    | ResponseCodeInterpreterCallInProgressEvent
        { output_index :: Natural
        , item_id :: Text
        , sequence_number :: Natural
        }
    | ResponseCodeInterpreterCallInterpretingEvent
        { output_index :: Natural
        , item_id :: Text
        , sequence_number :: Natural
        }
    | ResponseCodeInterpreterCallCompletedEvent
        { output_index :: Natural
        , item_id :: Text
        , sequence_number :: Natural
        }
    | ResponseCodeInterpreterCallCodeDeltaEvent
        { output_index :: Natural
        , item_id :: Text
        , delta :: Text
        , sequence_number :: Natural
        }
    | ResponseCodeInterpreterCallCodeDoneEvent
        { output_index :: Natural
        , item_id :: Text
        , code :: Text
        , sequence_number :: Natural
        }
    | ErrorEvent
        { error_code :: Maybe Text
        , error_message :: Text
        , error_param :: Maybe Text
        , error_sequence_number :: Natural
        }
    deriving stock (Generic, Show)

responseStreamEventOptions :: Options
responseStreamEventOptions = aesonOptions
    { sumEncoding = TaggedObject{ tagFieldName = "type", contentsFieldName = "" }
    , tagSingleConstructors = True
    , fieldLabelModifier = \s -> case s of
        -- Strip "error_" prefix from ErrorEvent fields
        "error_code" -> "code"
        "error_message" -> "message"
        "error_param" -> "param"
        "error_sequence_number" -> "sequence_number"
        -- Keep all other fields as-is
        other -> other
    , constructorTagModifier = \s -> case s of
        "ResponseCreatedEvent" -> "response.created"
        "ResponseInProgressEvent" -> "response.in_progress"
        "ResponseCompletedEvent" -> "response.completed"
        "ResponseFailedEvent" -> "response.failed"
        "ResponseOutputItemAddedEvent" -> "response.output_item.added"
        "ResponseOutputItemDoneEvent" -> "response.output_item.done"
        "ResponseContentPartAddedEvent" -> "response.content_part.added"
        "ResponseContentPartDoneEvent" -> "response.content_part.done"
        "ResponseTextDeltaEvent" -> "response.output_text.delta"
        "ResponseTextDoneEvent" -> "response.output_text.done"
        "ResponseOutputTextAnnotationAddedEvent" -> "response.output_text.annotation.added"
        "ResponseWebSearchCallInProgressEvent" -> "response.web_search_call.in_progress"
        "ResponseWebSearchCallSearchingEvent" -> "response.web_search_call.searching"
        "ResponseWebSearchCallCompletedEvent" -> "response.web_search_call.completed"
        "ResponseFileSearchCallInProgressEvent" -> "response.file_search_call.in_progress"
        "ResponseFileSearchCallSearchingEvent" -> "response.file_search_call.searching"
        "ResponseFileSearchCallCompletedEvent" -> "response.file_search_call.completed"
        "ResponseCodeInterpreterCallInProgressEvent" -> "response.code_interpreter_call.in_progress"
        "ResponseCodeInterpreterCallInterpretingEvent" -> "response.code_interpreter_call.interpreting"
        "ResponseCodeInterpreterCallCompletedEvent" -> "response.code_interpreter_call.completed"
        "ResponseCodeInterpreterCallCodeDeltaEvent" -> "response.code_interpreter_call_code.delta"
        "ResponseCodeInterpreterCallCodeDoneEvent" -> "response.code_interpreter_call_code.done"
        "ErrorEvent" -> "error"
        _ -> Prelude.error "Unknown ResponseStreamEvent constructor"
    }

instance FromJSON ResponseStreamEvent where
    parseJSON = genericParseJSON responseStreamEventOptions

instance ToJSON ResponseStreamEvent where
    toJSON = genericToJSON responseStreamEventOptions


-- | Usage statistics for the response request
data ResponseUsage = ResponseUsage
    { input_tokens :: Natural
    , input_tokens_details :: InputTokensDetails
    , output_tokens :: Natural
    , output_tokens_details :: OutputTokensDetails
    , total_tokens :: Natural
    } deriving stock (Generic, Show)
      deriving anyclass (FromJSON, ToJSON)

data InputTokensDetails = InputTokensDetails
    { cached_tokens :: Natural
    } deriving stock (Generic, Show)
      deriving anyclass (FromJSON, ToJSON)

data OutputTokensDetails = OutputTokensDetails
    { reasoning_tokens :: Natural
    } deriving stock (Generic, Show)
      deriving anyclass (FromJSON, ToJSON)

-- | Response object
data ResponseObject = ResponseObject
    { id :: Text
    , object :: Text
    , created_at :: POSIXTime
    , status :: Text
    , error :: Maybe Value
    , incomplete_details :: Maybe Value
    , instructions :: Maybe Value
    , model :: Model
    , output :: Vector OutputItem
    , parallel_tool_calls :: Bool
    , previous_response_id :: Maybe Text
    , reasoning :: Maybe Value
    , store :: Maybe Bool
    , temperature :: Maybe Double
    , tool_choice :: Maybe ToolChoice
    , tools :: Maybe (Vector Tool)
    , top_p :: Maybe Double
    , truncation :: Maybe Text
    , usage :: Maybe ResponseUsage
    , user :: Maybe Text
    , metadata :: Maybe (Map Text Text)
    } deriving stock (Generic, Show)

instance FromJSON ResponseObject where
    parseJSON (Aeson.Object o) =
        genericParseJSON aesonOptions (Aeson.Object (unflattenResponseToolFields o))
    parseJSON other = genericParseJSON aesonOptions other

instance ToJSON ResponseObject where
    toJSON response@ResponseObject{ tools = mTools, tool_choice = mChoice } =
        case genericToJSON aesonOptions response of
            Aeson.Object o ->
                Aeson.Object
                    (flattenResponseToolFields mTools mChoice o)
            other -> other

-- | Request body for /v1/responses
data CreateResponse = CreateResponse
    { model :: Model
    , input :: Maybe Input
    , include :: Maybe (Vector Text)
    , parallel_tool_calls :: Maybe Bool
    , store :: Maybe Bool
    , instructions :: Maybe Text
    , stream :: Maybe Bool
    , stream_options :: Maybe Value
    , metadata :: Maybe (Map Text Text)
    , temperature :: Maybe Double
    , top_p :: Maybe Double
    , tools :: Maybe (Vector Tool)
    , tool_choice :: Maybe ToolChoice
    } deriving stock (Generic, Show)

instance FromJSON CreateResponse where
    parseJSON (Aeson.Object o) =
        genericParseJSON aesonOptions (Aeson.Object (unflattenResponseToolFields o))
    parseJSON other = genericParseJSON aesonOptions other

instance ToJSON CreateResponse where
    toJSON request@CreateResponse{ tools = mTools, tool_choice = mChoice } =
        case genericToJSON aesonOptions request of
            Aeson.Object o ->
                Aeson.Object
                    (flattenResponseToolFields mTools mChoice o)
            other -> other

-- | Default CreateResponse
_CreateResponse :: CreateResponse
_CreateResponse = CreateResponse
    { input = Nothing
    , include = Nothing
    , parallel_tool_calls = Nothing
    , store = Nothing
    , instructions = Nothing
    , stream = Nothing
    , stream_options = Nothing
    , metadata = Nothing
    , temperature = Nothing
    , top_p = Nothing
    , tools = Nothing
    , tool_choice = Nothing
    }

-- | Servant API for /v1/responses
type API =
        "responses"
    :>  (         ReqBody '[JSON] CreateResponse
            :>  Post '[JSON] ResponseObject
        :<|>      Capture "response_id" Text
            :>  Get '[JSON] ResponseObject
        :<|>      Capture "response_id" Text
            :>  "cancel"
            :>  Post '[JSON] ResponseObject
        :<|>      Capture "response_id" Text
            :>  "input_items"
            :>  Get '[JSON] (ListOf InputItem)
        )