packages feed

claude 1.1.0 → 1.2.0

raw patch · 8 files changed

+181/−2 lines, 8 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Claude.V1.Messages: ContentBlock_Redacted_Thinking :: Text -> ContentBlock
+ Claude.V1.Messages: ContentBlock_Thinking :: Text -> Text -> ContentBlock
+ Claude.V1.Messages: Content_Redacted_Thinking :: Text -> Content
+ Claude.V1.Messages: Content_Thinking :: Text -> Text -> Content
+ Claude.V1.Messages: Delta_Signature_Delta :: Text -> ContentBlockDelta
+ Claude.V1.Messages: Delta_Thinking_Delta :: Text -> ContentBlockDelta
+ Claude.V1.Messages: Model_Context_Window_Exceeded :: StopReason
+ Claude.V1.Messages: ThinkingAdaptive :: Thinking
+ Claude.V1.Messages: ThinkingEnabled :: Natural -> Thinking
+ Claude.V1.Messages: [budget_tokens] :: Thinking -> Natural
+ Claude.V1.Messages: [signature] :: ContentBlockDelta -> Text
+ Claude.V1.Messages: [thinking] :: ContentBlockDelta -> Text
+ Claude.V1.Messages: data Thinking
+ Claude.V1.Messages: instance Data.Aeson.Types.FromJSON.FromJSON Claude.V1.Messages.Thinking
+ Claude.V1.Messages: instance Data.Aeson.Types.ToJSON.ToJSON Claude.V1.Messages.Thinking
+ Claude.V1.Messages: instance GHC.Classes.Eq Claude.V1.Messages.Thinking
+ Claude.V1.Messages: instance GHC.Generics.Generic Claude.V1.Messages.Thinking
+ Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.Thinking
- Claude.V1.Messages: CreateMessage :: Text -> Vector Message -> Natural -> Maybe Text -> Maybe Double -> Maybe Double -> Maybe Natural -> Maybe (Vector Text) -> Maybe Bool -> Maybe (Map Text Text) -> Maybe (Vector ToolDefinition) -> Maybe ToolChoice -> Maybe Text -> Maybe OutputConfig -> CreateMessage
+ Claude.V1.Messages: CreateMessage :: Text -> Vector Message -> Natural -> Maybe Text -> Maybe Double -> Maybe Double -> Maybe Natural -> Maybe (Vector Text) -> Maybe Bool -> Maybe (Map Text Text) -> Maybe (Vector ToolDefinition) -> Maybe ToolChoice -> Maybe Text -> Maybe OutputConfig -> Maybe Thinking -> CreateMessage

Files

CHANGELOG.md view
@@ -1,3 +1,12 @@+1.2.0:++- Add `Thinking` type with `ThinkingAdaptive` and `ThinkingEnabled` constructors+  - `thinking` field on `CreateMessage`+  - `ContentBlock_Thinking` and `ContentBlock_Redacted_Thinking` response blocks+  - `Content_Thinking` and `Content_Redacted_Thinking` for replaying thinking in multi-turn+  - `Delta_Thinking_Delta` and `Delta_Signature_Delta` for streaming+- Add `Model_Context_Window_Exceeded` constructor to `StopReason`+ 1.1.0:  - Replace `output_format` with `output_config` on `CreateMessage` (breaking change)
claude.cabal view
@@ -1,6 +1,6 @@ cabal-version:      2.4 name:               claude-version:            1.1.0+version:            1.2.0 synopsis:           Servant bindings to Anthropic's Claude API description:        This package provides comprehensive and type-safe bindings                     to Anthropic's Claude API, providing both a Servant interface
examples/claude-programmatic-tool-calling-example/Main.hs view
@@ -249,6 +249,10 @@   where     unless False action = action     unless True _ = pure ()+printContentBlock (Messages.ContentBlock_Thinking{}) =+    Text.IO.putStrLn "  [Thinking]"+printContentBlock (Messages.ContentBlock_Redacted_Thinking{}) =+    Text.IO.putStrLn "  [Redacted thinking]" printContentBlock (Messages.ContentBlock_Unknown{ Messages.type_ = t }) =     Text.IO.putStrLn $ "  [Unknown] " <> t 
examples/claude-stream-example/Main.hs view
@@ -27,6 +27,8 @@                 case d of                     Messages.Delta_Text_Delta{ Messages.text = t } ->                         TIO.putStr t >> hFlush stdout+                    Messages.Delta_Thinking_Delta{ Messages.thinking = t } ->+                        TIO.putStr t >> hFlush stdout                     _ -> pure ()             -- Print newline when message is done             Messages.Message_Stop -> putStrLn ""
examples/claude-tool-example/Main.hs view
@@ -186,5 +186,9 @@     Text.IO.putStrLn $ "[Tool search result for: " <> tid <> "]" printContent (Messages.ContentBlock_Code_Execution_Tool_Result{ Messages.tool_use_id = tid, Messages.code_execution_content = _ }) =     Text.IO.putStrLn $ "[Code execution result for: " <> tid <> "]"+printContent (Messages.ContentBlock_Thinking{}) =+    Text.IO.putStrLn "[Thinking]"+printContent (Messages.ContentBlock_Redacted_Thinking{}) =+    Text.IO.putStrLn "[Redacted thinking]" printContent (Messages.ContentBlock_Unknown{ Messages.type_ = t }) =     Text.IO.putStrLn $ "[Unknown block type: " <> t <> "]"
examples/claude-tool-search-example/Main.hs view
@@ -193,5 +193,9 @@             Text.IO.putStrLn "    Unknown result type" printContent (Messages.ContentBlock_Code_Execution_Tool_Result{ Messages.tool_use_id = tid }) =     Text.IO.putStrLn $ "  [code_execution_result] for tool_use_id: " <> tid+printContent (Messages.ContentBlock_Thinking{}) =+    Text.IO.putStrLn "  [thinking]"+printContent (Messages.ContentBlock_Redacted_Thinking{}) =+    Text.IO.putStrLn "  [redacted_thinking]" printContent (Messages.ContentBlock_Unknown{ Messages.type_ = t }) =     Text.IO.putStrLn $ "  [unknown] type: " <> t
src/Claude/V1/Messages.hs view
@@ -13,6 +13,8 @@     , jsonSchemaConfig     , jsonSchemaFormat     , effortConfig+      -- * Thinking+    , Thinking(..)       -- * Content types     , Content(..)     , ContentBlock(..)@@ -194,6 +196,8 @@         , input :: Value         }     | Content_Tool_Result { tool_use_id :: Text, content :: Maybe Text, is_error :: Maybe Bool }+    | Content_Thinking { thinking :: Text, signature :: Text }+    | Content_Redacted_Thinking { data_ :: Text }     deriving stock (Generic, Show)  -- | Create a text content block without cache control@@ -245,6 +249,10 @@         } contentBlockToContent ContentBlock_Tool_Search_Tool_Result{} = Nothing contentBlockToContent ContentBlock_Code_Execution_Tool_Result{} = Nothing+contentBlockToContent (ContentBlock_Thinking t sig) =+    Just Content_Thinking{ thinking = t, signature = sig }+contentBlockToContent (ContentBlock_Redacted_Thinking d) =+    Just Content_Redacted_Thinking{ data_ = d } contentBlockToContent ContentBlock_Unknown{} = Nothing  -- | A reference to a tool found by tool search@@ -411,6 +419,8 @@         { tool_use_id :: Text         , code_execution_content :: CodeExecutionToolResultContent         }+    | ContentBlock_Thinking { thinking :: Text, signature :: Text }+    | ContentBlock_Redacted_Thinking { data_ :: Text }     | ContentBlock_Unknown { type_ :: Text, raw :: Value }     deriving stock (Generic, Show) @@ -442,6 +452,11 @@                     { tool_use_id = toolUseId                     , code_execution_content = execContent                     }+            "thinking" -> ContentBlock_Thinking+                <$> o Aeson..: "thinking"+                <*> o Aeson..: "signature"+            "redacted_thinking" -> ContentBlock_Redacted_Thinking+                <$> o Aeson..: "data"             _ -> pure (ContentBlock_Unknown t (Aeson.Object o))  instance ToJSON ContentBlock where@@ -471,6 +486,15 @@         , "tool_use_id" Aeson..= toolUseId         , "content" Aeson..= execContent         ]+    toJSON (ContentBlock_Thinking t sig) = Aeson.object+        [ "type" Aeson..= ("thinking" :: Text)+        , "thinking" Aeson..= t+        , "signature" Aeson..= sig+        ]+    toJSON (ContentBlock_Redacted_Thinking d) = Aeson.object+        [ "type" Aeson..= ("redacted_thinking" :: Text)+        , "data" Aeson..= d+        ]     toJSON (ContentBlock_Unknown _typeName rawVal) = rawVal  -- | A message in the conversation@@ -496,7 +520,9 @@     | Stop_Sequence     | Tool_Use     | Refusal-    -- ^ Model refused the request for safety reasons (structured outputs only)+    -- ^ Model refused the request for safety reasons+    | Model_Context_Window_Exceeded+    -- ^ Generation stopped because the context window limit was reached (Claude 4.5+)     deriving stock (Eq, Generic, Show)  stopReasonOptions :: Options@@ -664,6 +690,43 @@     , format = Just $ jsonSchemaFormat s     } +-- | Thinking configuration for extended thinking+--+-- * 'ThinkingAdaptive': Let Claude decide when and how much to think (Opus 4.6+).+--   Use the effort parameter ('effortConfig') to guide thinking depth.+-- * 'ThinkingEnabled': Enable thinking with a fixed token budget+--   (all models; deprecated on Opus 4.6).+--+-- @+-- -- Adaptive thinking (recommended for Opus 4.6)+-- thinking = Just ThinkingAdaptive+--+-- -- Manual thinking with budget (older models)+-- thinking = Just ThinkingEnabled{ budget_tokens = 32000 }+-- @+data Thinking+    = ThinkingAdaptive+    | ThinkingEnabled { budget_tokens :: Natural }+    deriving stock (Eq, Generic, Show)++instance FromJSON Thinking where+    parseJSON = Aeson.withObject "Thinking" $ \o -> do+        t <- o Aeson..: "type"+        case (t :: Text) of+            "adaptive" -> pure ThinkingAdaptive+            "enabled" -> do+                budget <- o Aeson..: "budget_tokens"+                pure ThinkingEnabled{ budget_tokens = budget }+            _ -> fail $ "Unknown thinking type: " <> show t++instance ToJSON Thinking where+    toJSON ThinkingAdaptive = Aeson.object+        [ "type" Aeson..= ("adaptive" :: Text) ]+    toJSON (ThinkingEnabled budget) = Aeson.object+        [ "type" Aeson..= ("enabled" :: Text)+        , "budget_tokens" Aeson..= budget+        ]+ -- | Create a JSON schema output format jsonSchemaFormat :: Value -> OutputFormat jsonSchemaFormat s = OutputFormat@@ -692,6 +755,9 @@     , output_config :: Maybe OutputConfig     -- ^ Output configuration: effort level and/or structured output format.     -- Use 'effortConfig', 'jsonSchemaConfig', or construct 'OutputConfig' directly.+    , thinking :: Maybe Thinking+    -- ^ Thinking configuration. Use 'ThinkingAdaptive' for Opus 4.6+    -- or 'ThinkingEnabled' with @budget_tokens@ for older models.     } deriving stock (Generic, Show)  instance FromJSON CreateMessage where@@ -717,6 +783,7 @@     , tool_choice = Nothing     , container = Nothing     , output_config = Nothing+    , thinking = Nothing     }  -- | Text delta in streaming@@ -740,6 +807,8 @@ data ContentBlockDelta     = Delta_Text_Delta { text :: Text }     | Delta_Input_Json_Delta { partial_json :: Text }+    | Delta_Thinking_Delta { thinking :: Text }+    | Delta_Signature_Delta { signature :: Text }     deriving stock (Generic, Show)  contentBlockDeltaOptions :: Options@@ -749,6 +818,8 @@     , constructorTagModifier = \s -> case s of         "Delta_Text_Delta" -> "text_delta"         "Delta_Input_Json_Delta" -> "input_json_delta"+        "Delta_Thinking_Delta" -> "thinking_delta"+        "Delta_Signature_Delta" -> "signature_delta"         _ -> s     } 
tasty/Main.hs view
@@ -339,6 +339,84 @@                                 HUnit.assertFailure "user_id should be present and be an integer"                     _ -> HUnit.assertFailure "Expected exactly one tool_use block" +    let adaptiveThinkingTest =+            HUnit.testCase "Create message - adaptive thinking" do+                Messages.MessageResponse{ content } <-+                    createMessage+                        Messages._CreateMessage+                            { Messages.model = "claude-opus-4-6"+                            , Messages.messages =+                                [ Messages.Message+                                    { Messages.role = Messages.User+                                    , Messages.content =+                                        [ Messages.textContent "What is 2+2? Answer with just the number."+                                        ]+                                    , Messages.cache_control = Nothing+                                    }+                                ]+                            , Messages.max_tokens = 4096+                            , Messages.thinking = Just Messages.ThinkingAdaptive+                            }++                -- Should have at least one text block+                let hasText = any isTextBlock (toList content)+                HUnit.assertBool "Should have text content" hasText++                -- Thinking blocks may or may not be present (model decides),+                -- but verify we can parse them without errors+                let thinkingBlocks =+                        [ ()+                        | Messages.ContentBlock_Thinking{} <- toList content+                        ]+                let redactedBlocks =+                        [ ()+                        | Messages.ContentBlock_Redacted_Thinking{} <- toList content+                        ]+                -- Just verify counts are non-negative (always true, but exercises the pattern matches)+                HUnit.assertBool "Thinking block count should be non-negative"+                    (length thinkingBlocks >= 0 && length redactedBlocks >= 0)++    let adaptiveThinkingStreamingTest =+            HUnit.testCase "Create message - adaptive thinking streaming" do+                textAcc <- IORef.newIORef Text.empty+                thinkingAcc <- IORef.newIORef Text.empty+                done <- Concurrent.newEmptyMVar++                let onEvent (Left _err) = Concurrent.putMVar done ()+                    onEvent (Right ev) = case ev of+                        Messages.Content_Block_Delta{ Messages.delta = d } ->+                            case d of+                                Messages.Delta_Text_Delta{ Messages.text = t } ->+                                    IORef.modifyIORef' textAcc (<> t)+                                Messages.Delta_Thinking_Delta{ Messages.thinking = t } ->+                                    IORef.modifyIORef' thinkingAcc (<> t)+                                _ -> pure ()+                        Messages.Message_Stop ->+                            Concurrent.putMVar done ()+                        _ -> pure ()++                createMessageStreamTyped+                    Messages._CreateMessage+                        { Messages.model = "claude-opus-4-6"+                        , Messages.messages =+                            [ Messages.Message+                                { Messages.role = Messages.User+                                , Messages.content =+                                    [ Messages.textContent "What is 2+2? Answer with just the number."+                                    ]+                                , Messages.cache_control = Nothing+                                }+                            ]+                        , Messages.max_tokens = 4096+                        , Messages.thinking = Just Messages.ThinkingAdaptive+                        }+                    onEvent++                _ <- Concurrent.takeMVar done+                text <- IORef.readIORef textAcc+                HUnit.assertBool "Expected non-empty streamed text"+                    (not (Text.null text))+     -- Tool search test requires beta header     let betaOptions = V1.defaultClientOptions             { V1.apiKey = Text.pack key@@ -557,11 +635,18 @@             , tokenCountingTest             , jsonOutputsTest             , strictToolUseTest+            , adaptiveThinkingTest+            , adaptiveThinkingStreamingTest             , toolSearchTest             , programmaticToolCallingTest             ]      Tasty.defaultMain (Tasty.testGroup "Claude API Tests" tests)++-- | Check if a content block is a text block+isTextBlock :: Messages.ContentBlock -> Bool+isTextBlock (Messages.ContentBlock_Text{}) = True+isTextBlock _ = False  -- | Check if a content block is a server_tool_use for code_execution isCodeExecutionServerToolUse :: Messages.ContentBlock -> Bool