packages feed

claude 1.0.0 → 1.0.1

raw patch · 10 files changed

+565/−124 lines, 10 filesdep ~aesondep ~basedep ~bytestringnew-component:exe:claude-structured-outputs-examplePVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependency ranges changed: aeson, base, bytestring, text

API changes (from Hackage documentation)

- Claude.V1: instance GHC.Show.Show Claude.V1.ClientOptions
- Claude.V1.Error: instance GHC.Generics.Generic Claude.V1.Error.Error
- Claude.V1.Error: instance GHC.Generics.Generic Claude.V1.Error.ErrorType
- Claude.V1.Error: instance GHC.Show.Show Claude.V1.Error.Error
- Claude.V1.Error: instance GHC.Show.Show Claude.V1.Error.ErrorType
- Claude.V1.Messages: instance GHC.Generics.Generic Claude.V1.Messages.CacheControl
- Claude.V1.Messages: instance GHC.Generics.Generic Claude.V1.Messages.CodeExecutionResult
- Claude.V1.Messages: instance GHC.Generics.Generic Claude.V1.Messages.ContainerInfo
- Claude.V1.Messages: instance GHC.Generics.Generic Claude.V1.Messages.Content
- Claude.V1.Messages: instance GHC.Generics.Generic Claude.V1.Messages.ContentBlock
- Claude.V1.Messages: instance GHC.Generics.Generic Claude.V1.Messages.ContentBlockDelta
- Claude.V1.Messages: instance GHC.Generics.Generic Claude.V1.Messages.CountTokensRequest
- Claude.V1.Messages: instance GHC.Generics.Generic Claude.V1.Messages.CreateMessage
- Claude.V1.Messages: instance GHC.Generics.Generic Claude.V1.Messages.ImageSource
- Claude.V1.Messages: instance GHC.Generics.Generic Claude.V1.Messages.InputJsonDelta
- Claude.V1.Messages: instance GHC.Generics.Generic Claude.V1.Messages.Message
- Claude.V1.Messages: instance GHC.Generics.Generic Claude.V1.Messages.MessageDelta
- Claude.V1.Messages: instance GHC.Generics.Generic Claude.V1.Messages.MessageResponse
- Claude.V1.Messages: instance GHC.Generics.Generic Claude.V1.Messages.MessageStreamEvent
- Claude.V1.Messages: instance GHC.Generics.Generic Claude.V1.Messages.Role
- Claude.V1.Messages: instance GHC.Generics.Generic Claude.V1.Messages.ServerToolUseUsage
- Claude.V1.Messages: instance GHC.Generics.Generic Claude.V1.Messages.StopReason
- Claude.V1.Messages: instance GHC.Generics.Generic Claude.V1.Messages.StreamUsage
- Claude.V1.Messages: instance GHC.Generics.Generic Claude.V1.Messages.TextContent
- Claude.V1.Messages: instance GHC.Generics.Generic Claude.V1.Messages.TextDelta
- Claude.V1.Messages: instance GHC.Generics.Generic Claude.V1.Messages.TokenCount
- Claude.V1.Messages: instance GHC.Generics.Generic Claude.V1.Messages.ToolReference
- Claude.V1.Messages: instance GHC.Generics.Generic Claude.V1.Messages.ToolResultContent
- Claude.V1.Messages: instance GHC.Generics.Generic Claude.V1.Messages.ToolUseContent
- Claude.V1.Messages: instance GHC.Generics.Generic Claude.V1.Messages.Usage
- Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.CacheControl
- Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.CodeExecutionResult
- Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.CodeExecutionToolResultContent
- Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.ContainerInfo
- Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.Content
- Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.ContentBlock
- Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.ContentBlockDelta
- Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.CountTokensRequest
- Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.CreateMessage
- Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.ImageSource
- Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.InputJsonDelta
- Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.Message
- Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.MessageDelta
- Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.MessageResponse
- Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.MessageStreamEvent
- Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.Role
- Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.ServerToolUseUsage
- Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.StopReason
- Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.StreamUsage
- Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.TextContent
- Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.TextDelta
- Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.TokenCount
- Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.ToolCaller
- Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.ToolReference
- Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.ToolResultContent
- Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.ToolSearchToolResultContent
- Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.ToolUseContent
- Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.Usage
- Claude.V1.Messages.Batches: instance GHC.Generics.Generic Claude.V1.Messages.Batches.BatchObject
- Claude.V1.Messages.Batches: instance GHC.Generics.Generic Claude.V1.Messages.Batches.BatchRequest
- Claude.V1.Messages.Batches: instance GHC.Generics.Generic Claude.V1.Messages.Batches.BatchResult
- Claude.V1.Messages.Batches: instance GHC.Generics.Generic Claude.V1.Messages.Batches.BatchResultType
- Claude.V1.Messages.Batches: instance GHC.Generics.Generic Claude.V1.Messages.Batches.CreateBatch
- Claude.V1.Messages.Batches: instance GHC.Generics.Generic Claude.V1.Messages.Batches.ListBatchesResponse
- Claude.V1.Messages.Batches: instance GHC.Generics.Generic Claude.V1.Messages.Batches.ProcessingStatus
- Claude.V1.Messages.Batches: instance GHC.Generics.Generic Claude.V1.Messages.Batches.RequestCounts
- Claude.V1.Messages.Batches: instance GHC.Show.Show Claude.V1.Messages.Batches.BatchObject
- Claude.V1.Messages.Batches: instance GHC.Show.Show Claude.V1.Messages.Batches.BatchRequest
- Claude.V1.Messages.Batches: instance GHC.Show.Show Claude.V1.Messages.Batches.BatchResult
- Claude.V1.Messages.Batches: instance GHC.Show.Show Claude.V1.Messages.Batches.BatchResultType
- Claude.V1.Messages.Batches: instance GHC.Show.Show Claude.V1.Messages.Batches.CreateBatch
- Claude.V1.Messages.Batches: instance GHC.Show.Show Claude.V1.Messages.Batches.ListBatchesResponse
- Claude.V1.Messages.Batches: instance GHC.Show.Show Claude.V1.Messages.Batches.ProcessingStatus
- Claude.V1.Messages.Batches: instance GHC.Show.Show Claude.V1.Messages.Batches.RequestCounts
- Claude.V1.Tool: instance GHC.Generics.Generic Claude.V1.Tool.InputSchema
- Claude.V1.Tool: instance GHC.Generics.Generic Claude.V1.Tool.Tool
- Claude.V1.Tool: instance GHC.Generics.Generic Claude.V1.Tool.ToolChoice
- Claude.V1.Tool: instance GHC.Show.Show Claude.V1.Tool.InputSchema
- Claude.V1.Tool: instance GHC.Show.Show Claude.V1.Tool.Tool
- Claude.V1.Tool: instance GHC.Show.Show Claude.V1.Tool.ToolChoice
- Claude.V1.Tool: instance GHC.Show.Show Claude.V1.Tool.ToolDefinition
- Claude.V1.Tool: instance GHC.Show.Show Claude.V1.Tool.ToolSearchTool
- Claude.V1.Tool: instance GHC.Show.Show Claude.V1.Tool.ToolSearchToolType
+ Claude.V1: instance GHC.Internal.Show.Show Claude.V1.ClientOptions
+ Claude.V1.Error: instance GHC.Internal.Generics.Generic Claude.V1.Error.Error
+ Claude.V1.Error: instance GHC.Internal.Generics.Generic Claude.V1.Error.ErrorType
+ Claude.V1.Error: instance GHC.Internal.Show.Show Claude.V1.Error.Error
+ Claude.V1.Error: instance GHC.Internal.Show.Show Claude.V1.Error.ErrorType
+ Claude.V1.Messages: OutputFormat :: Text -> Value -> OutputFormat
+ Claude.V1.Messages: Refusal :: StopReason
+ Claude.V1.Messages: [additionalProperties] :: InputSchema -> Maybe Bool
+ Claude.V1.Messages: [output_format] :: CreateMessage -> Maybe OutputFormat
+ Claude.V1.Messages: [schema] :: OutputFormat -> Value
+ Claude.V1.Messages: [strict] :: Tool -> Maybe Bool
+ Claude.V1.Messages: data OutputFormat
+ Claude.V1.Messages: instance Data.Aeson.Types.FromJSON.FromJSON Claude.V1.Messages.OutputFormat
+ Claude.V1.Messages: instance Data.Aeson.Types.ToJSON.ToJSON Claude.V1.Messages.OutputFormat
+ Claude.V1.Messages: instance GHC.Classes.Eq Claude.V1.Messages.OutputFormat
+ Claude.V1.Messages: instance GHC.Internal.Generics.Generic Claude.V1.Messages.CacheControl
+ Claude.V1.Messages: instance GHC.Internal.Generics.Generic Claude.V1.Messages.CodeExecutionResult
+ Claude.V1.Messages: instance GHC.Internal.Generics.Generic Claude.V1.Messages.ContainerInfo
+ Claude.V1.Messages: instance GHC.Internal.Generics.Generic Claude.V1.Messages.Content
+ Claude.V1.Messages: instance GHC.Internal.Generics.Generic Claude.V1.Messages.ContentBlock
+ Claude.V1.Messages: instance GHC.Internal.Generics.Generic Claude.V1.Messages.ContentBlockDelta
+ Claude.V1.Messages: instance GHC.Internal.Generics.Generic Claude.V1.Messages.CountTokensRequest
+ Claude.V1.Messages: instance GHC.Internal.Generics.Generic Claude.V1.Messages.CreateMessage
+ Claude.V1.Messages: instance GHC.Internal.Generics.Generic Claude.V1.Messages.ImageSource
+ Claude.V1.Messages: instance GHC.Internal.Generics.Generic Claude.V1.Messages.InputJsonDelta
+ Claude.V1.Messages: instance GHC.Internal.Generics.Generic Claude.V1.Messages.Message
+ Claude.V1.Messages: instance GHC.Internal.Generics.Generic Claude.V1.Messages.MessageDelta
+ Claude.V1.Messages: instance GHC.Internal.Generics.Generic Claude.V1.Messages.MessageResponse
+ Claude.V1.Messages: instance GHC.Internal.Generics.Generic Claude.V1.Messages.MessageStreamEvent
+ Claude.V1.Messages: instance GHC.Internal.Generics.Generic Claude.V1.Messages.OutputFormat
+ Claude.V1.Messages: instance GHC.Internal.Generics.Generic Claude.V1.Messages.Role
+ Claude.V1.Messages: instance GHC.Internal.Generics.Generic Claude.V1.Messages.ServerToolUseUsage
+ Claude.V1.Messages: instance GHC.Internal.Generics.Generic Claude.V1.Messages.StopReason
+ Claude.V1.Messages: instance GHC.Internal.Generics.Generic Claude.V1.Messages.StreamUsage
+ Claude.V1.Messages: instance GHC.Internal.Generics.Generic Claude.V1.Messages.TextContent
+ Claude.V1.Messages: instance GHC.Internal.Generics.Generic Claude.V1.Messages.TextDelta
+ Claude.V1.Messages: instance GHC.Internal.Generics.Generic Claude.V1.Messages.TokenCount
+ Claude.V1.Messages: instance GHC.Internal.Generics.Generic Claude.V1.Messages.ToolReference
+ Claude.V1.Messages: instance GHC.Internal.Generics.Generic Claude.V1.Messages.ToolResultContent
+ Claude.V1.Messages: instance GHC.Internal.Generics.Generic Claude.V1.Messages.ToolUseContent
+ Claude.V1.Messages: instance GHC.Internal.Generics.Generic Claude.V1.Messages.Usage
+ Claude.V1.Messages: instance GHC.Internal.Show.Show Claude.V1.Messages.CacheControl
+ Claude.V1.Messages: instance GHC.Internal.Show.Show Claude.V1.Messages.CodeExecutionResult
+ Claude.V1.Messages: instance GHC.Internal.Show.Show Claude.V1.Messages.CodeExecutionToolResultContent
+ Claude.V1.Messages: instance GHC.Internal.Show.Show Claude.V1.Messages.ContainerInfo
+ Claude.V1.Messages: instance GHC.Internal.Show.Show Claude.V1.Messages.Content
+ Claude.V1.Messages: instance GHC.Internal.Show.Show Claude.V1.Messages.ContentBlock
+ Claude.V1.Messages: instance GHC.Internal.Show.Show Claude.V1.Messages.ContentBlockDelta
+ Claude.V1.Messages: instance GHC.Internal.Show.Show Claude.V1.Messages.CountTokensRequest
+ Claude.V1.Messages: instance GHC.Internal.Show.Show Claude.V1.Messages.CreateMessage
+ Claude.V1.Messages: instance GHC.Internal.Show.Show Claude.V1.Messages.ImageSource
+ Claude.V1.Messages: instance GHC.Internal.Show.Show Claude.V1.Messages.InputJsonDelta
+ Claude.V1.Messages: instance GHC.Internal.Show.Show Claude.V1.Messages.Message
+ Claude.V1.Messages: instance GHC.Internal.Show.Show Claude.V1.Messages.MessageDelta
+ Claude.V1.Messages: instance GHC.Internal.Show.Show Claude.V1.Messages.MessageResponse
+ Claude.V1.Messages: instance GHC.Internal.Show.Show Claude.V1.Messages.MessageStreamEvent
+ Claude.V1.Messages: instance GHC.Internal.Show.Show Claude.V1.Messages.OutputFormat
+ Claude.V1.Messages: instance GHC.Internal.Show.Show Claude.V1.Messages.Role
+ Claude.V1.Messages: instance GHC.Internal.Show.Show Claude.V1.Messages.ServerToolUseUsage
+ Claude.V1.Messages: instance GHC.Internal.Show.Show Claude.V1.Messages.StopReason
+ Claude.V1.Messages: instance GHC.Internal.Show.Show Claude.V1.Messages.StreamUsage
+ Claude.V1.Messages: instance GHC.Internal.Show.Show Claude.V1.Messages.TextContent
+ Claude.V1.Messages: instance GHC.Internal.Show.Show Claude.V1.Messages.TextDelta
+ Claude.V1.Messages: instance GHC.Internal.Show.Show Claude.V1.Messages.TokenCount
+ Claude.V1.Messages: instance GHC.Internal.Show.Show Claude.V1.Messages.ToolCaller
+ Claude.V1.Messages: instance GHC.Internal.Show.Show Claude.V1.Messages.ToolReference
+ Claude.V1.Messages: instance GHC.Internal.Show.Show Claude.V1.Messages.ToolResultContent
+ Claude.V1.Messages: instance GHC.Internal.Show.Show Claude.V1.Messages.ToolSearchToolResultContent
+ Claude.V1.Messages: instance GHC.Internal.Show.Show Claude.V1.Messages.ToolUseContent
+ Claude.V1.Messages: instance GHC.Internal.Show.Show Claude.V1.Messages.Usage
+ Claude.V1.Messages: jsonSchemaFormat :: Value -> OutputFormat
+ Claude.V1.Messages: strictFunctionTool :: Text -> Maybe Text -> Value -> Tool
+ Claude.V1.Messages.Batches: instance GHC.Internal.Generics.Generic Claude.V1.Messages.Batches.BatchObject
+ Claude.V1.Messages.Batches: instance GHC.Internal.Generics.Generic Claude.V1.Messages.Batches.BatchRequest
+ Claude.V1.Messages.Batches: instance GHC.Internal.Generics.Generic Claude.V1.Messages.Batches.BatchResult
+ Claude.V1.Messages.Batches: instance GHC.Internal.Generics.Generic Claude.V1.Messages.Batches.BatchResultType
+ Claude.V1.Messages.Batches: instance GHC.Internal.Generics.Generic Claude.V1.Messages.Batches.CreateBatch
+ Claude.V1.Messages.Batches: instance GHC.Internal.Generics.Generic Claude.V1.Messages.Batches.ListBatchesResponse
+ Claude.V1.Messages.Batches: instance GHC.Internal.Generics.Generic Claude.V1.Messages.Batches.ProcessingStatus
+ Claude.V1.Messages.Batches: instance GHC.Internal.Generics.Generic Claude.V1.Messages.Batches.RequestCounts
+ Claude.V1.Messages.Batches: instance GHC.Internal.Show.Show Claude.V1.Messages.Batches.BatchObject
+ Claude.V1.Messages.Batches: instance GHC.Internal.Show.Show Claude.V1.Messages.Batches.BatchRequest
+ Claude.V1.Messages.Batches: instance GHC.Internal.Show.Show Claude.V1.Messages.Batches.BatchResult
+ Claude.V1.Messages.Batches: instance GHC.Internal.Show.Show Claude.V1.Messages.Batches.BatchResultType
+ Claude.V1.Messages.Batches: instance GHC.Internal.Show.Show Claude.V1.Messages.Batches.CreateBatch
+ Claude.V1.Messages.Batches: instance GHC.Internal.Show.Show Claude.V1.Messages.Batches.ListBatchesResponse
+ Claude.V1.Messages.Batches: instance GHC.Internal.Show.Show Claude.V1.Messages.Batches.ProcessingStatus
+ Claude.V1.Messages.Batches: instance GHC.Internal.Show.Show Claude.V1.Messages.Batches.RequestCounts
+ Claude.V1.Tool: [additionalProperties] :: InputSchema -> Maybe Bool
+ Claude.V1.Tool: [strict] :: Tool -> Maybe Bool
+ Claude.V1.Tool: instance GHC.Internal.Generics.Generic Claude.V1.Tool.InputSchema
+ Claude.V1.Tool: instance GHC.Internal.Generics.Generic Claude.V1.Tool.Tool
+ Claude.V1.Tool: instance GHC.Internal.Generics.Generic Claude.V1.Tool.ToolChoice
+ Claude.V1.Tool: instance GHC.Internal.Show.Show Claude.V1.Tool.InputSchema
+ Claude.V1.Tool: instance GHC.Internal.Show.Show Claude.V1.Tool.Tool
+ Claude.V1.Tool: instance GHC.Internal.Show.Show Claude.V1.Tool.ToolChoice
+ Claude.V1.Tool: instance GHC.Internal.Show.Show Claude.V1.Tool.ToolDefinition
+ Claude.V1.Tool: instance GHC.Internal.Show.Show Claude.V1.Tool.ToolSearchTool
+ Claude.V1.Tool: instance GHC.Internal.Show.Show Claude.V1.Tool.ToolSearchToolType
+ Claude.V1.Tool: strictFunctionTool :: Text -> Maybe Text -> Value -> Tool
- 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 -> 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 OutputFormat -> CreateMessage
- Claude.V1.Messages: InputSchema :: Text -> Maybe Value -> Maybe (Vector Text) -> InputSchema
+ Claude.V1.Messages: InputSchema :: Text -> Maybe Value -> Maybe (Vector Text) -> Maybe Bool -> InputSchema
- Claude.V1.Messages: Tool :: Text -> Maybe Text -> InputSchema -> Tool
+ Claude.V1.Messages: Tool :: Text -> Maybe Text -> InputSchema -> Maybe Bool -> Tool
- Claude.V1.Tool: InputSchema :: Text -> Maybe Value -> Maybe (Vector Text) -> InputSchema
+ Claude.V1.Tool: InputSchema :: Text -> Maybe Value -> Maybe (Vector Text) -> Maybe Bool -> InputSchema
- Claude.V1.Tool: Tool :: Text -> Maybe Text -> InputSchema -> Tool
+ Claude.V1.Tool: Tool :: Text -> Maybe Text -> InputSchema -> Maybe Bool -> Tool

Files

CHANGELOG.md view
@@ -1,3 +1,13 @@+1.0.1:++- Add structured outputs support (beta feature `structured-outputs-2025-11-13`)+  - `OutputFormat` type and `jsonSchemaFormat` helper for JSON outputs+  - `output_format` field on `CreateMessage`+  - `strict` field on `Tool` and `strictFunctionTool` helper for strict tool validation+  - `additionalProperties` field on `InputSchema`+  - `Refusal` constructor for `StopReason`+- Add `claude-structured-outputs-example`+ 1.0.0:  - Initial release
README.md view
@@ -80,6 +80,8 @@  ## Running the Examples +See [examples/README.md](examples/README.md) for descriptions of each example.+ ```bash cabal run claude-example cabal run claude-stream-example@@ -87,21 +89,26 @@ cabal run claude-vision-example cabal run claude-tool-search-example cabal run claude-programmatic-tool-calling-example+cabal run claude-structured-outputs-example ```  ### Advanced Examples -The `claude-tool-search-example` and `claude-programmatic-tool-calling-example` demonstrate beta features that require the `advanced-tool-use-2025-11-20` beta header:+Several examples demonstrate beta features: -- **[Tool Search Tool](https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool)**: Server-side tool search for efficiently handling large numbers of tools-- **[Programmatic Tool Calling (PTC)](https://platform.claude.com/docs/en/agents-and-tools/tool-use/programmatic-tool-calling)**: Claude writes and executes code to call multiple tools and aggregate results+**Tool Search & Programmatic Tool Calling** (`advanced-tool-use-2025-11-20`):+- **[Tool Search Tool](https://docs.anthropic.com/en/docs/agents-and-tools/tool-use/tool-search-tool)**: Server-side tool search for efficiently handling large numbers of tools+- **[Programmatic Tool Calling (PTC)](https://docs.anthropic.com/en/docs/agents-and-tools/tool-use/programmatic-tool-calling)**: Claude writes and executes code to call multiple tools and aggregate results -To enable these features, use `makeMethodsWith` with the beta header:+**Structured Outputs** (`structured-outputs-2025-11-13`):+- **[Structured Outputs](https://docs.anthropic.com/en/docs/build-with-claude/structured-outputs)**: Constrain Claude's responses to follow a specific JSON schema, or validate tool parameters with strict mode +To enable beta features, use `makeMethodsWith` with the appropriate beta header:+ ```haskell let options = defaultClientOptions         { apiKey = key-        , anthropicBeta = Just "advanced-tool-use-2025-11-20"+        , anthropicBeta = Just "structured-outputs-2025-11-13"         } let Methods{ createMessage } = makeMethodsWith clientEnv options ```
claude.cabal view
@@ -1,6 +1,6 @@ cabal-version:      2.4 name:               claude-version:            1.0.0+version:            1.0.1 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@@ -84,7 +84,7 @@                       , OverloadedLists                       , OverloadedStrings                       , RecordWildCards-    ghc-options:      -Wall+    ghc-options:      -Wall -Wno-missing-fields  executable claude-example     default-language: Haskell2010@@ -127,7 +127,7 @@                       , NamedFieldPuns                       , OverloadedLists                       , OverloadedStrings-    ghc-options:      -Wall+    ghc-options:      -Wall -Wno-missing-fields  executable claude-vision-example     default-language: Haskell2010@@ -142,7 +142,7 @@                       , NamedFieldPuns                       , OverloadedLists                       , OverloadedStrings-    ghc-options:      -Wall+    ghc-options:      -Wall -Wno-missing-fields  executable claude-tool-search-example     default-language: Haskell2010@@ -156,7 +156,7 @@                       , NamedFieldPuns                       , OverloadedLists                       , OverloadedStrings-    ghc-options:      -Wall+    ghc-options:      -Wall -Wno-missing-fields  executable claude-programmatic-tool-calling-example     default-language: Haskell2010@@ -171,4 +171,19 @@                       , NamedFieldPuns                       , OverloadedLists                       , OverloadedStrings-    ghc-options:      -Wall+    ghc-options:      -Wall -Wno-missing-fields++executable claude-structured-outputs-example+    default-language: Haskell2010+    hs-source-dirs:   examples/claude-structured-outputs-example+    main-is:          Main.hs+    build-depends:    base+                    , aeson+                    , bytestring+                    , claude+                    , text+    default-extensions: DuplicateRecordFields+                      , NamedFieldPuns+                      , OverloadedLists+                      , OverloadedStrings+    ghc-options:      -Wall -Wno-missing-fields
examples/claude-programmatic-tool-calling-example/Main.hs view
@@ -33,21 +33,19 @@  -- | Define a query_database tool that Claude can call programmatically queryDatabaseTool :: Tool.Tool-queryDatabaseTool = Tool.Tool-    { Tool.name = "query_database"-    , Tool.description = Just "Query a regional database for sales data. Returns sales figures for the specified region."-    , Tool.input_schema = Tool.InputSchema-        { Tool.type_ = "object"-        , Tool.properties = Just $ Aeson.object+queryDatabaseTool = Tool.functionTool+    "query_database"+    (Just "Query a regional database for sales data. Returns sales figures for the specified region.")+    $ Aeson.object+        [ "properties" Aeson..= Aeson.object             [ "region" Aeson..= Aeson.object                 [ "type" Aeson..= ("string" :: Text)                 , "description" Aeson..= ("The region to query: west, east, or central" :: Text)                 , "enum" Aeson..= (["west", "east", "central"] :: [Text])                 ]             ]-        , Tool.required = Just ["region"]-        }-    }+        , "required" Aeson..= (["region"] :: [Text])+        ]  -- | Fake database query - returns sales data for a region queryDatabase :: Text -> Text
+ examples/claude-structured-outputs-example/Main.hs view
@@ -0,0 +1,225 @@+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE NamedFieldPuns        #-}+{-# LANGUAGE OverloadedLists       #-}+{-# LANGUAGE OverloadedStrings     #-}++-- | Example demonstrating structured outputs with Claude+--+-- This example shows how to use:+-- 1. JSON outputs (output_format) to get structured JSON responses+-- 2. Strict tool use (strict: true) to validate tool parameters+--+-- Requires the structured-outputs-2025-11-13 beta header.+module Main where++import Data.Aeson (FromJSON(..), (.:), (.=))+import Data.Foldable (toList)+import Data.Text (Text)++import qualified Claude.V1 as V1+import qualified Claude.V1.Messages as Messages+import qualified Claude.V1.Tool as Tool+import qualified Data.Aeson as Aeson+import qualified Data.Aeson.KeyMap as KeyMap+import qualified Data.ByteString.Lazy as LBS+import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text.Encoding+import qualified Data.Text.IO as Text.IO+import qualified System.Environment as Environment++-- | Structured output type for contact extraction+data ContactInfo = ContactInfo+    { name :: Text+    , email :: Text+    , company :: Maybe Text+    , interested_in :: Text+    } deriving Show++instance FromJSON ContactInfo where+    parseJSON = Aeson.withObject "ContactInfo" $ \obj -> do+        n <- obj .: "name"+        e <- obj .: "email"+        c <- obj .: "company"+        i <- obj .: "interested_in"+        pure ContactInfo{ name = n, email = e, company = c, interested_in = i }++main :: IO ()+main = do+    key <- Text.pack <$> Environment.getEnv "ANTHROPIC_KEY"+    env <- V1.getClientEnv "https://api.anthropic.com"++    -- Use makeMethodsWith to pass the structured outputs beta header+    let options = V1.defaultClientOptions+            { V1.apiKey = key+            , V1.anthropicBeta = Just "structured-outputs-2025-11-13"+            }++    let V1.Methods{ V1.createMessage } = V1.makeMethodsWith env options++    -- Example 1: JSON outputs for data extraction+    Text.IO.putStrLn "=== Example 1: JSON Outputs (Data Extraction) ===\n"+    jsonOutputsExample createMessage++    Text.IO.putStrLn "\n"++    -- Example 2: Strict tool use for validated parameters+    Text.IO.putStrLn "=== Example 2: Strict Tool Use ===\n"+    strictToolUseExample createMessage++-- | Example using output_format to get structured JSON responses+jsonOutputsExample :: (Messages.CreateMessage -> IO Messages.MessageResponse) -> IO ()+jsonOutputsExample createMessage = do+    let emailText = "Hi there! I'm John Smith from Acme Corp (john.smith@acme.com). \+                    \I saw your demo at the conference and I'm very interested in \+                    \your Enterprise plan. Could we schedule a call next week?"++    -- Define the JSON schema for our expected output+    let outputSchema = Aeson.object+            [ "type" .= ("object" :: Text)+            , "properties" .= Aeson.object+                [ "name" .= Aeson.object+                    [ "type" .= ("string" :: Text)+                    , "description" .= ("Full name of the contact" :: Text)+                    ]+                , "email" .= Aeson.object+                    [ "type" .= ("string" :: Text)+                    , "description" .= ("Email address" :: Text)+                    ]+                , "company" .= Aeson.object+                    [ "type" .= ("string" :: Text)+                    , "description" .= ("Company name if mentioned" :: Text)+                    ]+                , "interested_in" .= Aeson.object+                    [ "type" .= ("string" :: Text)+                    , "description" .= ("What product/service they're interested in" :: Text)+                    ]+                ]+            , "required" .= (["name", "email", "interested_in"] :: [Text])+            , "additionalProperties" .= False+            ]++    let message = Messages.Message+            { Messages.role = Messages.User+            , Messages.content = [Messages.textContent $ "Extract contact information from this email:\n\n" <> emailText]+            , Messages.cache_control = Nothing+            }++    Text.IO.putStrLn $ "Input email: " <> emailText+    Text.IO.putStrLn "\nExtracting structured data..."++    response <- createMessage Messages._CreateMessage+        { Messages.model = "claude-sonnet-4-5-20250929"+        , Messages.messages = [message]+        , Messages.max_tokens = 1024+        , Messages.output_format = Just (Messages.jsonSchemaFormat outputSchema)+        }++    let Messages.MessageResponse{ Messages.content = responseContent, Messages.stop_reason = stopReason } = response++    Text.IO.putStrLn $ "Stop reason: " <> Text.pack (show stopReason)++    -- The response text should be valid JSON matching our schema+    case toList responseContent of+        [Messages.ContentBlock_Text{ Messages.text = jsonText }] -> do+            Text.IO.putStrLn $ "\nRaw JSON response:\n" <> jsonText++            -- Parse and display the structured data+            case Aeson.eitherDecodeStrict (Text.Encoding.encodeUtf8 jsonText) of+                Left err -> Text.IO.putStrLn $ "Parse error: " <> Text.pack err+                Right ContactInfo{ name, email, company, interested_in } -> do+                    Text.IO.putStrLn "\nParsed contact info:"+                    Text.IO.putStrLn $ "  Name: " <> name+                    Text.IO.putStrLn $ "  Email: " <> email+                    Text.IO.putStrLn $ "  Company: " <> maybe "(not specified)" id company+                    Text.IO.putStrLn $ "  Interested in: " <> interested_in+        _ -> Text.IO.putStrLn "Unexpected response format"++-- | Example using strict: true for validated tool parameters+strictToolUseExample :: (Messages.CreateMessage -> IO Messages.MessageResponse) -> IO ()+strictToolUseExample createMessage = do+    -- Define a tool with strict mode enabled+    -- This guarantees the tool input will match the schema exactly+    let flightSearchTool = Tool.strictFunctionTool+            "search_flights"+            (Just "Search for available flights between two cities")+            $ Aeson.object+                [ "type" .= ("object" :: Text)+                , "properties" .= Aeson.object+                    [ "origin" .= Aeson.object+                        [ "type" .= ("string" :: Text)+                        , "description" .= ("Origin airport code (e.g., SFO)" :: Text)+                        ]+                    , "destination" .= Aeson.object+                        [ "type" .= ("string" :: Text)+                        , "description" .= ("Destination airport code (e.g., JFK)" :: Text)+                        ]+                    , "date" .= Aeson.object+                        [ "type" .= ("string" :: Text)+                        , "format" .= ("date" :: Text)+                        , "description" .= ("Travel date in YYYY-MM-DD format" :: Text)+                        ]+                    , "passengers" .= Aeson.object+                        [ "type" .= ("integer" :: Text)+                        , "enum" .= ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10] :: [Int])+                        , "description" .= ("Number of passengers (1-10)" :: Text)+                        ]+                    ]+                , "required" .= (["origin", "destination", "date", "passengers"] :: [Text])+                , "additionalProperties" .= False+                ]++    let message = Messages.Message+            { Messages.role = Messages.User+            , Messages.content = [Messages.textContent "Find me flights from San Francisco to New York for 2 people on January 15th, 2025"]+            , Messages.cache_control = Nothing+            }++    Text.IO.putStrLn "Asking Claude to search for flights with strict tool validation..."++    response <- createMessage Messages._CreateMessage+        { Messages.model = "claude-sonnet-4-5-20250929"+        , Messages.messages = [message]+        , Messages.max_tokens = 1024+        , Messages.tools = Just [Tool.inlineTool flightSearchTool]+        , Messages.tool_choice = Just Tool.toolChoiceAny  -- Force tool use+        }++    let Messages.MessageResponse{ Messages.content = responseContent, Messages.stop_reason = stopReason } = response++    Text.IO.putStrLn $ "Stop reason: " <> Text.pack (show stopReason)++    -- With strict mode, we're guaranteed the tool input matches the schema+    mapM_ processBlock (toList responseContent)+  where+    processBlock (Messages.ContentBlock_Tool_Use{ Messages.id = toolId, Messages.name = toolName, Messages.input = toolInput }) = do+        Text.IO.putStrLn $ "\nTool called: " <> toolName+        Text.IO.putStrLn $ "Tool ID: " <> toolId+        Text.IO.putStrLn $ "Input (guaranteed to match schema):"+        Text.IO.putStrLn $ "  " <> Text.Encoding.decodeUtf8 (LBS.toStrict (Aeson.encode toolInput))++        -- In strict mode, we can safely extract fields without error handling+        case toolInput of+            Aeson.Object obj -> do+                let origin = lookupString "origin" obj+                let destination = lookupString "destination" obj+                let date = lookupString "date" obj+                let passengers = lookupInt "passengers" obj+                Text.IO.putStrLn "\nParsed parameters:"+                Text.IO.putStrLn $ "  Origin: " <> origin+                Text.IO.putStrLn $ "  Destination: " <> destination+                Text.IO.putStrLn $ "  Date: " <> date+                Text.IO.putStrLn $ "  Passengers: " <> Text.pack (show passengers)+            _ -> pure ()++    processBlock (Messages.ContentBlock_Text{ Messages.text = t }) =+        Text.IO.putStrLn $ "Text: " <> t++    processBlock _ = pure ()++    lookupString key obj = case KeyMap.lookup key obj of+        Just (Aeson.String s) -> s+        _ -> "(missing)"++    lookupInt key obj = case KeyMap.lookup key obj of+        Just (Aeson.Number n) -> round n :: Int+        _ -> 0
examples/claude-tool-example/Main.hs view
@@ -49,12 +49,11 @@  -- | Define our weather tool weatherTool :: Tool.Tool-weatherTool = Tool.Tool-    { Tool.name = "get_weather"-    , Tool.description = Just "Get the current weather for a location"-    , Tool.input_schema = Tool.InputSchema-        { Tool.type_ = "object"-        , Tool.properties = Just $ Aeson.object+weatherTool = Tool.functionTool+    "get_weather"+    (Just "Get the current weather for a location")+    $ Aeson.object+        [ "properties" .= Aeson.object             [ "location" .= Aeson.object                 [ "type" .= ("string" :: Text)                 , "description" .= ("City and state, e.g. San Francisco, CA" :: Text)@@ -65,9 +64,8 @@                 , "description" .= ("Temperature unit" :: Text)                 ]             ]-        , Tool.required = Just ["location"]-        }-    }+        , "required" .= (["location"] :: [Text])+        ]  main :: IO () main = do
examples/claude-tool-search-example/Main.hs view
@@ -26,44 +26,39 @@ -- In a real application, you might have hundreds of tools  weatherTool :: Tool.Tool-weatherTool = Tool.Tool-    { Tool.name = "get_weather"-    , Tool.description = Just "Get the current weather for a location"-    , Tool.input_schema = Tool.InputSchema-        { Tool.type_ = "object"-        , Tool.properties = Just $ Aeson.object+weatherTool = Tool.functionTool+    "get_weather"+    (Just "Get the current weather for a location")+    $ Aeson.object+        [ "properties" Aeson..= Aeson.object             [ "location" Aeson..= Aeson.object                 [ "type" Aeson..= ("string" :: Text)                 , "description" Aeson..= ("City and state, e.g. San Francisco, CA" :: Text)                 ]             ]-        , Tool.required = Just ["location"]-        }-    }+        , "required" Aeson..= (["location"] :: [Text])+        ]  stockPriceTool :: Tool.Tool-stockPriceTool = Tool.Tool-    { Tool.name = "get_stock_price"-    , Tool.description = Just "Get the current stock price for a ticker symbol"-    , Tool.input_schema = Tool.InputSchema-        { Tool.type_ = "object"-        , Tool.properties = Just $ Aeson.object+stockPriceTool = Tool.functionTool+    "get_stock_price"+    (Just "Get the current stock price for a ticker symbol")+    $ Aeson.object+        [ "properties" Aeson..= Aeson.object             [ "ticker" Aeson..= Aeson.object                 [ "type" Aeson..= ("string" :: Text)                 , "description" Aeson..= ("Stock ticker symbol, e.g. AAPL" :: Text)                 ]             ]-        , Tool.required = Just ["ticker"]-        }-    }+        , "required" Aeson..= (["ticker"] :: [Text])+        ]  currencyConvertTool :: Tool.Tool-currencyConvertTool = Tool.Tool-    { Tool.name = "convert_currency"-    , Tool.description = Just "Convert an amount from one currency to another"-    , Tool.input_schema = Tool.InputSchema-        { Tool.type_ = "object"-        , Tool.properties = Just $ Aeson.object+currencyConvertTool = Tool.functionTool+    "convert_currency"+    (Just "Convert an amount from one currency to another")+    $ Aeson.object+        [ "properties" Aeson..= Aeson.object             [ "amount" Aeson..= Aeson.object                 [ "type" Aeson..= ("number" :: Text)                 , "description" Aeson..= ("Amount to convert" :: Text)@@ -77,41 +72,36 @@                 , "description" Aeson..= ("Target currency code, e.g. EUR" :: Text)                 ]             ]-        , Tool.required = Just ["amount", "from_currency", "to_currency"]-        }-    }+        , "required" Aeson..= (["amount", "from_currency", "to_currency"] :: [Text])+        ]  calculatorTool :: Tool.Tool-calculatorTool = Tool.Tool-    { Tool.name = "calculator"-    , Tool.description = Just "Perform basic arithmetic calculations"-    , Tool.input_schema = Tool.InputSchema-        { Tool.type_ = "object"-        , Tool.properties = Just $ Aeson.object+calculatorTool = Tool.functionTool+    "calculator"+    (Just "Perform basic arithmetic calculations")+    $ Aeson.object+        [ "properties" Aeson..= Aeson.object             [ "expression" Aeson..= Aeson.object                 [ "type" Aeson..= ("string" :: Text)                 , "description" Aeson..= ("Math expression to evaluate, e.g. 2+2" :: Text)                 ]             ]-        , Tool.required = Just ["expression"]-        }-    }+        , "required" Aeson..= (["expression"] :: [Text])+        ]  searchWebTool :: Tool.Tool-searchWebTool = Tool.Tool-    { Tool.name = "search_web"-    , Tool.description = Just "Search the web for information"-    , Tool.input_schema = Tool.InputSchema-        { Tool.type_ = "object"-        , Tool.properties = Just $ Aeson.object+searchWebTool = Tool.functionTool+    "search_web"+    (Just "Search the web for information")+    $ Aeson.object+        [ "properties" Aeson..= Aeson.object             [ "query" Aeson..= Aeson.object                 [ "type" Aeson..= ("string" :: Text)                 , "description" Aeson..= ("Search query" :: Text)                 ]             ]-        , Tool.required = Just ["query"]-        }-    }+        , "required" Aeson..= (["query"] :: [Text])+        ]  main :: IO () main = do
src/Claude/V1/Messages.hs view
@@ -7,6 +7,9 @@     , _CreateMessage     , MessageResponse(..)     , MessageStreamEvent(..)+      -- * Structured outputs+    , OutputFormat(..)+    , jsonSchemaFormat       -- * Content types     , Content(..)     , ContentBlock(..)@@ -38,6 +41,7 @@     , ToolSearchTool(..)     , ToolSearchToolType(..)     , functionTool+    , strictFunctionTool     , inlineTool     , deferredTool     , toolSearchRegex@@ -84,15 +88,16 @@     , deferredTool     , functionTool     , inlineTool+    , strictFunctionTool     , toolChoiceAny     , toolChoiceAuto     , toolChoiceTool     , toolSearchBm25     , toolSearchRegex     )-import           Data.Time (UTCTime) import qualified Data.Aeson as Aeson import qualified Data.Aeson.KeyMap as KeyMap+import           Data.Time (UTCTime)  -- | Role of a message participant data Role = User | Assistant@@ -487,6 +492,8 @@     | Max_Tokens     | Stop_Sequence     | Tool_Use+    | Refusal+    -- ^ Model refused the request for safety reasons (structured outputs only)     deriving stock (Eq, Generic, Show)  stopReasonOptions :: Options@@ -549,6 +556,43 @@ instance ToJSON MessageResponse where     toJSON = genericToJSON aesonOptions +-- | Output format specification for structured outputs+--+-- Use with the @structured-outputs-2025-11-13@ beta header.+--+-- Example:+--+-- @+-- let outputFormat = jsonSchemaFormat $ Aeson.object+--         [ "type" .= ("object" :: Text)+--         , "properties" .= Aeson.object+--             [ "name" .= Aeson.object ["type" .= ("string" :: Text)]+--             , "age" .= Aeson.object ["type" .= ("integer" :: Text)]+--             ]+--         , "required" .= (["name", "age"] :: [Text])+--         , "additionalProperties" .= False+--         ]+-- @+data OutputFormat = OutputFormat+    { type_ :: Text    -- ^ Currently only "json_schema" is supported+    , schema :: Value  -- ^ JSON Schema for the output+    } deriving stock (Eq, Generic, Show)++instance FromJSON OutputFormat where+    parseJSON = genericParseJSON aesonOptions++instance ToJSON OutputFormat where+    toJSON = genericToJSON aesonOptions++-- | Create a JSON schema output format+--+-- This is the primary way to use structured outputs.+jsonSchemaFormat :: Value -> OutputFormat+jsonSchemaFormat s = OutputFormat+    { type_ = "json_schema"+    , schema = s+    }+ -- | Request body for @\/v1\/messages@ -- -- For programmatic tool calling (PTC), use the @container@ field to reuse@@ -567,6 +611,8 @@     , tools :: Maybe (Vector ToolDefinition)     , tool_choice :: Maybe ToolChoice     , container :: Maybe Text+    , output_format :: Maybe OutputFormat+    -- ^ Structured output format (requires @structured-outputs-2025-11-13@ beta header)     } deriving stock (Generic, Show)  instance FromJSON CreateMessage where@@ -591,6 +637,7 @@     , tools = Nothing     , tool_choice = Nothing     , container = Nothing+    , output_format = Nothing     }  -- | Text delta in streaming
src/Claude/V1/Tool.hs view
@@ -33,6 +33,7 @@     , ToolSearchToolType(..)       -- * Tool constructors     , functionTool+    , strictFunctionTool     , simpleInputSchema       -- * ToolDefinition constructors     , inlineTool@@ -64,17 +65,31 @@ -- | Tool input schema (JSON Schema) -- -- The schema follows JSON Schema format. At minimum, specify @type_@ as \"object\".+-- For strict mode (structured outputs), set @additionalProperties = Just False@. data InputSchema = InputSchema     { type_ :: Text     , properties :: Maybe Value     , required :: Maybe (Vector Text)+    , additionalProperties :: Maybe Bool+    -- ^ For strict mode, must be @Just False@     } deriving stock (Eq, Generic, Show)  instance FromJSON InputSchema where-    parseJSON = genericParseJSON aesonOptions+    parseJSON = Aeson.withObject "InputSchema" $ \o -> do+        type_ <- o Aeson..: "type"+        properties <- o Aeson..:? "properties"+        required <- o Aeson..:? "required"+        additionalProperties <- o Aeson..:? "additionalProperties"+        pure InputSchema{ type_, properties, required, additionalProperties }  instance ToJSON InputSchema where-    toJSON = genericToJSON aesonOptions+    toJSON InputSchema{ type_, properties, required, additionalProperties } =+        Aeson.object $ filter ((/= Aeson.Null) . snd)+            [ "type" Aeson..= type_+            , "properties" Aeson..= properties+            , "required" Aeson..= required+            , "additionalProperties" Aeson..= additionalProperties+            ]  -- | Create a simple input schema with properties and required fields simpleInputSchema@@ -85,16 +100,22 @@     { type_ = "object"     , properties = Just props     , required = Just reqs+    , additionalProperties = Nothing     }  -- | A tool that can be used by Claude -- -- Tools allow Claude to call external functions. When Claude decides to use a tool, -- it will return a @tool_use@ content block with the tool name and input arguments.+--+-- Set @strict = Just True@ to enable strict schema validation (structured outputs).+-- This requires the @structured-outputs-2025-11-13@ beta header. data Tool = Tool     { name :: Text     , description :: Maybe Text     , input_schema :: InputSchema+    , strict :: Maybe Bool+    -- ^ Enable strict schema validation for tool inputs (structured outputs beta)     } deriving stock (Eq, Generic, Show)  instance FromJSON Tool where@@ -106,6 +127,7 @@ -- | Create a function tool with a name, description, and JSON schema for parameters -- -- This is the primary way to define tools for Claude.+-- To enable strict schema validation, use 'strictFunctionTool' instead. functionTool     :: Text           -- ^ Tool name (must match [a-zA-Z0-9_-]+)     -> Maybe Text     -- ^ Description of what the tool does@@ -126,13 +148,33 @@                 Just (Aeson.Array arr) -> Just (Vector.mapMaybe getString arr)                 _ -> Nothing             _ -> Nothing+        , additionalProperties = Nothing         }+    , strict = Nothing     }   where     getString (Aeson.String s) = Just s     getString _ = Nothing     lookupKey k obj = KeyMap.lookup (Key.fromText k) obj +-- | Create a function tool with strict schema validation enabled+--+-- This is like 'functionTool' but enables strict mode for structured outputs.+-- Automatically sets @additionalProperties = False@ as required by the API.+-- Requires the @structured-outputs-2025-11-13@ beta header.+strictFunctionTool+    :: Text           -- ^ Tool name (must match [a-zA-Z0-9_-]+)+    -> Maybe Text     -- ^ Description of what the tool does+    -> Value          -- ^ JSON Schema for the input parameters+    -> Tool+strictFunctionTool toolName toolDescription schema =+    let tool = functionTool toolName toolDescription schema+        inputSchema = input_schema tool+    in tool+        { strict = Just True+        , input_schema = inputSchema{ additionalProperties = Just False }+        }+ -- | Controls which tool the model should use data ToolChoice     = ToolChoice_Auto@@ -258,7 +300,7 @@         isCodeExecutionType t = t == "code_execution_20250825"  instance ToJSON ToolDefinition where-    toJSON (ToolDef_Function Tool{ name, description, input_schema } defer_loading allowed_callers) =+    toJSON (ToolDef_Function Tool{ name, description, input_schema, strict } defer_loading allowed_callers) =         Aeson.Object (baseMap <> optionalFields)       where         baseObj = Aeson.object $@@ -270,7 +312,8 @@             _ -> KeyMap.empty         optionalFields = KeyMap.fromList $             maybe [] (\dl -> [("defer_loading", Aeson.toJSON dl)]) defer_loading <>-            maybe [] (\ac -> [("allowed_callers", Aeson.toJSON ac)]) allowed_callers+            maybe [] (\ac -> [("allowed_callers", Aeson.toJSON ac)]) allowed_callers <>+            maybe [] (\s -> [("strict", Aeson.toJSON s)]) strict     toJSON (ToolDef_SearchTool searchTool) = Aeson.toJSON searchTool     toJSON (ToolDef_CodeExecutionTool name type_) = Aeson.object         [ "name" Aeson..= name
tasty/Main.hs view
@@ -21,6 +21,7 @@ import qualified Data.IORef as IORef import           Data.Maybe (mapMaybe) import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text import qualified Data.Vector as Vector import qualified Network.HTTP.Client as HTTP.Client import qualified Network.HTTP.Client.TLS as TLS@@ -166,20 +167,18 @@      let toolUseTest =             HUnit.testCase "Create message - tool use" do-                let calculatorTool = Tool.Tool-                        { Tool.name = "calculator"-                        , Tool.description = Just "Perform basic arithmetic"-                        , Tool.input_schema = Tool.InputSchema-                            { Tool.type_ = "object"-                            , Tool.properties = Just $ Aeson.object+                let calculatorTool = Tool.functionTool+                        "calculator"+                        (Just "Perform basic arithmetic")+                        $ Aeson.object+                            [ "properties" Aeson..= Aeson.object                                 [ "expression" Aeson..= Aeson.object                                     [ "type" Aeson..= ("string" :: Text.Text)                                     , "description" Aeson..= ("Math expression like 2+2" :: Text.Text)                                     ]                                 ]-                            , Tool.required = Just ["expression"]-                            }-                        }+                            , "required" Aeson..= (["expression"] :: [Text.Text])+                            ]                  Messages.MessageResponse{ stop_reason, content } <-                     createMessage@@ -233,6 +232,121 @@                 HUnit.assertBool "Should have positive token count"                     (input_tokens > 0) +    -- Structured outputs tests require beta header+    let structuredOutputsOptions = V1.defaultClientOptions+            { V1.apiKey = Text.pack key+            , V1.anthropicBeta = Just "structured-outputs-2025-11-13"+            }+    let V1.Methods{ V1.createMessage = createMessageStructured } =+            V1.makeMethodsWith clientEnv structuredOutputsOptions++    let jsonOutputsTest =+            HUnit.testCase "Create message - JSON outputs" do+                -- Define output schema+                let outputSchema = 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+                        ]++                Messages.MessageResponse{ stop_reason, content } <-+                    createMessageStructured+                        Messages._CreateMessage+                            { Messages.model = model+                            , Messages.messages =+                                [ Messages.Message+                                    { Messages.role = Messages.User+                                    , Messages.content =+                                        [ Messages.textContent "Extract: John Smith is 30 years old."+                                        ]+                                    , Messages.cache_control = Nothing+                                    }+                                ]+                            , Messages.max_tokens = 200+                            , Messages.output_format = Just (Messages.jsonSchemaFormat outputSchema)+                            }++                -- Should complete normally+                HUnit.assertEqual "Should complete with end_turn"+                    (Just Messages.End_Turn)+                    stop_reason++                -- Should have text content that is valid JSON+                case toList content of+                    [Messages.ContentBlock_Text{ Messages.text = jsonText }] -> do+                        case Aeson.eitherDecodeStrict (Text.encodeUtf8 jsonText) of+                            Left err -> HUnit.assertFailure $ "Invalid JSON: " <> err+                            Right (obj :: Aeson.Value) -> do+                                -- Check it has the expected structure+                                case obj of+                                    Aeson.Object _ -> pure ()+                                    _ -> HUnit.assertFailure "Expected JSON object"+                    _ -> HUnit.assertFailure "Expected single text content block"++    let strictToolUseTest =+            HUnit.testCase "Create message - strict tool use" do+                -- Define a tool with strict mode+                let strictTool = Tool.strictFunctionTool+                        "get_user"+                        (Just "Get user by ID")+                        $ Aeson.object+                            [ "type" Aeson..= ("object" :: Text.Text)+                            , "properties" Aeson..= Aeson.object+                                [ "user_id" Aeson..= Aeson.object+                                    [ "type" Aeson..= ("integer" :: Text.Text)+                                    , "description" Aeson..= ("The user ID" :: Text.Text)+                                    ]+                                ]+                            , "required" Aeson..= (["user_id"] :: [Text.Text])+                            ]++                Messages.MessageResponse{ stop_reason, content } <-+                    createMessageStructured+                        Messages._CreateMessage+                            { Messages.model = model+                            , Messages.messages =+                                [ Messages.Message+                                    { Messages.role = Messages.User+                                    , Messages.content =+                                        [ Messages.textContent "Get user 42"+                                        ]+                                    , Messages.cache_control = Nothing+                                    }+                                ]+                            , Messages.max_tokens = 200+                            , Messages.tools = Just [Tool.inlineTool strictTool]+                            , Messages.tool_choice = Just Tool.ToolChoice_Any+                            }++                -- Should stop for tool use+                HUnit.assertEqual "Should stop for tool use"+                    (Just Messages.Tool_Use)+                    stop_reason++                -- Should have a tool_use block with integer user_id+                let toolUseBlocks =+                        [ (toolId, toolInput)+                        | Messages.ContentBlock_Tool_Use{ Messages.id = toolId, Messages.input = toolInput }+                            <- toList content+                        ]+                case toolUseBlocks of+                    [(_, toolInput)] -> do+                        -- Verify user_id is an integer (strict mode guarantees this)+                        case Aeson.Types.parseMaybe (Aeson.withObject "input" (\o -> o Aeson..: "user_id")) toolInput of+                            Just (userId :: Int) ->+                                HUnit.assertEqual "user_id should be 42" 42 userId+                            Nothing ->+                                HUnit.assertFailure "user_id should be present and be an integer"+                    _ -> HUnit.assertFailure "Expected exactly one tool_use block"+     -- Tool search test requires beta header     let betaOptions = V1.defaultClientOptions             { V1.apiKey = Text.pack key@@ -244,47 +358,41 @@     let toolSearchTest =             HUnit.testCase "Create message - tool search" do                 -- Define several tools to search through-                let weatherTool = Tool.Tool-                        { Tool.name = "get_weather"-                        , Tool.description = Just "Get the current weather for a location"-                        , Tool.input_schema = Tool.InputSchema-                            { Tool.type_ = "object"-                            , Tool.properties = Just $ Aeson.object+                let weatherTool = Tool.functionTool+                        "get_weather"+                        (Just "Get the current weather for a location")+                        $ Aeson.object+                            [ "properties" Aeson..= Aeson.object                                 [ "location" Aeson..= Aeson.object                                     [ "type" Aeson..= ("string" :: Text.Text)                                     ]                                 ]-                            , Tool.required = Just ["location"]-                            }-                        }+                            , "required" Aeson..= (["location"] :: [Text.Text])+                            ] -                let stockTool = Tool.Tool-                        { Tool.name = "get_stock_price"-                        , Tool.description = Just "Get the stock price for a ticker"-                        , Tool.input_schema = Tool.InputSchema-                            { Tool.type_ = "object"-                            , Tool.properties = Just $ Aeson.object+                let stockTool = Tool.functionTool+                        "get_stock_price"+                        (Just "Get the stock price for a ticker")+                        $ Aeson.object+                            [ "properties" Aeson..= Aeson.object                                 [ "ticker" Aeson..= Aeson.object                                     [ "type" Aeson..= ("string" :: Text.Text)                                     ]                                 ]-                            , Tool.required = Just ["ticker"]-                            }-                        }+                            , "required" Aeson..= (["ticker"] :: [Text.Text])+                            ] -                let calculatorTool' = Tool.Tool-                        { Tool.name = "calculator"-                        , Tool.description = Just "Perform arithmetic calculations"-                        , Tool.input_schema = Tool.InputSchema-                            { Tool.type_ = "object"-                            , Tool.properties = Just $ Aeson.object+                let calculatorTool' = Tool.functionTool+                        "calculator"+                        (Just "Perform arithmetic calculations")+                        $ Aeson.object+                            [ "properties" Aeson..= Aeson.object                                 [ "expression" Aeson..= Aeson.object                                     [ "type" Aeson..= ("string" :: Text.Text)                                     ]                                 ]-                            , Tool.required = Just ["expression"]-                            }-                        }+                            , "required" Aeson..= (["expression"] :: [Text.Text])+                            ]                  -- Use tool search with deferred tools                 let tools =@@ -328,19 +436,17 @@     let programmaticToolCallingTest =             HUnit.testCase "Create message - programmatic tool calling" do                 -- Define a simple tool for PTC-                let queryTool = Tool.Tool-                        { Tool.name = "get_data"-                        , Tool.description = Just "Get data for a key"-                        , Tool.input_schema = Tool.InputSchema-                            { Tool.type_ = "object"-                            , Tool.properties = Just $ Aeson.object+                let queryTool = Tool.functionTool+                        "get_data"+                        (Just "Get data for a key")+                        $ Aeson.object+                            [ "properties" Aeson..= Aeson.object                                 [ "key" Aeson..= Aeson.object                                     [ "type" Aeson..= ("string" :: Text.Text)                                     ]                                 ]-                            , Tool.required = Just ["key"]-                            }-                        }+                            , "required" Aeson..= (["key"] :: [Text.Text])+                            ]                  -- Tools: code execution + query tool with allowed_callers                 let tools =@@ -457,6 +563,8 @@             , messagesConversationTest             , toolUseTest             , tokenCountingTest+            , jsonOutputsTest+            , strictToolUseTest             , toolSearchTest             , programmaticToolCallingTest             ]