packages feed

claude 1.3.1 → 1.4.0

raw patch · 7 files changed

+401/−31 lines, 7 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Claude.V1.Messages: instance Data.Aeson.Types.FromJSON.FromJSON Claude.V1.Messages.CacheControl
- Claude.V1.Messages: instance Data.Aeson.Types.ToJSON.ToJSON Claude.V1.Messages.CacheControl
- Claude.V1.Messages: instance GHC.Generics.Generic Claude.V1.Messages.CacheControl
- Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.CacheControl
+ Claude.V1.Messages: CacheTTLDuration :: Text -> CacheTTL
+ Claude.V1.Messages: CacheTTLSeconds :: Natural -> CacheTTL
+ Claude.V1.Messages: SystemBlock :: Text -> Text -> Maybe CacheControl -> SystemBlock
+ Claude.V1.Messages: SystemPromptBlocks :: Vector SystemBlock -> SystemPrompt
+ Claude.V1.Messages: SystemPromptText :: Text -> SystemPrompt
+ Claude.V1.Messages: [tool_search_tool] :: ToolDefinition -> ToolSearchTool
+ Claude.V1.Messages: [ttl] :: CacheControl -> Maybe CacheTTL
+ Claude.V1.Messages: data CacheTTL
+ Claude.V1.Messages: data SystemBlock
+ Claude.V1.Messages: data SystemPrompt
+ Claude.V1.Messages: ephemeralCacheWithTTL :: CacheTTL -> CacheControl
+ Claude.V1.Messages: ephemeralCacheWithTTLDuration :: Text -> CacheControl
+ Claude.V1.Messages: ephemeralCacheWithTTLSeconds :: Natural -> CacheControl
+ Claude.V1.Messages: instance Data.Aeson.Types.FromJSON.FromJSON Claude.V1.Messages.SystemBlock
+ Claude.V1.Messages: instance Data.Aeson.Types.FromJSON.FromJSON Claude.V1.Messages.SystemPrompt
+ Claude.V1.Messages: instance Data.Aeson.Types.ToJSON.ToJSON Claude.V1.Messages.SystemBlock
+ Claude.V1.Messages: instance Data.Aeson.Types.ToJSON.ToJSON Claude.V1.Messages.SystemPrompt
+ Claude.V1.Messages: instance Data.String.IsString Claude.V1.Messages.SystemPrompt
+ Claude.V1.Messages: instance GHC.Classes.Eq Claude.V1.Messages.SystemBlock
+ Claude.V1.Messages: instance GHC.Classes.Eq Claude.V1.Messages.SystemPrompt
+ Claude.V1.Messages: instance GHC.Generics.Generic Claude.V1.Messages.SystemBlock
+ Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.SystemBlock
+ Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.SystemPrompt
+ Claude.V1.Messages: systemBlocks :: Vector SystemBlock -> SystemPrompt
+ Claude.V1.Messages: systemText :: Text -> SystemPrompt
+ Claude.V1.Messages: systemTextBlock :: Text -> SystemBlock
+ Claude.V1.Messages: systemTextBlockCached :: CacheControl -> Text -> SystemBlock
+ Claude.V1.Messages: withToolCacheControl :: CacheControl -> ToolDefinition -> ToolDefinition
+ Claude.V1.Tool: CacheControl :: Text -> Maybe CacheTTL -> CacheControl
+ Claude.V1.Tool: CacheTTLDuration :: Text -> CacheTTL
+ Claude.V1.Tool: CacheTTLSeconds :: Natural -> CacheTTL
+ Claude.V1.Tool: [cache_control] :: ToolDefinition -> Maybe CacheControl
+ Claude.V1.Tool: [tool_search_tool] :: ToolDefinition -> ToolSearchTool
+ Claude.V1.Tool: [ttl] :: CacheControl -> Maybe CacheTTL
+ Claude.V1.Tool: data CacheControl
+ Claude.V1.Tool: data CacheTTL
+ Claude.V1.Tool: ephemeralCache :: CacheControl
+ Claude.V1.Tool: ephemeralCacheWithTTL :: CacheTTL -> CacheControl
+ Claude.V1.Tool: ephemeralCacheWithTTLDuration :: Text -> CacheControl
+ Claude.V1.Tool: ephemeralCacheWithTTLSeconds :: Natural -> CacheControl
+ Claude.V1.Tool: withToolCacheControl :: CacheControl -> ToolDefinition -> ToolDefinition
- Claude.V1.Messages: CacheControl :: Text -> CacheControl
+ Claude.V1.Messages: CacheControl :: Text -> Maybe CacheTTL -> CacheControl
- Claude.V1.Messages: CountTokensRequest :: Text -> Vector Message -> Maybe Text -> Maybe (Vector ToolDefinition) -> Maybe ToolChoice -> CountTokensRequest
+ Claude.V1.Messages: CountTokensRequest :: Text -> Vector Message -> Maybe SystemPrompt -> Maybe (Vector ToolDefinition) -> Maybe ToolChoice -> CountTokensRequest
- 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 ContextManagementConfig -> Maybe Text -> Maybe Speed -> Maybe OutputConfig -> Maybe Thinking -> CreateMessage
+ Claude.V1.Messages: CreateMessage :: Text -> Vector Message -> Natural -> Maybe SystemPrompt -> Maybe CacheControl -> Maybe Double -> Maybe Double -> Maybe Natural -> Maybe (Vector Text) -> Maybe Bool -> Maybe (Map Text Text) -> Maybe (Vector ToolDefinition) -> Maybe ToolChoice -> Maybe Text -> Maybe ContextManagementConfig -> Maybe Text -> Maybe Speed -> Maybe OutputConfig -> Maybe Thinking -> CreateMessage
- Claude.V1.Messages: ToolDef_CodeExecutionTool :: Text -> Text -> ToolDefinition
+ Claude.V1.Messages: ToolDef_CodeExecutionTool :: Text -> Text -> Maybe CacheControl -> ToolDefinition
- Claude.V1.Messages: ToolDef_Function :: Tool -> Maybe Bool -> Maybe (Vector Text) -> ToolDefinition
+ Claude.V1.Messages: ToolDef_Function :: Tool -> Maybe Bool -> Maybe (Vector Text) -> Maybe CacheControl -> ToolDefinition
- Claude.V1.Messages: ToolDef_SearchTool :: ToolSearchTool -> ToolDefinition
+ Claude.V1.Messages: ToolDef_SearchTool :: ToolSearchTool -> Maybe CacheControl -> ToolDefinition
- Claude.V1.Messages: [cache_control] :: Message -> Maybe CacheControl
+ Claude.V1.Messages: [cache_control] :: SystemBlock -> Maybe CacheControl
- Claude.V1.Messages: [system] :: CountTokensRequest -> Maybe Text
+ Claude.V1.Messages: [system] :: CountTokensRequest -> Maybe SystemPrompt
- Claude.V1.Messages: [text] :: TextDelta -> Text
+ Claude.V1.Messages: [text] :: SystemBlock -> Text
- Claude.V1.Messages: [type_] :: CacheControl -> Text
+ Claude.V1.Messages: [type_] :: SystemBlock -> Text
- Claude.V1.Tool: ToolDef_CodeExecutionTool :: Text -> Text -> ToolDefinition
+ Claude.V1.Tool: ToolDef_CodeExecutionTool :: Text -> Text -> Maybe CacheControl -> ToolDefinition
- Claude.V1.Tool: ToolDef_Function :: Tool -> Maybe Bool -> Maybe (Vector Text) -> ToolDefinition
+ Claude.V1.Tool: ToolDef_Function :: Tool -> Maybe Bool -> Maybe (Vector Text) -> Maybe CacheControl -> ToolDefinition
- Claude.V1.Tool: ToolDef_SearchTool :: ToolSearchTool -> ToolDefinition
+ Claude.V1.Tool: ToolDef_SearchTool :: ToolSearchTool -> Maybe CacheControl -> ToolDefinition
- Claude.V1.Tool: [type_] :: ToolSearchTool -> ToolSearchToolType
+ Claude.V1.Tool: [type_] :: CacheControl -> Text

Files

CHANGELOG.md view
@@ -1,3 +1,22 @@+1.4.0:++- Expand prompt caching support:+  - Add top-level `cache_control` on `CreateMessage` (automatic caching).+  - Add `ttl` support on `CacheControl` via `CacheTTL`.+  - Add cache-control helpers:+    - `ephemeralCacheWithTTL`+    - `ephemeralCacheWithTTLSeconds`+    - `ephemeralCacheWithTTLDuration`+- Add structured system prompt support:+  - `SystemPrompt` (string or blocks)+  - `SystemBlock` with per-block `cache_control`+  - `systemText`, `systemBlocks`, `systemTextBlock`, `systemTextBlockCached`+  - `CreateMessage.system` and `CountTokensRequest.system` now use `Maybe SystemPrompt`+  - breaking: `system = Just someTextVar` (where `someTextVar :: Text`) now needs `system = Just (systemText someTextVar)`+- Add tool-level cache control support:+  - `ToolDefinition` variants now accept optional `cache_control`+  - new helper `withToolCacheControl`+ 1.3.1:  - Remove `advanced-tool-use-2025-11-20` beta header requirement — tool search,
README.md view
@@ -34,6 +34,28 @@     traverse_ display content ``` +## Prompt caching++The bindings support prompt caching at multiple levels:++- top-level request (`CreateMessage.cache_control`)+- system blocks (`SystemBlock.cache_control`)+- message/content blocks (`Message.cache_control`, text/image content block cache control)+- tool definitions (`withToolCacheControl`)++```haskell+MessageResponse{ usage } <- createMessage _CreateMessage+    { model = "claude-sonnet-4-5-20250929"+    , max_tokens = 1024+    , cache_control = Just (ephemeralCacheWithTTLDuration "1h")+    , system = Just $ systemBlocks+        [ systemTextBlockCached ephemeralCache "You are a coding assistant."+        ]+    , messages =+        [ Message{ role = User, content = [ textContent "Summarize this file." ] } ]+    }+```+ ## Setup  ### Using Nix with Flakes (Recommended)
claude.cabal view
@@ -1,6 +1,6 @@ cabal-version:      2.4 name:               claude-version:            1.3.1+version:            1.4.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@@ -47,6 +47,7 @@                         Claude.V1.Messages.Batches                         Claude.V1.Tool     other-modules:      Claude.Prelude+                        Claude.V1.CacheControl     default-extensions: DataKinds                       , DeriveAnyClass                       , DeriveGeneric
+ src/Claude/V1/CacheControl.hs view
@@ -0,0 +1,62 @@+module Claude.V1.CacheControl+    ( CacheControl(..)+    , CacheTTL(..)+    , ephemeralCache+    , ephemeralCacheWithTTL+    , ephemeralCacheWithTTLSeconds+    , ephemeralCacheWithTTLDuration+    ) where++import Claude.Prelude+import qualified Data.Aeson as Aeson++-- | Cache TTL configuration for prompt caching.+--+-- Supports both numeric seconds and duration strings for forward compatibility.+data CacheTTL+    = CacheTTLSeconds Natural+    | CacheTTLDuration Text+    deriving stock (Eq, Show)++instance FromJSON CacheTTL where+    parseJSON n@(Aeson.Number _) = CacheTTLSeconds <$> Aeson.parseJSON n+    parseJSON (Aeson.String t) = pure (CacheTTLDuration t)+    parseJSON _ = fail "CacheTTL must be a number (seconds) or string duration"++instance ToJSON CacheTTL where+    toJSON (CacheTTLSeconds n) = Aeson.toJSON n+    toJSON (CacheTTLDuration t) = Aeson.toJSON t++-- | Cache control for prompt caching.+data CacheControl = CacheControl+    { type_ :: Text+    , ttl :: Maybe CacheTTL+    } deriving stock (Eq, Generic, Show)++instance FromJSON CacheControl where+    parseJSON = genericParseJSON aesonOptions++instance ToJSON CacheControl where+    toJSON = genericToJSON aesonOptions++-- | Convenience constructor for ephemeral cache control.+ephemeralCache :: CacheControl+ephemeralCache = CacheControl+    { type_ = "ephemeral"+    , ttl = Nothing+    }++-- | Convenience constructor for ephemeral cache control with TTL.+ephemeralCacheWithTTL :: CacheTTL -> CacheControl+ephemeralCacheWithTTL cacheTTL = CacheControl+    { type_ = "ephemeral"+    , ttl = Just cacheTTL+    }++-- | Convenience constructor for ephemeral cache control with TTL in seconds.+ephemeralCacheWithTTLSeconds :: Natural -> CacheControl+ephemeralCacheWithTTLSeconds = ephemeralCacheWithTTL . CacheTTLSeconds++-- | Convenience constructor for ephemeral cache control with TTL duration text.+ephemeralCacheWithTTLDuration :: Text -> CacheControl+ephemeralCacheWithTTLDuration = ephemeralCacheWithTTL . CacheTTLDuration
src/Claude/V1/Messages.hs view
@@ -61,6 +61,7 @@     , codeExecutionTool     , allowedCallersCodeExecution     , allowCallers+    , withToolCacheControl     , toolChoiceAuto     , toolChoiceAny     , toolChoiceTool@@ -76,7 +77,18 @@     , TokenCount(..)       -- * Prompt caching     , CacheControl(..)+    , CacheTTL(..)     , ephemeralCache+    , ephemeralCacheWithTTL+    , ephemeralCacheWithTTLSeconds+    , ephemeralCacheWithTTLDuration+      -- * System prompt helpers+    , SystemPrompt(..)+    , SystemBlock(..)+    , systemText+    , systemBlocks+    , systemTextBlock+    , systemTextBlockCached       -- * Convenience constructors     , textContent     , imageContent@@ -87,6 +99,14 @@     ) where  import           Claude.Prelude+import           Claude.V1.CacheControl+    ( CacheControl(..)+    , CacheTTL(..)+    , ephemeralCache+    , ephemeralCacheWithTTL+    , ephemeralCacheWithTTLDuration+    , ephemeralCacheWithTTLSeconds+    ) import           Claude.V1.Tool     ( InputSchema(..)     , Tool(..)@@ -104,6 +124,7 @@     , toolChoiceAny     , toolChoiceAuto     , toolChoiceTool+    , withToolCacheControl     , toolSearchBm25     , toolSearchRegex     )@@ -134,21 +155,64 @@ instance ToJSON ImageSource where     toJSON = genericToJSON aesonOptions --- | Cache control for prompt caching-data CacheControl = CacheControl-    { type_ :: Text  -- ^ Currently only "ephemeral" is supported-    } deriving stock (Generic, Show)+-- | A system prompt block.+--+-- Use @type_ = \"text\"@ for text system blocks.+data SystemBlock = SystemBlock+    { type_ :: Text+    , text :: Text+    , cache_control :: Maybe CacheControl+    } deriving stock (Eq, Generic, Show) -instance FromJSON CacheControl where+instance FromJSON SystemBlock where     parseJSON = genericParseJSON aesonOptions -instance ToJSON CacheControl where+instance ToJSON SystemBlock where     toJSON = genericToJSON aesonOptions --- | Convenience constructor for ephemeral cache control-ephemeralCache :: CacheControl-ephemeralCache = CacheControl{ type_ = "ephemeral" }+-- | System prompt, either a plain string or an array of system blocks.+data SystemPrompt+    = SystemPromptText Text+    | SystemPromptBlocks (Vector SystemBlock)+    deriving stock (Eq, Show) +instance IsString SystemPrompt where+    fromString = SystemPromptText . fromString++instance FromJSON SystemPrompt where+    parseJSON (Aeson.String t) = pure (SystemPromptText t)+    parseJSON (Aeson.Array arr) =+        SystemPromptBlocks <$> traverse Aeson.parseJSON arr+    parseJSON _ = fail "SystemPrompt must be a string or array of system blocks"++instance ToJSON SystemPrompt where+    toJSON (SystemPromptText t) = Aeson.String t+    toJSON (SystemPromptBlocks blocks) = Aeson.toJSON blocks++-- | Create a plain-text system prompt.+systemText :: Text -> SystemPrompt+systemText = SystemPromptText++-- | Create a block-based system prompt.+systemBlocks :: Vector SystemBlock -> SystemPrompt+systemBlocks = SystemPromptBlocks++-- | Create a text system block without cache control.+systemTextBlock :: Text -> SystemBlock+systemTextBlock t = SystemBlock+    { type_ = "text"+    , text = t+    , cache_control = Nothing+    }++-- | Create a text system block with cache control.+systemTextBlockCached :: CacheControl -> Text -> SystemBlock+systemTextBlockCached cc t = SystemBlock+    { type_ = "text"+    , text = t+    , cache_control = Just cc+    }+ -- | Text content block data TextContent = TextContent     { text :: Text@@ -836,7 +900,10 @@     { model :: Text     , messages :: Vector Message     , max_tokens :: Natural-    , system :: Maybe Text+    , system :: Maybe SystemPrompt+    -- ^ System prompt, either plain text or structured system blocks.+    , cache_control :: Maybe CacheControl+    -- ^ Top-level automatic prompt caching configuration.     , temperature :: Maybe Double     , top_p :: Maybe Double     , top_k :: Maybe Natural@@ -873,6 +940,7 @@     , messages = mempty     , max_tokens = 1024     , system = Nothing+    , cache_control = Nothing     , temperature = Nothing     , top_p = Nothing     , top_k = Nothing@@ -1014,7 +1082,7 @@ data CountTokensRequest = CountTokensRequest     { model :: Text     , messages :: Vector Message-    , system :: Maybe Text+    , system :: Maybe SystemPrompt     , tools :: Maybe (Vector ToolDefinition)     , tool_choice :: Maybe ToolChoice     } deriving stock (Generic, Show)
src/Claude/V1/Tool.hs view
@@ -31,6 +31,13 @@     , ToolDefinition(..)     , ToolSearchTool(..)     , ToolSearchToolType(..)+      -- * Prompt caching+    , CacheControl(..)+    , CacheTTL(..)+    , ephemeralCache+    , ephemeralCacheWithTTL+    , ephemeralCacheWithTTLSeconds+    , ephemeralCacheWithTTLDuration       -- * Tool constructors     , functionTool     , strictFunctionTool@@ -44,6 +51,7 @@     , codeExecutionTool     , allowedCallersCodeExecution     , allowCallers+    , withToolCacheControl       -- * ToolChoice constructors     , toolChoiceAuto     , toolChoiceAny@@ -56,6 +64,14 @@     ) where  import Claude.Prelude+import Claude.V1.CacheControl+    ( CacheControl(..)+    , CacheTTL(..)+    , ephemeralCache+    , ephemeralCacheWithTTL+    , ephemeralCacheWithTTLDuration+    , ephemeralCacheWithTTLSeconds+    )  import qualified Data.Aeson as Aeson import qualified Data.Aeson.Key as Key@@ -265,11 +281,16 @@         { tool :: Tool         , defer_loading :: Maybe Bool         , allowed_callers :: Maybe (Vector Text)+        , cache_control :: Maybe CacheControl         }-    | ToolDef_SearchTool ToolSearchTool+    | ToolDef_SearchTool+        { tool_search_tool :: ToolSearchTool+        , cache_control :: Maybe CacheControl+        }     | ToolDef_CodeExecutionTool         { name :: Text         , type_ :: Text+        , cache_control :: Maybe CacheControl         }     deriving stock (Eq, Show) @@ -277,19 +298,32 @@     parseJSON = Aeson.withObject "ToolDefinition" $ \o -> do         -- Check if this is a tool search tool or code execution tool by looking for "type" field         mType <- o Aeson..:? "type"+        cache_control <- o Aeson..:? "cache_control"         case mType of             Just t | isToolSearchType t -> do                 searchTool <- Aeson.parseJSON (Aeson.Object o)-                pure (ToolDef_SearchTool searchTool)+                pure ToolDef_SearchTool+                    { tool_search_tool = searchTool+                    , cache_control = cache_control+                    }             Just t | isCodeExecutionType t -> do                 name <- o Aeson..: "name"-                pure ToolDef_CodeExecutionTool{ name, type_ = t }+                pure ToolDef_CodeExecutionTool+                    { name+                    , type_ = t+                    , cache_control = cache_control+                    }             _ -> do                 -- Parse as function tool                 tool <- Aeson.parseJSON (Aeson.Object o)                 defer_loading <- o Aeson..:? "defer_loading"                 allowed_callers <- o Aeson..:? "allowed_callers"-                pure ToolDef_Function{ tool, defer_loading, allowed_callers }+                pure ToolDef_Function+                    { tool+                    , defer_loading+                    , allowed_callers+                    , cache_control = cache_control+                    }       where         isToolSearchType :: Text -> Bool         isToolSearchType t = t == "tool_search_tool_regex_20251119"@@ -298,7 +332,7 @@         isCodeExecutionType t = t == "code_execution_20250825"  instance ToJSON ToolDefinition where-    toJSON (ToolDef_Function Tool{ name, description, input_schema, strict } defer_loading allowed_callers) =+    toJSON (ToolDef_Function Tool{ name, description, input_schema, strict } defer_loading allowed_callers cache_control) =         Aeson.Object (baseMap <> optionalFields)       where         baseObj = Aeson.object $@@ -311,20 +345,37 @@         optionalFields = KeyMap.fromList $             maybe [] (\dl -> [("defer_loading", Aeson.toJSON dl)]) defer_loading <>             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+            maybe [] (\s -> [("strict", Aeson.toJSON s)]) strict <>+            maybe [] (\cc -> [("cache_control", Aeson.toJSON cc)]) cache_control+    toJSON (ToolDef_SearchTool searchTool cache_control) =+        case Aeson.toJSON searchTool of+            Aeson.Object baseMap ->+                Aeson.Object $+                    baseMap <> KeyMap.fromList+                        (maybe [] (\cc -> [("cache_control", Aeson.toJSON cc)]) cache_control)+            other -> other+    toJSON (ToolDef_CodeExecutionTool name type_ cache_control) = Aeson.object $         [ "name" Aeson..= name         , "type" Aeson..= type_-        ]+        ] <> maybe [] (\cc -> ["cache_control" Aeson..= cc]) cache_control  -- | Wrap a tool for inline (non-deferred) loading inlineTool :: Tool -> ToolDefinition-inlineTool t = ToolDef_Function{ tool = t, defer_loading = Nothing, allowed_callers = Nothing }+inlineTool t = ToolDef_Function+    { tool = t+    , defer_loading = Nothing+    , allowed_callers = Nothing+    , cache_control = Nothing+    }  -- | Wrap a tool for deferred loading (used with tool search) deferredTool :: Tool -> ToolDefinition-deferredTool t = ToolDef_Function{ tool = t, defer_loading = Just True, allowed_callers = Nothing }+deferredTool t = ToolDef_Function+    { tool = t+    , defer_loading = Just True+    , allowed_callers = Nothing+    , cache_control = Nothing+    }  -- | Code execution tool for programmatic tool calling (PTC) --@@ -334,6 +385,7 @@ codeExecutionTool = ToolDef_CodeExecutionTool     { name = "code_execution"     , type_ = "code_execution_20250825"+    , cache_control = Nothing     }  -- | Allowed callers for code execution (PTC)@@ -352,21 +404,34 @@ -- allowCallers allowedCallersCodeExecution (inlineTool myTool) -- @ allowCallers :: Vector Text -> ToolDefinition -> ToolDefinition-allowCallers callers (ToolDef_Function t dl _) = ToolDef_Function t dl (Just callers)+allowCallers callers (ToolDef_Function t dl _ cc) = ToolDef_Function t dl (Just callers) cc allowCallers _ td = td +-- | Set cache_control on a tool definition.+withToolCacheControl :: CacheControl -> ToolDefinition -> ToolDefinition+withToolCacheControl cc (ToolDef_Function t dl ac _) = ToolDef_Function t dl ac (Just cc)+withToolCacheControl cc (ToolDef_SearchTool t _) = ToolDef_SearchTool t (Just cc)+withToolCacheControl cc (ToolDef_CodeExecutionTool n t _) =+    ToolDef_CodeExecutionTool n t (Just cc)+ -- | Tool search using regex matching toolSearchRegex :: ToolDefinition-toolSearchRegex = ToolDef_SearchTool ToolSearchTool-    { name = "tool_search_tool_regex"-    , type_ = ToolSearchTool_Regex_20251119+toolSearchRegex = ToolDef_SearchTool+    { tool_search_tool = ToolSearchTool+        { name = "tool_search_tool_regex"+        , type_ = ToolSearchTool_Regex_20251119+        }+    , cache_control = Nothing     }  -- | Tool search using BM25 matching toolSearchBm25 :: ToolDefinition-toolSearchBm25 = ToolDef_SearchTool ToolSearchTool-    { name = "tool_search_tool_bm25"-    , type_ = ToolSearchTool_Bm25_20251119+toolSearchBm25 = ToolDef_SearchTool+    { tool_search_tool = ToolSearchTool+        { name = "tool_search_tool_bm25"+        , type_ = ToolSearchTool_Bm25_20251119+        }+    , cache_control = Nothing     }  -- | Content block types (duplicated here for helper functions)
tasty/Main.hs view
@@ -52,6 +52,94 @@     let version = Just "2023-06-01"     let Methods{..} = V1.makeMethods clientEnv (Text.pack key) version +    let systemPromptStringLiveTest =+            HUnit.testCase "Create message - system prompt string" do+                Messages.MessageResponse{ content } <-+                    createMessage+                        Messages._CreateMessage+                            { Messages.model = model+                            , Messages.messages =+                                [ Messages.Message+                                    { Messages.role = Messages.User+                                    , Messages.content =+                                        [ Messages.textContent "Say exactly: system-string-ok"+                                        ]+                                    , Messages.cache_control = Nothing+                                    }+                                ]+                            , Messages.max_tokens = 32+                            , Messages.system = Just "Respond with exactly: system-string-ok"+                            }++                HUnit.assertBool "Response should have content"+                    (not (null content))++    let systemPromptBlocksLiveTest =+            HUnit.testCase "Create message - system prompt blocks with cache control" do+                Messages.MessageResponse{ content } <-+                    createMessage+                        Messages._CreateMessage+                            { Messages.model = model+                            , Messages.messages =+                                [ Messages.Message+                                    { Messages.role = Messages.User+                                    , Messages.content =+                                        [ Messages.textContent "Follow the system instructions."+                                        ]+                                    , Messages.cache_control = Nothing+                                    }+                                ]+                            , Messages.max_tokens = 64+                            , Messages.system = Just $ Messages.systemBlocks+                                [ Messages.systemTextBlockCached+                                    Messages.ephemeralCache+                                    "Respond with exactly: system-block-ok"+                                ]+                            }++                HUnit.assertBool "Response should have content"+                    (not (null content))++    let toolDefinitionCacheControlLiveTest =+            HUnit.testCase "Create message - tool cache_control" do+                let weatherTool = Tool.functionTool+                        "get_weather"+                        (Just "Get weather for a location")+                        $ Aeson.object+                            [ "properties" Aeson..= Aeson.object+                                [ "location" Aeson..= Aeson.object+                                    [ "type" Aeson..= ("string" :: Text.Text)+                                    ]+                                ]+                            , "required" Aeson..= (["location"] :: [Text.Text])+                            ]++                Messages.MessageResponse{ stop_reason } <-+                    createMessage+                        Messages._CreateMessage+                            { Messages.model = model+                            , Messages.messages =+                                [ Messages.Message+                                    { Messages.role = Messages.User+                                    , Messages.content =+                                        [ Messages.textContent "What's the weather in SF? Use the tool."+                                        ]+                                    , Messages.cache_control = Nothing+                                    }+                                ]+                            , Messages.max_tokens = 200+                            , Messages.tools = Just+                                [ Tool.withToolCacheControl+                                    Messages.ephemeralCache+                                    (Tool.inlineTool weatherTool)+                                ]+                            , Messages.tool_choice = Just Tool.ToolChoice_Any+                            }++                HUnit.assertEqual "Should stop for tool use"+                    (Just Messages.Tool_Use)+                    stop_reason+     let messagesMinimalTest =             HUnit.testCase "Create message - minimal" do                 Messages.MessageResponse{ content } <-@@ -419,6 +507,47 @@                 HUnit.assertBool "Expected non-empty streamed text"                     (not (Text.null text)) +    let promptCachingLiveTest =+            HUnit.testCase "Create message - prompt caching" do+                let largeSystemText =+                        Text.intercalate " " (replicate 6000 ("cacheable-guidance" :: Text.Text))+                let systemPrompt = Messages.systemBlocks+                        [ Messages.systemTextBlockCached Messages.ephemeralCache largeSystemText+                        ]+                let request = Messages._CreateMessage+                        { Messages.model = model+                        , Messages.messages =+                            [ Messages.Message+                                { Messages.role = Messages.User+                                , Messages.content =+                                    [ Messages.textContent "Respond with exactly: cachetest"+                                    ]+                                , Messages.cache_control = Nothing+                                }+                            ]+                        , Messages.max_tokens = 64+                        , Messages.system = Just systemPrompt+                        , Messages.cache_control = Just Messages.ephemeralCache+                        }++                Messages.MessageResponse{ Messages.usage = usage1 } <- createMessage request+                Messages.MessageResponse{ Messages.usage = usage2 } <- createMessage request++                let firstCreatedCache =+                        maybe False (> 0) (Messages.cache_creation_input_tokens usage1)+                let firstReadCache =+                        maybe False (> 0) (Messages.cache_read_input_tokens usage1)+                let readCache =+                        maybe False (> 0) (Messages.cache_read_input_tokens usage2)++                HUnit.assertBool+                    "First request should create or read prompt cache tokens"+                    (firstCreatedCache || firstReadCache)++                HUnit.assertBool+                    "Second request should read prompt cache tokens"+                    readCache+     let inferenceGeoLiveTest =             HUnit.testCase "Create message - inference_geo" do                 Messages.MessageResponse{ content } <-@@ -735,7 +864,10 @@                         HUnit.assertFailure $ "Unexpected stop_reason: " <> show other      let tests =-            [ messagesMinimalTest+            [ systemPromptStringLiveTest+            , systemPromptBlocksLiveTest+            , toolDefinitionCacheControlLiveTest+            , messagesMinimalTest             , messagesWithSystemTest             , messagesStreamingTest             , messagesConversationTest@@ -745,6 +877,7 @@             , strictToolUseTest             , adaptiveThinkingTest             , adaptiveThinkingStreamingTest+            , promptCachingLiveTest             , inferenceGeoLiveTest             , compactionLiveTest             , fastModeLiveTest