packages feed

baikai-claude 0.1.0.0 → 0.1.1.0

raw patch · 3 files changed

+66/−8 lines, 3 filesdep ~baikaiPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: baikai

API changes (from Hackage documentation)

+ Baikai.Provider.Claude.Api: mapRequest :: Model -> Context -> Options -> Either Text CreateMessage

Files

baikai-claude.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.4 name:          baikai-claude-version:       0.1.0.0+version:       0.1.1.0 synopsis:      Anthropic Claude providers for the baikai abstraction description:   Wraps the claude Haskell package as a Baikai Provider for both the Anthropic API and the@@ -38,7 +38,7 @@    build-depends:     , aeson-    , baikai             ^>=0.1.0+    , baikai             ^>=0.1.1     , base               >=4.20   && <5     , base64-bytestring     , bytestring@@ -65,10 +65,12 @@   -- cradle (used by Baikai.Provider.Claude.Interactive) requires the threaded RTS.   ghc-options:    -threaded -with-rtsopts=-N   build-depends:-    , baikai         ^>=0.1.0+    , aeson+    , baikai         ^>=0.1.1     , baikai-claude     , base           >=4.20   && <5     , bytestring+    , claude     , generic-lens     , lens           ^>=5.3     , streamly-core  >=0.3    && <0.5
src/Baikai/Provider/Claude/Api.hs view
@@ -22,6 +22,7 @@   ( register,     registerWithRegistry,     claudeMessagesStream,+    mapRequest,   ) where @@ -42,6 +43,7 @@     globalProviderRegistry,     registerApiProviderWith,   )+import Baikai.ResponseFormat (ResponseFormat (..)) import Baikai.StopReason qualified as Stop import Baikai.Stream (streamingComplete) import Baikai.Stream.Event@@ -63,7 +65,7 @@ import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan) import Control.Exception (SomeException, displayException, try) import Control.Lens ((%~), (&), (.~), (^.))-import Data.Aeson (Value)+import Data.Aeson (Value, (.=)) import Data.Aeson qualified as Aeson import Data.ByteString.Base64 qualified as Base64 import Data.ByteString.Lazy qualified as BSL@@ -564,6 +566,7 @@         Just Tool.ToolChoiceNone -> Nothing         Just tc -> Just (mkAnthropicToolChoice tc)         Nothing -> Nothing+      outputConfigField = fmap mkAnthropicOutputConfig (opts ^. #responseFormat)   pure     Messages._CreateMessage       { Messages.model = m ^. #modelId,@@ -574,8 +577,24 @@         Messages.tools = toolsField,         Messages.tool_choice = toolChoiceField,         Messages.cache_control = cacheControlField,-        Messages.thinking = thinkingField+        Messages.thinking = thinkingField,+        Messages.output_config = outputConfigField       }++-- | Map a baikai 'ResponseFormat' onto the upstream Anthropic+-- 'Messages.OutputConfig'. 'JsonSchema' forwards the schema+-- verbatim via 'Messages.jsonSchemaConfig'. Anthropic's structured+-- outputs are always schema-enforcing, so the baikai 'strict' flag+-- has no wire analog and is dropped. 'JsonObject' (plain-JSON mode)+-- has no native Anthropic equivalent — 'output_config' requires a+-- schema — so it downgrades to a permissive @{"type":"object"}@+-- schema, which still forces the model to emit a JSON object.+mkAnthropicOutputConfig :: ResponseFormat -> Messages.OutputConfig+mkAnthropicOutputConfig = \case+  JsonSchema {schema = s} -> Messages.jsonSchemaConfig s+  JsonObject ->+    Messages.jsonSchemaConfig+      (Aeson.object ["type" .= ("object" :: Text)])  -- | Translate the call-time 'Baikai.CacheRetention' preference into -- the Anthropic SDK's @cache_control@ shape.
test/Main.hs view
@@ -3,24 +3,61 @@ import Baikai import Baikai.Provider.Claude.Api import Baikai.Provider.Claude.Interactive+import Claude.V1.Messages qualified as Messages import Control.Lens ((&), (.~), (^.))+import Data.Aeson qualified as Aeson import Data.ByteString.Char8 qualified as BS8 import Data.Generics.Labels () import Data.Text qualified as Text import Data.Vector qualified as Vector import Streamly.Data.Stream qualified as Stream import Test.Tasty (TestTree, defaultMain, testGroup)-import Test.Tasty.HUnit (assertBool, testCase, (@?=))+import Test.Tasty.HUnit (assertBool, assertFailure, testCase, (@?=))  main :: IO () main =   defaultMain $     testGroup-      "Baikai.Provider.Claude.Interactive"+      "Baikai.Provider.Claude"       [ commandRenderingTest,         compatDetectionTest,-        rejectsImageToolResultsTest+        rejectsImageToolResultsTest,+        responseFormatMappingTest       ]++-- | A 'JsonSchema' on 'Options.responseFormat' maps onto Anthropic's+-- native @output_config@, forwarding the schema 'Value' verbatim via+-- 'Messages.jsonSchemaConfig'. Pure: 'mapRequest' is+-- 'Either Text Messages.CreateMessage'.+responseFormatMappingTest :: TestTree+responseFormatMappingTest =+  testCase "responseFormat JsonSchema maps onto Anthropic output_config" $ do+    let model =+          _Model+            & #modelId .~ "claude-haiku-4-5-20251001"+            & #api .~ AnthropicMessages+            & #provider .~ "anthropic"+        personSchema =+          Aeson.object+            [ "type" Aeson..= ("object" :: Text.Text),+              "properties"+                Aeson..= Aeson.object+                  [ "name" Aeson..= Aeson.object ["type" Aeson..= ("string" :: Text.Text)],+                    "age" Aeson..= Aeson.object ["type" Aeson..= ("integer" :: Text.Text)]+                  ],+              "required" Aeson..= (["name", "age"] :: [Text.Text]),+              "additionalProperties" Aeson..= False+            ]+        ctx = _Context+        opts =+          _Options+            & #responseFormat+              .~ Just (JsonSchema {name = "person", schema = personSchema, strict = True})+    case mapRequest model ctx opts of+      Left e -> assertFailure ("mapRequest failed: " <> Text.unpack e)+      Right req ->+        Messages.output_config req+          @?= Just (Messages.jsonSchemaConfig personSchema)  commandRenderingTest :: TestTree commandRenderingTest =