packages feed

openai-2.5.3: 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 Control.Monad.IO.Class (liftIO)
import Data.Foldable (toList)
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.Builder as ByteString.Builder
import qualified Data.ByteString.Char8 as S8
import qualified Data.CaseInsensitive as CI
import qualified Data.IORef as IORef
import qualified Data.Sequence as Sequence
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text.Encoding
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.Chat.Completions.Stream as Chat.Completions.Stream
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
import qualified Servant.Client.Core.Request as Client.Request
import qualified Servant.Client.Core.Response as Client.Response

-- | 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

    ( createResponseClientM
      :<|> _
      :<|> _
      :<|> _
      ) =
        Client.client @ResponsesClientAPI Proxy authorization organizationID projectID

    (       (     createSpeech
            :<|>  createTranscription_
            :<|>  createTranslation_
            )
      :<|>  createChatCompletion
      :<|>  (     createChatKitSession
            :<|>  cancelChatSession
            :<|>  _listChatKitThreads
            :<|>  retrieveChatKitThread
            :<|>  deleteChatKitThread
            :<|>  _listChatKitThreadItems
            )
      :<|>  (     _
            :<|>  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)
    createResponse a = run (createResponseClientM a)
    createResponseWithMetadata a =
        runWithMetadata (createResponseClientM 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
        createResponseStreamWithMetadata req (\_ -> pure ()) onEvent

    createResponseStreamWithMetadata req onMetadata onEvent = do
        let req' = req{ Responses.stream = Just True }
        ssePostJSON "/v1/responses" req' onMetadata onEvent

    createResponseStreamTyped
        :: CreateResponse
        -> (Either Text Responses.ResponseStreamEvent -> IO ())
        -> IO ()
    createResponseStreamTyped req onEvent =
        createResponseStreamTypedWithMetadata req (\_ -> pure ()) onEvent

    createResponseStreamTypedWithMetadata
        :: CreateResponse
        -> (Responses.ResponseWithMetadata () -> IO ())
        -> (Either Text Responses.ResponseStreamEvent -> IO ())
        -> IO ()
    createResponseStreamTypedWithMetadata req onMetadata onEvent =
        createResponseStreamWithMetadata req onMetadata $ \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' (\_ -> pure ()) onEvent

    createChatCompletionStreamTyped
        :: CreateChatCompletion
        -> (Either Text Chat.Completions.Stream.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)

    runWithMetadata :: Client.ClientM a -> IO (Responses.ResponseWithMetadata a)
    runWithMetadata clientM = do
        responseHeadersRef <- IORef.newIORef []
        let existingMiddleware = Client.middleware clientEnv
        let captureResponseHeaders app request = do
                response <- app request
                liftIO $
                    IORef.writeIORef
                        responseHeadersRef
                        (adaptHeaders (toList (Client.Response.responseHeaders response)))
                pure response
        let clientEnvWithMetadata =
                clientEnv
                    { Client.middleware =
                        \app ->
                            captureResponseHeaders (existingMiddleware app)
                    }

        result <- Client.runClientM clientM clientEnvWithMetadata
        case result of
            Left exception -> Exception.throwIO exception
            Right responseBody -> do
                responseHeaders <- IORef.readIORef responseHeadersRef
                pure Responses.ResponseWithMetadata
                    { Responses.body = responseBody
                    , Responses.response_headers = responseHeaders
                    }

    ssePostJSON :: ToJSON a
                => String
                -> a
                -> (Responses.ResponseWithMetadata () -> IO ())
                -> (Either Text Aeson.Value -> IO ())
                -> IO ()
    ssePostJSON path body onMetadata onEvent = do
        let headers0 =
                [ ("Authorization", S8.pack (Text.unpack authorization))
                , ("Accept", "text/event-stream")
                ]
        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 =
                Client.Request.defaultRequest
                    { Client.Request.requestPath =
                        ByteString.Builder.byteString (S8.pack (normalizePath path))
                    , Client.Request.requestMethod = "POST"
                    , Client.Request.requestBody =
                        Just
                            ( Client.Request.RequestBodyLBS (Aeson.encode body)
                            , contentType (Proxy @JSON)
                            )
                    , Client.Request.requestHeaders = Sequence.fromList headers
                    }

        let app :: Client.Request.Request -> Client.ClientM Client.Response.Response
            app servantRequest = do
                httpRequest0 <- liftIO $
                    Client.makeClientRequest
                        clientEnv
                        (Client.baseUrl clientEnv)
                        servantRequest
                let httpRequest =
                        httpRequest0
                            { HTTP.Client.responseTimeout =
                                HTTP.Client.responseTimeoutNone
                            }
                liftIO $
                    HTTP.Client.withResponse httpRequest (Client.manager clientEnv) $ \response -> do
                        onMetadata
                            Responses.ResponseWithMetadata
                                { Responses.body = ()
                                , Responses.response_headers =
                                    adaptHeaders (HTTP.Client.responseHeaders response)
                                }
                        -- 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

                        pure Client.Response.Response
                            { Client.Response.responseStatusCode =
                                HTTP.Client.responseStatus response
                            , Client.Response.responseHeaders =
                                Sequence.fromList (HTTP.Client.responseHeaders response)
                            , Client.Response.responseHttpVersion =
                                HTTP.Client.responseVersion response
                            , Client.Response.responseBody = mempty
                            }

        result <-
            Client.runClientM
                ((Client.middleware clientEnv app) request)
                clientEnv
        case result of
            Left exception -> Exception.throwIO exception
            Right _ -> pure ()

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

    adaptHeaders
        :: [(CI.CI SBS.ByteString, SBS.ByteString)]
        -> [(Text, Text)]
    adaptHeaders = fmap adaptHeader

    adaptHeader (headerName, headerValue) =
        ( decodeText (CI.original headerName)
        , decodeText headerValue
        )

    decodeText bytes =
        case Text.Encoding.decodeUtf8' bytes of
            Left _ -> Text.pack (S8.unpack bytes)
            Right text -> text

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

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

type ResponsesClientAPI
    = Header' [ Required, Strict ] "Authorization" Text
    :> Header' [ Optional, Strict ] "OpenAI-Organization" Text
    :> Header' [ Optional, Strict ] "OpenAI-Project" Text
    :> "v1"
    :> Responses.API

-- | 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.Stream.ChatCompletionStreamEvent -> IO ())
        -> IO ()
    , createResponse :: CreateResponse -> IO ResponseObject
    , createResponseWithMetadata
        :: CreateResponse
        -> IO (Responses.ResponseWithMetadata ResponseObject)
    , createResponseStream
        :: CreateResponse
        -> (Either Text Aeson.Value -> IO ())
        -> IO ()
    , createResponseStreamWithMetadata
        :: CreateResponse
        -> (Responses.ResponseWithMetadata () -> IO ())
        -> (Either Text Aeson.Value -> IO ())
        -> IO ()
    , createEmbeddings :: CreateEmbeddings -> IO (Vector EmbeddingObject)
    , createResponseStreamTyped
        :: CreateResponse
        -> (Either Text Responses.ResponseStreamEvent -> IO ())
        -> IO ()
    , createResponseStreamTypedWithMetadata
        :: CreateResponse
        -> (Responses.ResponseWithMetadata () -> IO ())
        -> (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
        )