{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}

{- |
Module      : Data.Ollama.Common.Utils
Copyright   : (c) 2025 Tushar Adhatrao
License     : MIT
Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>
Stability   : experimental
Description : Utility functions for interacting with the Ollama API, including image encoding, HTTP request handling, and retry logic.

This module provides helper functions for common tasks in the Ollama client, such as encoding images to Base64,
sending HTTP requests to the Ollama API, handling streaming and non-streaming responses, and managing retries for failed requests.
It also includes a default model options configuration and a function to retrieve the Ollama server version.

The functions in this module are used internally by other modules like 'Data.Ollama.Chat' and 'Data.Ollama.Generate' but can also be used directly for custom API interactions.
-}
module Data.Ollama.Common.Utils
  ( -- * Image Encoding
    encodeImage

    -- * HTTP Request Handling
  , withOllamaRequest
  , commonNonStreamingHandler
  , commonStreamHandler
  , nonJsonHandler

    -- * Model Options
  , defaultModelOptions

    -- * Retry Logic
  , withRetry

    -- * Version Retrieval
  , getVersion
  ) where

import Control.Concurrent (threadDelay)
import Control.Exception (IOException, try)
import Data.Aeson
import Data.ByteString qualified as BS
import Data.ByteString.Base64 qualified as Base64
import Data.ByteString.Lazy qualified as BSL
import Data.Char (toLower)
import Data.Maybe (fromMaybe)
import Data.Ollama.Common.Config
import Data.Ollama.Common.Error
import Data.Ollama.Common.Error qualified as Error
import Data.Ollama.Common.Types
import Data.Text (Text)
import Data.Text qualified as T
import Data.Text.Encoding qualified as TE
import Network.HTTP.Client
import Network.HTTP.Client.TLS
import Network.HTTP.Types (Status (statusCode))
import System.Directory
import System.FilePath

-- | List of supported image file extensions for 'encodeImage'.
supportedExtensions :: [String]
supportedExtensions :: [[Char]]
supportedExtensions = [[Char]
".jpg", [Char]
".jpeg", [Char]
".png"]

-- | Safely read a file, returning an 'Either' with an 'IOException' on failure.
safeReadFile :: FilePath -> IO (Either IOException BS.ByteString)
safeReadFile :: [Char] -> IO (Either IOException ByteString)
safeReadFile = IO ByteString -> IO (Either IOException ByteString)
forall e a. Exception e => IO a -> IO (Either e a)
try (IO ByteString -> IO (Either IOException ByteString))
-> ([Char] -> IO ByteString)
-> [Char]
-> IO (Either IOException ByteString)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> IO ByteString
BS.readFile

-- | Read a file if it exists, returning 'Nothing' if it does not.
asPath :: FilePath -> IO (Maybe BS.ByteString)
asPath :: [Char] -> IO (Maybe ByteString)
asPath [Char]
filePath = do
  Bool
exists <- [Char] -> IO Bool
doesFileExist [Char]
filePath
  if Bool
exists
    then (IOException -> Maybe ByteString)
-> (ByteString -> Maybe ByteString)
-> Either IOException ByteString
-> Maybe ByteString
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either (Maybe ByteString -> IOException -> Maybe ByteString
forall a b. a -> b -> a
const Maybe ByteString
forall a. Maybe a
Nothing) ByteString -> Maybe ByteString
forall a. a -> Maybe a
Just (Either IOException ByteString -> Maybe ByteString)
-> IO (Either IOException ByteString) -> IO (Maybe ByteString)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [Char] -> IO (Either IOException ByteString)
safeReadFile [Char]
filePath
    else Maybe ByteString -> IO (Maybe ByteString)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe ByteString
forall a. Maybe a
Nothing

-- | Check if a file has a supported image extension.
isSupportedExtension :: FilePath -> Bool
isSupportedExtension :: [Char] -> Bool
isSupportedExtension [Char]
p = (Char -> Char) -> [Char] -> [Char]
forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
toLower ([Char] -> [Char]
takeExtension [Char]
p) [Char] -> [[Char]] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [[Char]]
supportedExtensions

{- | Encodes an image file to Base64 format.

Takes a file path to an image (jpg, jpeg, or png) and returns its data encoded as a Base64 'Text'.
Returns 'Nothing' if the file extension is unsupported or the file cannot be read.
This is useful for including images in API requests that expect Base64-encoded data, such as 'GenerateOps' images field.
-}
encodeImage :: FilePath -> IO (Maybe Text)
encodeImage :: [Char] -> IO (Maybe Text)
encodeImage [Char]
filePath = do
  if Bool -> Bool
not ([Char] -> Bool
isSupportedExtension [Char]
filePath)
    then Maybe Text -> IO (Maybe Text)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe Text
forall a. Maybe a
Nothing
    else do
      Maybe ByteString
maybeContent <- [Char] -> IO (Maybe ByteString)
asPath [Char]
filePath
      Maybe Text -> IO (Maybe Text)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe Text -> IO (Maybe Text)) -> Maybe Text -> IO (Maybe Text)
forall a b. (a -> b) -> a -> b
$ (ByteString -> Text) -> Maybe ByteString -> Maybe Text
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (ByteString -> Text
TE.decodeUtf8 (ByteString -> Text)
-> (ByteString -> ByteString) -> ByteString -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> ByteString
Base64.encode) Maybe ByteString
maybeContent

{- | Executes an action with retry logic for recoverable errors.

Retries the given action up to the specified number of times with a delay (in seconds) between attempts.
Only retries on recoverable errors such as HTTP errors, timeouts, JSON schema errors, or decoding errors.
-}
withRetry ::
  -- | Number of retries
  Int ->
  -- | Delay between retries in seconds
  Int ->
  -- | Action to execute, returning 'Either' 'OllamaError' or a result
  IO (Either OllamaError a) ->
  IO (Either OllamaError a)
withRetry :: forall a.
Int
-> Int -> IO (Either OllamaError a) -> IO (Either OllamaError a)
withRetry Int
0 Int
_ IO (Either OllamaError a)
action = IO (Either OllamaError a)
action
withRetry Int
retries Int
delaySeconds IO (Either OllamaError a)
action = do
  Either OllamaError a
result <- IO (Either OllamaError a)
action
  case Either OllamaError a
result of
    Left OllamaError
err | OllamaError -> Bool
isRetryableError OllamaError
err -> do
      Int -> IO ()
threadDelay (Int
delaySeconds Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
1000000) -- Convert to microseconds
      Int
-> Int -> IO (Either OllamaError a) -> IO (Either OllamaError a)
forall a.
Int
-> Int -> IO (Either OllamaError a) -> IO (Either OllamaError a)
withRetry (Int
retries Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1) Int
delaySeconds IO (Either OllamaError a)
action
    Either OllamaError a
_ -> Either OllamaError a -> IO (Either OllamaError a)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return Either OllamaError a
result
  where
    isRetryableError :: OllamaError -> Bool
isRetryableError (HttpError HttpException
_) = Bool
True
    isRetryableError (TimeoutError [Char]
_) = Bool
True
    isRetryableError (JsonSchemaError [Char]
_) = Bool
True
    isRetryableError (DecodeError [Char]
_ [Char]
_) = Bool
True
    isRetryableError OllamaError
_ = Bool
False

{- | Sends an HTTP request to the Ollama API.

A unified function for making API requests to the Ollama server. Supports both GET and POST methods,
customizable payloads, and optional configuration. The response is processed by the provided handler.
-}
withOllamaRequest ::
  forall payload response.
  (ToJSON payload) =>
  -- | API endpoint
  Text ->
  -- | HTTP method ("GET" or "POST")
  BS.ByteString ->
  -- | Optional request payload (must implement 'ToJSON')
  Maybe payload ->
  -- | Optional 'OllamaConfig' (defaults to 'defaultOllamaConfig')
  Maybe OllamaConfig ->
  -- | Response handler to process the HTTP response
  (Response BodyReader -> IO (Either OllamaError response)) ->
  IO (Either OllamaError response)
withOllamaRequest :: forall payload response.
ToJSON payload =>
Text
-> ByteString
-> Maybe payload
-> Maybe OllamaConfig
-> (Response (IO ByteString) -> IO (Either OllamaError response))
-> IO (Either OllamaError response)
withOllamaRequest Text
endpoint ByteString
reqMethod Maybe payload
mbPayload Maybe OllamaConfig
mbOllamaConfig Response (IO ByteString) -> IO (Either OllamaError response)
handler = do
  let OllamaConfig {Int
Maybe Int
Maybe (IO ())
Maybe Manager
Text
hostUrl :: Text
timeout :: Int
onModelStart :: Maybe (IO ())
onModelError :: Maybe (IO ())
onModelFinish :: Maybe (IO ())
retryCount :: Maybe Int
retryDelay :: Maybe Int
commonManager :: Maybe Manager
commonManager :: OllamaConfig -> Maybe Manager
retryDelay :: OllamaConfig -> Maybe Int
retryCount :: OllamaConfig -> Maybe Int
onModelFinish :: OllamaConfig -> Maybe (IO ())
onModelError :: OllamaConfig -> Maybe (IO ())
onModelStart :: OllamaConfig -> Maybe (IO ())
timeout :: OllamaConfig -> Int
hostUrl :: OllamaConfig -> Text
..} = OllamaConfig -> Maybe OllamaConfig -> OllamaConfig
forall a. a -> Maybe a -> a
fromMaybe OllamaConfig
defaultOllamaConfig Maybe OllamaConfig
mbOllamaConfig
      fullUrl :: [Char]
fullUrl = Text -> [Char]
T.unpack (Text -> [Char]) -> Text -> [Char]
forall a b. (a -> b) -> a -> b
$ Text
hostUrl Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
endpoint
      timeoutMicros :: Int
timeoutMicros = Int
timeout Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
1000000
  Manager
manager <- case Maybe Manager
commonManager of
    Maybe Manager
Nothing ->
      ManagerSettings -> IO Manager
forall (m :: * -> *). MonadIO m => ManagerSettings -> m Manager
newTlsManagerWith
        ManagerSettings
tlsManagerSettings {managerResponseTimeout = responseTimeoutMicro timeoutMicros}
    Just Manager
m -> Manager -> IO Manager
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Manager
m
  Either HttpException Request
eRequest <- IO Request -> IO (Either HttpException Request)
forall e a. Exception e => IO a -> IO (Either e a)
try (IO Request -> IO (Either HttpException Request))
-> IO Request -> IO (Either HttpException Request)
forall a b. (a -> b) -> a -> b
$ [Char] -> IO Request
forall (m :: * -> *). MonadThrow m => [Char] -> m Request
parseRequest [Char]
fullUrl
  case Either HttpException Request
eRequest of
    Left HttpException
ex -> Either OllamaError response -> IO (Either OllamaError response)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Either OllamaError response -> IO (Either OllamaError response))
-> Either OllamaError response -> IO (Either OllamaError response)
forall a b. (a -> b) -> a -> b
$ OllamaError -> Either OllamaError response
forall a b. a -> Either a b
Left (OllamaError -> Either OllamaError response)
-> OllamaError -> Either OllamaError response
forall a b. (a -> b) -> a -> b
$ HttpException -> OllamaError
Error.HttpError HttpException
ex
    Right Request
req -> do
      let request :: Request
request =
            Request
req
              { method = reqMethod
              , requestBody =
                  maybe mempty (\payload
x -> ByteString -> RequestBody
RequestBodyLBS (ByteString -> RequestBody) -> ByteString -> RequestBody
forall a b. (a -> b) -> a -> b
$ payload -> ByteString
forall a. ToJSON a => a -> ByteString
encode payload
x) mbPayload
              }
          retryCnt :: Int
retryCnt = Int -> Maybe Int -> Int
forall a. a -> Maybe a -> a
fromMaybe Int
0 Maybe Int
retryCount
          retryDelay_ :: Int
retryDelay_ = Int -> Maybe Int -> Int
forall a. a -> Maybe a -> a
fromMaybe Int
1 Maybe Int
retryDelay
      Int
-> Int
-> IO (Either OllamaError response)
-> IO (Either OllamaError response)
forall a.
Int
-> Int -> IO (Either OllamaError a) -> IO (Either OllamaError a)
withRetry Int
retryCnt Int
retryDelay_ (IO (Either OllamaError response)
 -> IO (Either OllamaError response))
-> IO (Either OllamaError response)
-> IO (Either OllamaError response)
forall a b. (a -> b) -> a -> b
$ do
        IO () -> Maybe (IO ()) -> IO ()
forall a. a -> Maybe a -> a
fromMaybe (() -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()) Maybe (IO ())
onModelStart
        Either HttpException (Either OllamaError response)
eResponse <- IO (Either OllamaError response)
-> IO (Either HttpException (Either OllamaError response))
forall e a. Exception e => IO a -> IO (Either e a)
try (IO (Either OllamaError response)
 -> IO (Either HttpException (Either OllamaError response)))
-> IO (Either OllamaError response)
-> IO (Either HttpException (Either OllamaError response))
forall a b. (a -> b) -> a -> b
$ Request
-> Manager
-> (Response (IO ByteString) -> IO (Either OllamaError response))
-> IO (Either OllamaError response)
forall a.
Request -> Manager -> (Response (IO ByteString) -> IO a) -> IO a
withResponse Request
request Manager
manager Response (IO ByteString) -> IO (Either OllamaError response)
handler
        case Either HttpException (Either OllamaError response)
eResponse of
          Left HttpException
ex -> do
            IO () -> Maybe (IO ()) -> IO ()
forall a. a -> Maybe a -> a
fromMaybe (() -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()) Maybe (IO ())
onModelError
            case HttpException
ex of
              (HttpExceptionRequest Request
_ HttpExceptionContent
ResponseTimeout) ->
                Either OllamaError response -> IO (Either OllamaError response)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Either OllamaError response -> IO (Either OllamaError response))
-> Either OllamaError response -> IO (Either OllamaError response)
forall a b. (a -> b) -> a -> b
$ OllamaError -> Either OllamaError response
forall a b. a -> Either a b
Left (OllamaError -> Either OllamaError response)
-> OllamaError -> Either OllamaError response
forall a b. (a -> b) -> a -> b
$ [Char] -> OllamaError
Error.TimeoutError [Char]
"No response from LLM yet"
              HttpException
_ -> Either OllamaError response -> IO (Either OllamaError response)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Either OllamaError response -> IO (Either OllamaError response))
-> Either OllamaError response -> IO (Either OllamaError response)
forall a b. (a -> b) -> a -> b
$ OllamaError -> Either OllamaError response
forall a b. a -> Either a b
Left (OllamaError -> Either OllamaError response)
-> OllamaError -> Either OllamaError response
forall a b. (a -> b) -> a -> b
$ HttpException -> OllamaError
Error.HttpError HttpException
ex
          Right Either OllamaError response
result -> do
            IO () -> Maybe (IO ()) -> IO ()
forall a. a -> Maybe a -> a
fromMaybe (() -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()) Maybe (IO ())
onModelFinish
            Either OllamaError response -> IO (Either OllamaError response)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return Either OllamaError response
result

{- | Handles non-streaming API responses.

Processes an HTTP response, accumulating all chunks until EOF and decoding the result as JSON.
Returns an 'Either' with an 'OllamaError' on failure or the decoded response on success.
Suitable for APIs that return a single JSON response.
-}
commonNonStreamingHandler ::
  FromJSON a =>
  Response BodyReader ->
  IO (Either OllamaError a)
commonNonStreamingHandler :: forall a.
FromJSON a =>
Response (IO ByteString) -> IO (Either OllamaError a)
commonNonStreamingHandler Response (IO ByteString)
resp = do
  let bodyReader :: IO ByteString
bodyReader = Response (IO ByteString) -> IO ByteString
forall body. Response body -> body
responseBody Response (IO ByteString)
resp
      respStatus :: Int
respStatus = Status -> Int
statusCode (Status -> Int) -> Status -> Int
forall a b. (a -> b) -> a -> b
$ Response (IO ByteString) -> Status
forall body. Response body -> Status
responseStatus Response (IO ByteString)
resp
  if Int
respStatus Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
200 Bool -> Bool -> Bool
&& Int
respStatus Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
300
    then do
      ByteString
finalBs <- ByteString -> IO ByteString -> IO ByteString
readFullBuff ByteString
BS.empty IO ByteString
bodyReader
      case ByteString -> Either [Char] a
forall a. FromJSON a => ByteString -> Either [Char] a
eitherDecode (ByteString -> ByteString
BSL.fromStrict ByteString
finalBs) of
        Left [Char]
err -> Either OllamaError a -> IO (Either OllamaError a)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Either OllamaError a -> IO (Either OllamaError a))
-> (OllamaError -> Either OllamaError a)
-> OllamaError
-> IO (Either OllamaError a)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. OllamaError -> Either OllamaError a
forall a b. a -> Either a b
Left (OllamaError -> IO (Either OllamaError a))
-> OllamaError -> IO (Either OllamaError a)
forall a b. (a -> b) -> a -> b
$ [Char] -> [Char] -> OllamaError
Error.DecodeError [Char]
err (ByteString -> [Char]
forall a. Show a => a -> [Char]
show ByteString
finalBs)
        Right a
decoded -> Either OllamaError a -> IO (Either OllamaError a)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Either OllamaError a -> IO (Either OllamaError a))
-> (a -> Either OllamaError a) -> a -> IO (Either OllamaError a)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> Either OllamaError a
forall a b. b -> Either a b
Right (a -> IO (Either OllamaError a)) -> a -> IO (Either OllamaError a)
forall a b. (a -> b) -> a -> b
$ a
decoded
    else OllamaError -> Either OllamaError a
forall a b. a -> Either a b
Left (OllamaError -> Either OllamaError a)
-> (ByteString -> OllamaError)
-> ByteString
-> Either OllamaError a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> OllamaError
ApiError (Text -> OllamaError)
-> (ByteString -> Text) -> ByteString -> OllamaError
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> Text
TE.decodeUtf8 (ByteString -> Either OllamaError a)
-> IO ByteString -> IO (Either OllamaError a)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IO ByteString -> IO ByteString
brRead IO ByteString
bodyReader

{- | Accumulates response chunks into a single ByteString.

Internal helper function to read all chunks from a 'BodyReader' until EOF.
-}
readFullBuff :: BS.ByteString -> BodyReader -> IO BS.ByteString
readFullBuff :: ByteString -> IO ByteString -> IO ByteString
readFullBuff ByteString
acc IO ByteString
reader = do
  ByteString
chunk <- IO ByteString -> IO ByteString
brRead IO ByteString
reader
  if ByteString -> Bool
BS.null ByteString
chunk
    then ByteString -> IO ByteString
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ByteString
acc
    else ByteString -> IO ByteString -> IO ByteString
readFullBuff (ByteString
acc ByteString -> ByteString -> ByteString
`BS.append` ByteString
chunk) IO ByteString
reader

{- | Handles streaming API responses.

Processes a streaming HTTP response, decoding each chunk as JSON and passing it to the provided
'sendChunk' function. The 'flush' function is called after each chunk. Stops when the response
indicates completion (via 'HasDone'). Returns the final decoded response or an error.
-}
commonStreamHandler ::
  (HasDone a, FromJSON a) =>
  -- | Function to handle each decoded chunk
  (a -> IO ()) ->
  -- | Function to flush after each chunk
  IO () ->
  Response BodyReader ->
  IO (Either OllamaError a)
commonStreamHandler :: forall a.
(HasDone a, FromJSON a) =>
(a -> IO ())
-> IO () -> Response (IO ByteString) -> IO (Either OllamaError a)
commonStreamHandler a -> IO ()
sendChunk IO ()
flush Response (IO ByteString)
resp = ByteString -> IO (Either OllamaError a)
go ByteString
forall a. Monoid a => a
mempty
  where
    go :: ByteString -> IO (Either OllamaError a)
go ByteString
acc = do
      ByteString
bs <- IO ByteString -> IO ByteString
brRead (IO ByteString -> IO ByteString) -> IO ByteString -> IO ByteString
forall a b. (a -> b) -> a -> b
$ Response (IO ByteString) -> IO ByteString
forall body. Response body -> body
responseBody Response (IO ByteString)
resp
      if ByteString -> Bool
BS.null ByteString
bs
        then do
          case ByteString -> Either [Char] a
forall a. FromJSON a => ByteString -> Either [Char] a
eitherDecode (ByteString -> ByteString
BSL.fromStrict ByteString
acc) of
            Left [Char]
err -> Either OllamaError a -> IO (Either OllamaError a)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Either OllamaError a -> IO (Either OllamaError a))
-> Either OllamaError a -> IO (Either OllamaError a)
forall a b. (a -> b) -> a -> b
$ OllamaError -> Either OllamaError a
forall a b. a -> Either a b
Left (OllamaError -> Either OllamaError a)
-> OllamaError -> Either OllamaError a
forall a b. (a -> b) -> a -> b
$ [Char] -> [Char] -> OllamaError
Error.DecodeError [Char]
err (ByteString -> [Char]
forall a. Show a => a -> [Char]
show ByteString
acc)
            Right a
decoded -> Either OllamaError a -> IO (Either OllamaError a)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Either OllamaError a -> IO (Either OllamaError a))
-> Either OllamaError a -> IO (Either OllamaError a)
forall a b. (a -> b) -> a -> b
$ a -> Either OllamaError a
forall a b. b -> Either a b
Right a
decoded
        else do
          let chunk :: ByteString
chunk = ByteString -> ByteString
BSL.fromStrict ByteString
bs
          case ByteString -> Either [Char] a
forall a. FromJSON a => ByteString -> Either [Char] a
eitherDecode ByteString
chunk of
            Left [Char]
err -> Either OllamaError a -> IO (Either OllamaError a)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Either OllamaError a -> IO (Either OllamaError a))
-> Either OllamaError a -> IO (Either OllamaError a)
forall a b. (a -> b) -> a -> b
$ OllamaError -> Either OllamaError a
forall a b. a -> Either a b
Left (OllamaError -> Either OllamaError a)
-> OllamaError -> Either OllamaError a
forall a b. (a -> b) -> a -> b
$ [Char] -> [Char] -> OllamaError
Error.DecodeError [Char]
err (ByteString -> [Char]
forall a. Show a => a -> [Char]
show ByteString
acc)
            Right a
res -> do
              a -> IO ()
sendChunk a
res
              IO ()
flush
              if a -> Bool
forall a. HasDone a => a -> Bool
getDone a
res then Either OllamaError a -> IO (Either OllamaError a)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (a -> Either OllamaError a
forall a b. b -> Either a b
Right a
res) else ByteString -> IO (Either OllamaError a)
go (ByteString
acc ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
bs)

{- | Handles non-JSON API responses.

Processes an HTTP response, accumulating all chunks into a 'ByteString'. Returns the accumulated
data on success (HTTP status 2xx) or an 'ApiError' on failure.
-}
nonJsonHandler :: Response BodyReader -> IO (Either OllamaError BS.ByteString)
nonJsonHandler :: Response (IO ByteString) -> IO (Either OllamaError ByteString)
nonJsonHandler Response (IO ByteString)
resp = do
  let bodyReader :: IO ByteString
bodyReader = Response (IO ByteString) -> IO ByteString
forall body. Response body -> body
responseBody Response (IO ByteString)
resp
      respStatus :: Int
respStatus = Status -> Int
statusCode (Status -> Int) -> Status -> Int
forall a b. (a -> b) -> a -> b
$ Response (IO ByteString) -> Status
forall body. Response body -> Status
responseStatus Response (IO ByteString)
resp
  if Int
respStatus Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
200 Bool -> Bool -> Bool
&& Int
respStatus Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
300
    then ByteString -> Either OllamaError ByteString
forall a b. b -> Either a b
Right (ByteString -> Either OllamaError ByteString)
-> IO ByteString -> IO (Either OllamaError ByteString)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ByteString -> IO ByteString -> IO ByteString
readFullBuff ByteString
BS.empty IO ByteString
bodyReader
    else OllamaError -> Either OllamaError ByteString
forall a b. a -> Either a b
Left (OllamaError -> Either OllamaError ByteString)
-> (ByteString -> OllamaError)
-> ByteString
-> Either OllamaError ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> OllamaError
ApiError (Text -> OllamaError)
-> (ByteString -> Text) -> ByteString -> OllamaError
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> Text
TE.decodeUtf8 (ByteString -> Either OllamaError ByteString)
-> IO ByteString -> IO (Either OllamaError ByteString)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IO ByteString -> IO ByteString
brRead IO ByteString
bodyReader

{- | Default model options for API requests.

Provides a default 'ModelOptions' configuration with all fields set to 'Nothing',
suitable as a starting point for customizing model parameters like temperature or token limits.

Example:

>>> let opts = defaultModelOptions { temperature = Just 0.7 }
-}
defaultModelOptions :: ModelOptions
defaultModelOptions :: ModelOptions
defaultModelOptions =
  ModelOptions
    { numKeep :: Maybe Int
numKeep = Maybe Int
forall a. Maybe a
Nothing
    , seed :: Maybe Int
seed = Maybe Int
forall a. Maybe a
Nothing
    , numPredict :: Maybe Int
numPredict = Maybe Int
forall a. Maybe a
Nothing
    , topK :: Maybe Int
topK = Maybe Int
forall a. Maybe a
Nothing
    , topP :: Maybe Double
topP = Maybe Double
forall a. Maybe a
Nothing
    , minP :: Maybe Double
minP = Maybe Double
forall a. Maybe a
Nothing
    , typicalP :: Maybe Double
typicalP = Maybe Double
forall a. Maybe a
Nothing
    , repeatLastN :: Maybe Int
repeatLastN = Maybe Int
forall a. Maybe a
Nothing
    , temperature :: Maybe Double
temperature = Maybe Double
forall a. Maybe a
Nothing
    , repeatPenalty :: Maybe Double
repeatPenalty = Maybe Double
forall a. Maybe a
Nothing
    , presencePenalty :: Maybe Double
presencePenalty = Maybe Double
forall a. Maybe a
Nothing
    , frequencyPenalty :: Maybe Double
frequencyPenalty = Maybe Double
forall a. Maybe a
Nothing
    , penalizeNewline :: Maybe Bool
penalizeNewline = Maybe Bool
forall a. Maybe a
Nothing
    , stop :: Maybe [Text]
stop = Maybe [Text]
forall a. Maybe a
Nothing
    , numa :: Maybe Bool
numa = Maybe Bool
forall a. Maybe a
Nothing
    , numCtx :: Maybe Int
numCtx = Maybe Int
forall a. Maybe a
Nothing
    , numBatch :: Maybe Int
numBatch = Maybe Int
forall a. Maybe a
Nothing
    , numGpu :: Maybe Int
numGpu = Maybe Int
forall a. Maybe a
Nothing
    , mainGpu :: Maybe Int
mainGpu = Maybe Int
forall a. Maybe a
Nothing
    , useMmap :: Maybe Bool
useMmap = Maybe Bool
forall a. Maybe a
Nothing
    , numThread :: Maybe Int
numThread = Maybe Int
forall a. Maybe a
Nothing
    }

{- | Retrieves the Ollama server version.

Sends a GET request to the "/api//version" endpoint and returns the server version
as a 'Version' wrapped in an 'Either' 'OllamaError'.

Example:

>>> getVersion
--
-- @since 0.2.0.0
-}
getVersion :: IO (Either OllamaError Version)
getVersion :: IO (Either OllamaError Version)
getVersion = do
  Text
-> ByteString
-> Maybe Value
-> Maybe OllamaConfig
-> (Response (IO ByteString) -> IO (Either OllamaError Version))
-> IO (Either OllamaError Version)
forall payload response.
ToJSON payload =>
Text
-> ByteString
-> Maybe payload
-> Maybe OllamaConfig
-> (Response (IO ByteString) -> IO (Either OllamaError response))
-> IO (Either OllamaError response)
withOllamaRequest
    Text
"/api//version"
    ByteString
"GET"
    (Maybe Value
forall a. Maybe a
Nothing :: Maybe Value)
    Maybe OllamaConfig
forall a. Maybe a
Nothing
    Response (IO ByteString) -> IO (Either OllamaError Version)
forall a.
FromJSON a =>
Response (IO ByteString) -> IO (Either OllamaError a)
commonNonStreamingHandler