packages feed

openai-2.3.0: src/OpenAI/V1.hs

-- | @\/v1@
--
-- Example usage:
--
-- @
-- {-# LANGUAGE DuplicateRecordFields #-}
-- {-# LANGUAGE NamedFieldPuns        #-}
-- {-# LANGUAGE OverloadedStrings     #-}
-- {-# LANGUAGE OverloadedLists       #-}
--
-- module Main where
--
-- import "Data.Foldable" (`Data.Foldable.traverse_`)
-- import "OpenAI.V1"
-- import "OpenAI.V1.Chat.Completions"
--
-- import qualified "Data.Text" as Text
-- import qualified "Data.Text.IO" as Text.IO
-- import qualified "System.Environment" as Environment
--
-- main :: `IO` ()
-- main = do
--     key <- Environment.`System.Environment.getEnv` \"OPENAI_KEY\"
--
--     clientEnv <- `OpenAI.V1.getClientEnv` \"https://api.openai.com\"
--
--     let `OpenAI.V1.Methods`{ createChatCompletion } = `OpenAI.V1.makeMethods` clientEnv (Text.`Data.Text.pack` key) Nothing Nothing
--
--     text <- Text.IO.`Data.Text.IO.getLine`
--
--     `OpenAI.V1.Chat.Completions.ChatCompletionObject`{ `OpenAI.V1.Chat.Completions.choices` } <- createChatCompletion `OpenAI.V1.Chat.Completions._CreateChatCompletion`
--         { `OpenAI.V1.Chat.Completions.messages` = [ `OpenAI.V1.Chat.Completions.User`{ `OpenAI.V1.Chat.Completions.content` = [ `OpenAI.V1.Chat.Completions.Text`{ `OpenAI.V1.Chat.Completions.text` } ], `OpenAI.V1.Chat.Completions.name` = `Nothing` } ]
--         , `OpenAI.V1.Chat.Completions.model` = \"gpt-4o-mini\"
--         }
--
--     let display `OpenAI.V1.Chat.Completions.Choice`{ `OpenAI.V1.Chat.Completions.message` } = Text.IO.`Data.Text.IO.putStrLn` (`OpenAI.V1.Chat.Completions.messageToContent` message)
--
--     `Data.Foldable.traverse_` display choices
-- @

module OpenAI.V1
    ( -- * Methods
      Methods(..)
    , getClientEnv
    , makeMethods
      -- * Servant
    , API
    ) where

import Control.Monad (foldM)
import Data.ByteString.Char8 ()
import Data.Proxy (Proxy(..))
import OpenAI.Prelude
import OpenAI.V1.Audio.Speech (CreateSpeech)
import OpenAI.V1.Audio.Transcriptions (CreateTranscription, TranscriptionObject)
import OpenAI.V1.Audio.Translations (CreateTranslation, TranslationObject)
import OpenAI.V1.Batches (BatchID, BatchObject, CreateBatch)
import OpenAI.V1.Chat.Completions (ChatCompletionObject, CreateChatCompletion)
import OpenAI.V1.DeletionStatus (DeletionStatus)
import OpenAI.V1.Embeddings (CreateEmbeddings, EmbeddingObject)
import OpenAI.V1.Files (FileID, FileObject, UploadFile)
import OpenAI.V1.Images.Edits (CreateImageEdit)
import OpenAI.V1.Images.Generations (CreateImage)
import OpenAI.V1.Images.Image (ImageObject)
import OpenAI.V1.Images.Variations (CreateImageVariation)
import OpenAI.V1.ListOf (ListOf(..))
import OpenAI.V1.Message (Message)
import OpenAI.V1.Models (Model, ModelObject)
import OpenAI.V1.Moderations (CreateModeration, Moderation)
import OpenAI.V1.Order (Order)
import OpenAI.V1.Responses (CreateResponse, InputItem, ResponseObject)
import OpenAI.V1.Threads (ModifyThread, Thread, ThreadID, ThreadObject)
import OpenAI.V1.Threads.Messages (MessageID, MessageObject, ModifyMessage)
import OpenAI.V1.Threads.Runs.Steps (RunStepObject(..), StepID)
import Servant.Client (ClientEnv)
import Servant.Multipart.Client ()

import OpenAI.V1.Assistants
    (AssistantID, AssistantObject, CreateAssistant, ModifyAssistant)
import OpenAI.V1.ChatKit
    ( CancelChatSession
    , CreateChatKitSession
    , ChatSessionObject
    , SessionID
    , ThreadItem
    )
import OpenAI.V1.FineTuning.Jobs
    ( CheckpointObject
    , CreateFineTuningJob
    , EventObject
    , FineTuningJobID
    , JobObject
    )
import OpenAI.V1.Threads.Runs
    ( CreateRun
    , CreateThreadAndRun
    , ModifyRun
    , RunID
    , RunObject
    , SubmitToolOutputsToRun
    )
import OpenAI.V1.Uploads
    ( AddUploadPart
    , CompleteUpload
    , CreateUpload
    , PartObject
    , UploadID
    , UploadObject
    )
import OpenAI.V1.VectorStores
    ( CreateVectorStore(..)
    , ModifyVectorStore(..)
    , VectorStoreID
    , VectorStoreObject(..)
    )
import OpenAI.V1.VectorStores.FileBatches
    ( CreateVectorStoreFileBatch(..)
    , VectorStoreFileBatchID
    , VectorStoreFilesBatchObject(..)
    )
import OpenAI.V1.VectorStores.Files
    (CreateVectorStoreFile(..), VectorStoreFileID, VectorStoreFileObject(..))

import qualified Control.Exception as Exception
import qualified Data.Aeson as Aeson
import qualified Data.ByteString as SBS
import qualified Data.ByteString.Char8 as S8
import qualified Data.IORef as IORef
import qualified Data.Text as Text
import qualified Network.HTTP.Client as HTTP.Client
import qualified Network.HTTP.Client.TLS as TLS
import qualified Network.HTTP.Types.Status as Status
import qualified OpenAI.V1.Assistants as Assistants
import qualified OpenAI.V1.Audio as Audio
import qualified OpenAI.V1.Batches as Batches
import qualified OpenAI.V1.Chat.Completions as Chat.Completions
import qualified OpenAI.V1.ChatKit as ChatKit
import qualified OpenAI.V1.Embeddings as Embeddings
import qualified OpenAI.V1.Files as Files
import qualified OpenAI.V1.FineTuning.Jobs as FineTuning.Jobs
import qualified OpenAI.V1.Images as Images
import qualified OpenAI.V1.Models as Models
import qualified OpenAI.V1.Moderations as Moderations
import qualified OpenAI.V1.Responses as Responses
import qualified OpenAI.V1.Threads as Threads
import qualified OpenAI.V1.Threads.Messages as Messages
import qualified OpenAI.V1.Threads.Runs as Threads.Runs
import qualified OpenAI.V1.Threads.Runs.Steps as Threads.Runs.Steps
import qualified OpenAI.V1.Uploads as Uploads
import qualified OpenAI.V1.VectorStores as VectorStores
import qualified OpenAI.V1.VectorStores.FileBatches as VectorStores.FileBatches
import qualified OpenAI.V1.VectorStores.Files as VectorStores.Files
import qualified OpenAI.V1.VectorStores.Status as VectorStores.Status
import qualified Servant.Client as Client

-- | Convenient utility to get a `ClientEnv` for the most common use case
getClientEnv
    :: Text
    -- ^ Base URL for API
    -> IO ClientEnv
getClientEnv baseUrlText = do
    baseUrl <- Client.parseBaseUrl (Text.unpack baseUrlText)

    let managerSettings = TLS.tlsManagerSettings
            { HTTP.Client.managerResponseTimeout =
                HTTP.Client.responseTimeoutNone
            }

    manager <- TLS.newTlsManagerWith managerSettings

    pure (Client.mkClientEnv manager baseUrl)

-- | Get a record of API methods after providing an API token
makeMethods
    :: ClientEnv
    -- ^
    -> Text
    -- ^ API token
    -> Maybe Text
    -- ^ Organization ID
    -> Maybe Text
    -- ^ Project ID
    -> Methods
makeMethods clientEnv token organizationID projectID = Methods{..}
  where
    authorization = "Bearer " <> token

    (       (     createSpeech
            :<|>  createTranscription_
            :<|>  createTranslation_
            )
      :<|>  createChatCompletion
      :<|>  (     createChatKitSession
            :<|>  cancelChatSession
            :<|>  _listChatKitThreads
            :<|>  retrieveChatKitThread
            :<|>  deleteChatKitThread
            :<|>  _listChatKitThreadItems
            )
      :<|>  (     createResponse
            :<|>  retrieveResponse
            :<|>  cancelResponse
            :<|>  listResponseInputItems_
            )
      :<|>  createEmbeddings_
      :<|>  (     createFineTuningJob
            :<|>  listFineTuningJobs_
            :<|>  listFineTuningEvents_
            :<|>  listFineTuningCheckpoints_
            :<|>  retrieveFineTuningJob
            :<|>  cancelFineTuning
            )
      :<|>  (     createBatch
            :<|>  retrieveBatch
            :<|>  cancelBatch
            :<|>  listBatch_
            )
      :<|>  (     uploadFile_
            :<|>  listFiles_
            :<|>  retrieveFile
            :<|>  deleteFile
            :<|>  retrieveFileContent
            )
      :<|>  (     createImage_
            :<|>  createImageEdit_
            :<|>  createImageVariation_
            )
      :<|>  (     createUpload
            :<|>  addUploadPart_
            :<|>  completeUpload
            :<|>  cancelUpload
            )
      :<|>  (     listModels_
            :<|>  retrieveModel
            :<|>  deleteModel
            )
      :<|>  (     createModeration
            )
      :<|>  (   (\x -> x "assistants=v2")
            ->  (     createAssistant
                :<|>  listAssistants_
                :<|>  retrieveAssistant
                :<|>  modifyAssistant
                :<|>  deleteAssistant
                )
            )
      :<|>  (   (\x -> x "assistants=v2")
            ->  (     createThread
                :<|>  retrieveThread
                :<|>  modifyThread
                :<|>  deleteThread
                )
            )
      :<|>  (   (\x -> x "assistants=v2")
            ->  (     createMessage
                :<|>  listMessages_
                :<|>  retrieveMessage
                :<|>  modifyMessage
                :<|>  deleteMessage
                )
            )
      :<|>  (   (\x -> x "assistants=v2")
            ->  (     createRun
                :<|>  createThreadAndRun
                :<|>  listRuns_
                :<|>  retrieveRun
                :<|>  modifyRun
                :<|>  submitToolOutputsToRun
                :<|>  cancelRun
                )
            )
      :<|>  (   (\x -> x "assistants=v2")
            ->  (     listRunSteps_
                :<|>  retrieveRunStep
                )
            )

      :<|>  (   (\x -> x "assistants=v2")
            ->  (     createVectorStore
                :<|>  listVectorStores_
                :<|>  retrieveVectorStore
                :<|>  modifyVectorStore
                :<|>  deleteVectorStore
                )
            )
      :<|>  (   (\x -> x "assistants=v2")
            ->  (     createVectorStoreFile
                :<|>  listVectorStoreFiles_
                :<|>  retrieveVectorStoreFile
                :<|>  deleteVectorStoreFile
                )
            )
      :<|>  (   (\x -> x "assistants=v2")
            ->  (     createVectorStoreFileBatch
                :<|>  retrieveVectorStoreFileBatch
                :<|>  cancelVectorStoreFileBatch
                :<|>  listVectorStoreFilesInABatch_
                )
            )
      ) = Client.hoistClient @API Proxy run (Client.client @API Proxy) authorization organizationID projectID

    run :: Client.ClientM a -> IO a
    run clientM = do
        result <- Client.runClientM clientM clientEnv
        case result of
            Left exception -> Exception.throwIO exception
            Right a -> return a

    toVector :: IO (ListOf a) -> IO (Vector a)
    toVector = fmap adapt
      where
        adapt List{ data_ } = data_

    createTranscription a = createTranscription_ (boundary, a)
    createTranslation a = createTranslation_ (boundary, a)
    createEmbeddings a = toVector (createEmbeddings_ a)
    listFineTuningJobs a b = toVector (listFineTuningJobs_ a b)
    listFineTuningEvents a b c = toVector (listFineTuningEvents_ a b c)
    listFineTuningCheckpoints a b c =
        toVector (listFineTuningCheckpoints_ a b c)
    listBatch a b = toVector (listBatch_ a b)
    uploadFile a = uploadFile_ (boundary, a)
    listFiles a b c d = toVector (listFiles_ a b c d)
    addUploadPart a b = addUploadPart_ a (boundary, b)
    createImage a = toVector (createImage_ a)
    createImageEdit a = toVector (createImageEdit_ (boundary, a))
    createImageVariation a = toVector (createImageVariation_ (boundary, a))
    listModels = toVector listModels_
    listAssistants a b c d = toVector (listAssistants_ a b c d)
    listMessages a = toVector (listMessages_ a)
    listRuns a b c d e = toVector (listRuns_ a b c d e)
    listRunSteps a b c d e f g = toVector (listRunSteps_ a b c d e f g)
    listVectorStores a b c d = toVector (listVectorStores_ a b c d)
    listVectorStoreFiles a b c d e f =
        toVector (listVectorStoreFiles_ a b c d e f)
    listVectorStoreFilesInABatch a b c d e f g =
        toVector (listVectorStoreFilesInABatch_ a b c d e f g)
    listResponseInputItems a = toVector (listResponseInputItems_ a)
    listChatKitThreads a b c d e = toVector (_listChatKitThreads a b c d e)
    listChatKitThreadItems a b c d e = toVector (_listChatKitThreadItems a b c d e)

    -- Streaming implementation using http-client and SSE parsing
    createResponseStream req onEvent = do
        let req' = req{ Responses.stream = Just True }
        ssePostJSON "/v1/responses" req' onEvent

    createResponseStreamTyped
        :: CreateResponse
        -> (Either Text Responses.ResponseStreamEvent -> IO ())
        -> IO ()
    createResponseStreamTyped req onEvent =
        createResponseStream req $ \ev -> case ev of
            Left err -> onEvent (Left err)
            Right val -> case Aeson.fromJSON val of
                Aeson.Error msg -> onEvent (Left (Text.pack msg))
                Aeson.Success e -> onEvent (Right e)

    -- Streaming implementation for chat completions
    createChatCompletionStream req onEvent = do
        let req' = req{ Chat.Completions.stream = Just True }
        ssePostJSON "/v1/chat/completions" req' onEvent

    createChatCompletionStreamTyped
        :: CreateChatCompletion
        -> (Either Text Chat.Completions.ChatCompletionStreamEvent -> IO ())
        -> IO ()
    createChatCompletionStreamTyped req onEvent =
        createChatCompletionStream req $ \ev -> case ev of
            Left err -> onEvent (Left err)
            Right val -> case Aeson.fromJSON val of
                Aeson.Error msg -> onEvent (Left (Text.pack msg))
                Aeson.Success e -> onEvent (Right e)

    ssePostJSON :: ToJSON a
                => String
                -> a
                -> (Either Text Aeson.Value -> IO ())
                -> IO ()
    ssePostJSON path body onEvent = do
        let base = Client.baseUrl clientEnv
        let secure = case Client.baseUrlScheme base of
                Client.Http -> False
                Client.Https -> True
        let host = S8.pack (Client.baseUrlHost base)
        let port = Client.baseUrlPort base
        let basePath = Client.baseUrlPath base
        let fullPath = S8.pack (normalizePath basePath <> path)

        let headers0 =
                [ ("Authorization", S8.pack (Text.unpack authorization))
                , ("Accept", "text/event-stream")
                , ("Content-Type", "application/json")
                ]
        let headers1 = case organizationID of
                Nothing -> headers0
                Just org -> ("OpenAI-Organization", S8.pack (Text.unpack org)) : headers0
        let headers = case projectID of
                Nothing -> headers1
                Just proj -> ("OpenAI-Project", S8.pack (Text.unpack proj)) : headers1

        let request = HTTP.Client.defaultRequest
                { HTTP.Client.secure = secure
                , HTTP.Client.host = host
                , HTTP.Client.port = port
                , HTTP.Client.method = "POST"
                , HTTP.Client.path = fullPath
                , HTTP.Client.requestHeaders = headers
                , HTTP.Client.requestBody = HTTP.Client.RequestBodyLBS (Aeson.encode body)
                , HTTP.Client.responseTimeout = HTTP.Client.responseTimeoutNone
                }

        HTTP.Client.withResponse request (Client.manager clientEnv) $ \response -> do
            -- Short-circuit on non-2xx HTTP statuses and surface a single error event
            let st = HTTP.Client.responseStatus response
            if not (Status.statusIsSuccessful st)
                then do
                    bodyChunks <- HTTP.Client.brConsume (HTTP.Client.responseBody response)
                    let errBody = SBS.concat bodyChunks
                    let msg =
                            "HTTP error "
                            <> renderIntegral (Status.statusCode st)
                            <> " "
                            <> (Text.pack (S8.unpack (Status.statusMessage st)))
                            <> (if SBS.null errBody then "" else ": " <> Text.pack (S8.unpack errBody))
                    onEvent (Left msg)
                else do
                    let br = HTTP.Client.responseBody response
                    lineBufRef <- IORef.newIORef SBS.empty
                    eventBufRef <- IORef.newIORef ([] :: [SBS.ByteString])
                    let flushEvent = do
                            es <- IORef.atomicModifyIORef eventBufRef (\buf -> ([], reverse buf))
                            if null es
                                then pure False
                                else do
                                    let payload = S8.concat es
                                    if payload == "[DONE]"
                                        then pure True
                                        else case (Aeson.eitherDecodeStrict payload :: Either String Aeson.Value) of
                                            Left err -> onEvent (Left (Text.pack err)) >> pure False
                                            Right val -> onEvent (Right val) >> pure False

                    -- Note: SSE frames can include fields like "event:" and others.
                    -- We currently ignore all non-"data:" fields and only buffer
                    -- "data:" lines; an empty line flushes a complete event.
                    let handleLine line = do
                            let l = stripCR line
                            if S8.null l
                                then flushEvent
                                else if "data:" `S8.isPrefixOf` l
                                    then do
                                        let d = S8.dropWhile (==' ') (S8.drop 5 l)
                                        IORef.modifyIORef' eventBufRef (d:)
                                        pure False
                                    else pure False

                    let loop = do
                            chunk <- HTTP.Client.brRead br
                            if SBS.null chunk
                                then do
                                    -- flush any pending event at EOF
                                    _ <- flushEvent
                                    pure ()
                                else do
                                    prev <- IORef.readIORef lineBufRef
                                    let combined = prev <> chunk
                                    let ls = S8.split '\n' combined
                                    case unsnoc ls of
                                        Nothing -> loop
                                        Just (completeLines, lastLine) -> do
                                            IORef.writeIORef lineBufRef lastLine
                                            stop <- foldM (\acc ln -> if acc then pure True else handleLine ln) False completeLines
                                            if stop then pure () else loop

                    loop

    normalizePath p = case p of
        "" -> ""
        ('/':_) -> p
        _ -> '/':p

    stripCR bs = case S8.unsnoc bs of
        Just (initBs, '\r') -> initBs
        _ -> bs

    unsnoc [] = Nothing
    unsnoc xs = Just (init xs, last xs)

-- | Hard-coded boundary to simplify the user-experience
--
-- I don't understand why `multipart-servant-client` insists on generating a
-- fresh boundary for each request (or why it doesn't handle that for you)
boundary :: ByteString
boundary = "j3qdD3XtDVjvva8IIqoBzHQAYwCenObtPMkxAFnylwFyU5xffWKoYrY"

-- | API methods
data Methods = Methods
    { createSpeech :: CreateSpeech -> IO ByteString
    , createTranscription :: CreateTranscription -> IO TranscriptionObject
    , createTranslation :: CreateTranslation -> IO TranslationObject
    , createChatCompletion :: CreateChatCompletion -> IO ChatCompletionObject
    , createChatKitSession :: CreateChatKitSession -> IO ChatSessionObject
    , cancelChatSession :: SessionID -> IO CancelChatSession
    , listChatKitThreads
        :: Maybe Text
        -- ^ after
        -> Maybe Text
        -- ^ before
        -> Maybe Natural
        -- ^ limit
        -> Maybe Order
        -- ^ order
        -> Maybe Text
        -- ^ user
        -> IO (Vector ChatKit.ThreadObject)
    , retrieveChatKitThread :: ChatKit.ThreadID -> IO ChatKit.ThreadObject
    , deleteChatKitThread :: ChatKit.ThreadID -> IO DeletionStatus
    , listChatKitThreadItems
        :: ChatKit.ThreadID
        -> Maybe Text
        -- ^ after
        -> Maybe Text
        -- ^ before
        -> Maybe Natural
        -- ^ limit
        -> Maybe Order
        -- ^ order
        -> IO (Vector ThreadItem)
    , createChatCompletionStream
        :: CreateChatCompletion
        -> (Either Text Aeson.Value -> IO ())
        -> IO ()
    , createChatCompletionStreamTyped
        :: CreateChatCompletion
        -> (Either Text Chat.Completions.ChatCompletionStreamEvent -> IO ())
        -> IO ()
    , createResponse :: CreateResponse -> IO ResponseObject
    , createResponseStream
        :: CreateResponse
        -> (Either Text Aeson.Value -> IO ())
        -> IO ()
    , createEmbeddings :: CreateEmbeddings -> IO (Vector EmbeddingObject)
    , createResponseStreamTyped
        :: CreateResponse
        -> (Either Text Responses.ResponseStreamEvent -> IO ())
        -> IO ()
    , createFineTuningJob :: CreateFineTuningJob -> IO JobObject
    , listFineTuningJobs
        :: Maybe Text
        -- ^ after
        -> Maybe Natural
        -- ^ limit
        -> IO (Vector JobObject)
    , listFineTuningEvents
        :: FineTuningJobID
        -- ^
        -> Maybe Text
        -- ^ after
        -> Maybe Natural
        -- ^ limit
        -> IO (Vector EventObject)
    , listFineTuningCheckpoints
        :: FineTuningJobID
        -- ^
        -> Maybe Text
        -- ^ after
        -> Maybe Natural
        -- ^ limit
        -> IO (Vector CheckpointObject)
    , retrieveFineTuningJob :: FineTuningJobID -> IO JobObject
    , cancelFineTuning :: FineTuningJobID -> IO JobObject
    , createBatch :: CreateBatch -> IO BatchObject
    , retrieveBatch :: BatchID -> IO BatchObject
    , cancelBatch :: BatchID -> IO BatchObject
    , listBatch
        :: Maybe Text
        -- ^ after
        -> Maybe Natural
        -- ^ limit
        -> IO (Vector BatchObject)
    , uploadFile :: UploadFile -> IO FileObject
    , listFiles
        :: Maybe Files.Purpose
        -- ^ purpose
        -> Maybe Natural
        -- ^ limit
        -> Maybe Order
        -- ^ order
        -> Maybe Text
        -- ^ after
        -> IO (Vector FileObject)
    , retrieveFile :: FileID -> IO FileObject
    , deleteFile :: FileID -> IO DeletionStatus
    , retrieveFileContent :: FileID -> IO ByteString
    , createUpload
        :: CreateUpload -> IO (UploadObject (Maybe Void))
    , addUploadPart :: UploadID -> AddUploadPart -> IO PartObject
    , completeUpload
        :: UploadID -> CompleteUpload -> IO (UploadObject FileObject)
    , cancelUpload :: UploadID -> IO (UploadObject (Maybe Void))
    , createImage :: CreateImage -> IO (Vector ImageObject)
    , createImageEdit :: CreateImageEdit -> IO (Vector ImageObject)
    , createImageVariation :: CreateImageVariation -> IO (Vector ImageObject)
    , listModels :: IO (Vector ModelObject)
    , retrieveModel :: Model -> IO ModelObject
    , deleteModel :: Model -> IO DeletionStatus
    , createModeration :: CreateModeration -> IO Moderation
    , createAssistant :: CreateAssistant -> IO AssistantObject
    , listAssistants
        :: Maybe Natural
        -- ^ limit
        -> Maybe Order
        -- ^ order
        -> Maybe Text
        -- ^ after
        -> Maybe Text
        -- ^ before
        -> IO (Vector AssistantObject)
    , retrieveAssistant :: AssistantID -> IO AssistantObject
    , modifyAssistant :: AssistantID -> ModifyAssistant -> IO AssistantObject
    , deleteAssistant :: AssistantID -> IO DeletionStatus
    , createThread :: Thread -> IO ThreadObject
    , retrieveThread :: ThreadID -> IO ThreadObject
    , modifyThread :: ThreadID -> ModifyThread -> IO ThreadObject
    , deleteThread :: ThreadID -> IO DeletionStatus
    , createMessage :: ThreadID -> Message -> IO MessageObject
    , listMessages :: ThreadID -> IO (Vector MessageObject)
    , retrieveMessage :: ThreadID -> MessageID -> IO MessageObject
    , modifyMessage
        :: ThreadID -> MessageID -> ModifyMessage -> IO MessageObject
    , deleteMessage :: ThreadID -> MessageID -> IO DeletionStatus
    , createRun
        :: ThreadID
        -- ^
        -> Maybe Text
        -- ^ include[]
        -> CreateRun
        -- ^
        -> IO RunObject
    , createThreadAndRun :: CreateThreadAndRun -> IO RunObject
    , listRuns
        :: ThreadID
        -- ^
        -> Maybe Natural
        -- ^ limit
        -> Maybe Order
        -- ^ order
        -> Maybe Text
        -- ^ after
        -> Maybe Text
        -- ^ before
        -> IO (Vector RunObject)
    , retrieveRun :: ThreadID -> RunID -> IO RunObject
    , modifyRun :: ThreadID -> RunID -> ModifyRun -> IO RunObject
    , submitToolOutputsToRun
        :: ThreadID -> RunID -> SubmitToolOutputsToRun -> IO RunObject
    , cancelRun :: ThreadID -> RunID -> IO RunObject
    , listRunSteps
        :: ThreadID
        -- ^
        -> RunID
        -- ^
        -> Maybe Natural
        -- ^ limit
        -> Maybe Order
        -- ^ order
        -> Maybe Text
        -- ^ after
        -> Maybe Text
        -- ^ before
        -> Maybe Text
        -- ^ include[]
        -> IO (Vector RunStepObject)
    , retrieveRunStep
        :: ThreadID
        -- ^
        -> RunID
        -- ^
        -> StepID
        -- ^
        -> Maybe Text
        -- ^ include[]
        -> IO RunStepObject
    , retrieveResponse :: Text -> IO ResponseObject
    , cancelResponse :: Text -> IO ResponseObject
    , listResponseInputItems :: Text -> IO (Vector InputItem)
    , createVectorStore :: CreateVectorStore -> IO VectorStoreObject
    , listVectorStores
        :: Maybe Natural
        -- ^ limit
        -> Maybe Order
        -- ^ order
        -> Maybe Text
        -- ^ after
        -> Maybe Text
        -- ^ before
        -> IO (Vector VectorStoreObject)
    , retrieveVectorStore :: VectorStoreID -> IO VectorStoreObject
    , modifyVectorStore
        :: VectorStoreID -> ModifyVectorStore -> IO VectorStoreObject
    , deleteVectorStore :: VectorStoreID -> IO DeletionStatus
    , createVectorStoreFile
        :: VectorStoreID -> CreateVectorStoreFile -> IO VectorStoreFileObject
    , listVectorStoreFiles
        :: VectorStoreID
        -- ^
        -> Maybe Natural
        -- ^ limit
        -> Maybe Order
        -- ^ order
        -> Maybe Text
        -- ^ after
        -> Maybe Text
        -- ^ before
        -> Maybe VectorStores.Status.Status
        -- ^ filter
        -> IO (Vector VectorStoreFileObject)
    , retrieveVectorStoreFile
        :: VectorStoreID -> VectorStoreFileID -> IO VectorStoreFileObject
    , deleteVectorStoreFile
        :: VectorStoreID -> VectorStoreFileID -> IO DeletionStatus
    , createVectorStoreFileBatch
        :: VectorStoreID
        -> CreateVectorStoreFileBatch
        -> IO VectorStoreFilesBatchObject
    , retrieveVectorStoreFileBatch
        :: VectorStoreID
        -> VectorStoreFileBatchID
        -> IO VectorStoreFilesBatchObject
    , cancelVectorStoreFileBatch
        :: VectorStoreID
        -> VectorStoreFileBatchID
        -> IO VectorStoreFilesBatchObject
    , listVectorStoreFilesInABatch
        :: VectorStoreID
        -- ^
        -> VectorStoreFileBatchID
        -- ^
        -> Maybe Natural
        -- ^ limit
        -> Maybe Order
        -- ^ order
        -> Maybe Text
        -- ^ after
        -> Maybe Text
        -- ^ before
        -> Maybe VectorStores.Status.Status
        -- ^ filter
        -> IO (Vector VectorStoreFilesBatchObject)
    }

-- | Servant API
type API
    =   Header' [ Required, Strict ] "Authorization" Text
    :>  Header' [ Optional, Strict ] "OpenAI-Organization" Text
    :>  Header' [ Optional, Strict ] "OpenAI-Project" Text
    :>  "v1"
    :>  (     Audio.API
        :<|>  Chat.Completions.API
        :<|>  ChatKit.API
        :<|>  Responses.API
        :<|>  Embeddings.API
        :<|>  FineTuning.Jobs.API
        :<|>  Batches.API
        :<|>  Files.API
        :<|>  Images.API
        :<|>  Uploads.API
        :<|>  Models.API
        :<|>  Moderations.API
        :<|>  Assistants.API
        :<|>  Threads.API
        :<|>  Messages.API
        :<|>  Threads.Runs.API
        :<|>  Threads.Runs.Steps.API
        :<|>  VectorStores.API
        :<|>  VectorStores.Files.API
        :<|>  VectorStores.FileBatches.API
        )