ollama-haskell-0.3.0.0: src/Ollama/Client.hs
{- |
Module : Ollama.Client
Copyright : (c) 2024-2026 Tushar Adhatrao
License : MIT
Maintainer : tusharadhatrao@gmail.com
Stability : stable
Portability : portable
Opaque client management and construction functions.
@since 1.0.0.0
-}
module Ollama.Client (
OllamaClient (..),
newClient,
defaultClient,
clientFromEnv,
closeClient,
withClient,
) where
import Control.Monad.IO.Class (MonadIO (liftIO))
import Control.Monad.IO.Unlift (MonadUnliftIO, withRunInIO)
import Data.Text (Text)
import Data.Text qualified as T
import Network.HTTP.Client (Manager, newManager)
import Network.HTTP.Client.TLS (tlsManagerSettings)
import Ollama.Client.Config (OllamaClientConfig (..), defaultConfig)
import System.Environment (lookupEnv)
{- | Opaque client handle for dispatching API calls.
@since 1.0.0.0
-}
data OllamaClient = OllamaClient
{ clientManager :: !Manager
, clientConfig :: !OllamaClientConfig
, clientOwned :: !Bool
}
{- | Construct a client with custom configuration settings.
@since 1.0.0.0
-}
newClient :: (MonadIO m) => OllamaClientConfig -> m OllamaClient
newClient cfg = liftIO $ do
case configManager cfg of
Just mgr -> pure $ OllamaClient mgr cfg False
Nothing -> do
mgr <- newManager tlsManagerSettings
pure $ OllamaClient mgr cfg True
{- | Construct a client with default settings (@http://127.0.0.1:11434@).
@since 1.0.0.0
-}
defaultClient :: (MonadIO m) => m OllamaClient
defaultClient = newClient defaultConfig
{- | Construct a client resolving host and credentials from environment variables
(@OLLAMA_HOST@, @OLLAMA_API_KEY@).
Parses @OLLAMA_HOST@ robustly, handling these formats:
* @host:port@ → @http:\/\/host:port@
* @http:\/\/host:port@ → used as-is
* @host@ → @http:\/\/host:11434@
@since 1.0.0.0
-}
clientFromEnv :: (MonadIO m) => m OllamaClient
clientFromEnv = liftIO $ do
mbHost <- lookupEnv "OLLAMA_HOST"
mbKey <- lookupEnv "OLLAMA_API_KEY"
let hostText = maybe "http://127.0.0.1:11434" (normalizeHost . T.pack) mbHost
keyText = T.pack <$> mbKey
cfg = defaultConfig {configBaseUrl = hostText, configApiKey = keyText}
newClient cfg
{- | Normalize an @OLLAMA_HOST@ value to a full URL with scheme.
Handles: bare @host:port@, @http(s):\/\/host:port@, and bare @host@.
@since 1.0.0.0
-}
normalizeHost :: Text -> Text
normalizeHost raw
| "http://" `T.isPrefixOf` raw || "https://" `T.isPrefixOf` raw = T.dropWhileEnd (== '/') raw
| ":" `T.isInfixOf` raw = "http://" <> T.dropWhileEnd (== '/') raw
| otherwise = "http://" <> T.dropWhileEnd (== '/') raw <> ":11434"
{- | Close the underlying HTTP connection manager if owned by this client.
Note: Connection managers in @http-client@ are automatically reclaimed by garbage collection.
@since 1.0.0.0
-}
closeClient :: (MonadIO m) => OllamaClient -> m ()
closeClient _ = pure ()
{- | Resource bracket helper to initialize, run a computation, and close client resources.
@since 1.0.0.0
-}
withClient :: (MonadUnliftIO m) => OllamaClientConfig -> (OllamaClient -> m a) -> m a
withClient cfg action = withRunInIO $ \run -> do
client <- newClient cfg
res <- run (action client)
closeClient client
pure res