never executed always true always false
    1 module PureClaw.Providers.OpenRouter
    2   ( -- * Provider type
    3     OpenRouterProvider
    4   , mkOpenRouterProvider
    5     -- * Errors
    6   , OpenRouterError (..)
    7     -- * Request/response encoding (exported for testing)
    8   , encodeRequest
    9   , decodeResponse
   10   ) where
   11 
   12 import Control.Exception
   13 import Data.ByteString (ByteString)
   14 import Data.ByteString.Lazy qualified as BL
   15 import Data.Text (Text)
   16 import Data.Text qualified as T
   17 import Network.HTTP.Client qualified as HTTP
   18 import Network.HTTP.Types.Status qualified as Status
   19 
   20 import PureClaw.Core.Errors
   21 import PureClaw.Providers.Class
   22 import PureClaw.Providers.OpenAI qualified as OAI
   23 import PureClaw.Security.Secrets
   24 
   25 -- | OpenRouter provider. Uses the OpenAI-compatible API with a
   26 -- different base URL and authentication header.
   27 data OpenRouterProvider = OpenRouterProvider
   28   { _or_manager :: HTTP.Manager
   29   , _or_apiKey  :: ApiKey
   30   }
   31 
   32 -- | Create an OpenRouter provider.
   33 mkOpenRouterProvider :: HTTP.Manager -> ApiKey -> OpenRouterProvider
   34 mkOpenRouterProvider = OpenRouterProvider
   35 
   36 instance Provider OpenRouterProvider where
   37   complete = openRouterComplete
   38 
   39 -- | Errors from the OpenRouter API.
   40 data OpenRouterError
   41   = OpenRouterAPIError Int ByteString
   42   | OpenRouterParseError Text
   43   deriving stock (Show)
   44 
   45 instance Exception OpenRouterError
   46 
   47 instance ToPublicError OpenRouterError where
   48   toPublicError (OpenRouterAPIError 429 _) = RateLimitError
   49   toPublicError (OpenRouterAPIError 401 _) = NotAllowedError
   50   toPublicError _                           = TemporaryError "Provider error"
   51 
   52 openRouterBaseUrl :: String
   53 openRouterBaseUrl = "https://openrouter.ai/api/v1/chat/completions"
   54 
   55 openRouterComplete :: OpenRouterProvider -> CompletionRequest -> IO CompletionResponse
   56 openRouterComplete provider req = do
   57   initReq <- HTTP.parseRequest openRouterBaseUrl
   58   let httpReq = initReq
   59         { HTTP.method = "POST"
   60         , HTTP.requestBody = HTTP.RequestBodyLBS (encodeRequest req)
   61         , HTTP.requestHeaders =
   62             [ ("Authorization", "Bearer " <> withApiKey (_or_apiKey provider) id)
   63             , ("content-type", "application/json")
   64             , ("HTTP-Referer", "https://github.com/pureclaw/pureclaw")
   65             , ("X-Title", "PureClaw")
   66             ]
   67         }
   68   resp <- HTTP.httpLbs httpReq (_or_manager provider)
   69   let status = Status.statusCode (HTTP.responseStatus resp)
   70   if status /= 200
   71     then throwIO (OpenRouterAPIError status (BL.toStrict (HTTP.responseBody resp)))
   72     else case decodeResponse (HTTP.responseBody resp) of
   73       Left err -> throwIO (OpenRouterParseError (T.pack err))
   74       Right response -> pure response
   75 
   76 -- | Encode request — reuses OpenAI format.
   77 encodeRequest :: CompletionRequest -> BL.ByteString
   78 encodeRequest = OAI.encodeRequest
   79 
   80 -- | Decode response — reuses OpenAI format.
   81 decodeResponse :: BL.ByteString -> Either String CompletionResponse
   82 decodeResponse = OAI.decodeResponse