packages feed

claude 1.4.0 → 1.5.0

raw patch · 8 files changed

+488/−19 lines, 8 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Claude.V1.Messages: instance GHC.Generics.Generic Claude.V1.Messages.StreamUsage
+ Claude.V1.Messages: ContentBlock_Fallback :: FallbackInfo -> FallbackInfo -> FallbackTrigger -> ContentBlock
+ Claude.V1.Messages: Content_Fallback :: FallbackInfo -> FallbackInfo -> FallbackTrigger -> Content
+ Claude.V1.Messages: Content_Tool_Addition :: ToolChangeReference -> Maybe CacheControl -> Content
+ Claude.V1.Messages: Content_Tool_Removal :: ToolChangeReference -> Maybe CacheControl -> Content
+ Claude.V1.Messages: Fallback :: Text -> Maybe Natural -> Maybe OutputConfig -> Maybe Speed -> Maybe Thinking -> Fallback
+ Claude.V1.Messages: FallbackInfo :: Text -> FallbackInfo
+ Claude.V1.Messages: FallbackTrigger :: Maybe Text -> FallbackTrigger
+ Claude.V1.Messages: FallbacksDefault :: Fallbacks
+ Claude.V1.Messages: FallbacksModels :: Vector Fallback -> Fallbacks
+ Claude.V1.Messages: IterationUsage :: Text -> Maybe Text -> Maybe Natural -> Maybe Natural -> Maybe Natural -> Maybe Natural -> IterationUsage
+ Claude.V1.Messages: MCPToolChangeReference :: Text -> Text -> ToolChangeReference
+ Claude.V1.Messages: MCPToolsetChangeReference :: Text -> ToolChangeReference
+ Claude.V1.Messages: OutputTokensDetails :: Natural -> OutputTokensDetails
+ Claude.V1.Messages: Pause_Turn :: StopReason
+ Claude.V1.Messages: StopDetails :: Maybe Text -> Maybe Text -> StopDetails
+ Claude.V1.Messages: System :: Role
+ Claude.V1.Messages: ThinkingAdaptiveWithDisplay :: ThinkingDisplay -> Thinking
+ Claude.V1.Messages: ThinkingDisabled :: Thinking
+ Claude.V1.Messages: ThinkingEnabledWithDisplay :: Natural -> ThinkingDisplay -> Thinking
+ Claude.V1.Messages: ThinkingOmitted :: ThinkingDisplay
+ Claude.V1.Messages: ThinkingSummarized :: ThinkingDisplay
+ Claude.V1.Messages: ToolChangeReference :: Text -> ToolChangeReference
+ Claude.V1.Messages: [category] :: StopDetails -> Maybe Text
+ Claude.V1.Messages: [display] :: Thinking -> ThinkingDisplay
+ Claude.V1.Messages: [explanation] :: StopDetails -> Maybe Text
+ Claude.V1.Messages: [fallbacks] :: CreateMessage -> Maybe Fallbacks
+ Claude.V1.Messages: [from_] :: ContentBlock -> FallbackInfo
+ Claude.V1.Messages: [iteration_cache_creation_input_tokens] :: IterationUsage -> Maybe Natural
+ Claude.V1.Messages: [iteration_cache_read_input_tokens] :: IterationUsage -> Maybe Natural
+ Claude.V1.Messages: [iteration_input_tokens] :: IterationUsage -> Maybe Natural
+ Claude.V1.Messages: [iteration_model] :: IterationUsage -> Maybe Text
+ Claude.V1.Messages: [iteration_output_tokens] :: IterationUsage -> Maybe Natural
+ Claude.V1.Messages: [iteration_type] :: IterationUsage -> Text
+ Claude.V1.Messages: [iterations] :: Usage -> Maybe (Vector IterationUsage)
+ Claude.V1.Messages: [output_tokens_details] :: Usage -> Maybe OutputTokensDetails
+ Claude.V1.Messages: [server_name] :: ToolChangeReference -> Text
+ Claude.V1.Messages: [service_tier] :: Usage -> Maybe Text
+ Claude.V1.Messages: [stop_details] :: MessageDelta -> Maybe StopDetails
+ Claude.V1.Messages: [stream_cache_creation_input_tokens] :: StreamUsage -> Maybe Natural
+ Claude.V1.Messages: [stream_cache_read_input_tokens] :: StreamUsage -> Maybe Natural
+ Claude.V1.Messages: [stream_input_tokens] :: StreamUsage -> Maybe Natural
+ Claude.V1.Messages: [stream_iterations] :: StreamUsage -> Maybe (Vector IterationUsage)
+ Claude.V1.Messages: [stream_output_tokens_details] :: StreamUsage -> Maybe OutputTokensDetails
+ Claude.V1.Messages: [stream_server_tool_use] :: StreamUsage -> Maybe ServerToolUseUsage
+ Claude.V1.Messages: [thinking_tokens] :: OutputTokensDetails -> Natural
+ Claude.V1.Messages: [to] :: ContentBlock -> FallbackInfo
+ Claude.V1.Messages: data Fallback
+ Claude.V1.Messages: data FallbackInfo
+ Claude.V1.Messages: data FallbackTrigger
+ Claude.V1.Messages: data Fallbacks
+ Claude.V1.Messages: data IterationUsage
+ Claude.V1.Messages: data OutputTokensDetails
+ Claude.V1.Messages: data StopDetails
+ Claude.V1.Messages: data ThinkingDisplay
+ Claude.V1.Messages: data ToolChangeReference
+ Claude.V1.Messages: instance Data.Aeson.Types.FromJSON.FromJSON Claude.V1.Messages.Fallback
+ Claude.V1.Messages: instance Data.Aeson.Types.FromJSON.FromJSON Claude.V1.Messages.FallbackInfo
+ Claude.V1.Messages: instance Data.Aeson.Types.FromJSON.FromJSON Claude.V1.Messages.FallbackTrigger
+ Claude.V1.Messages: instance Data.Aeson.Types.FromJSON.FromJSON Claude.V1.Messages.Fallbacks
+ Claude.V1.Messages: instance Data.Aeson.Types.FromJSON.FromJSON Claude.V1.Messages.IterationUsage
+ Claude.V1.Messages: instance Data.Aeson.Types.FromJSON.FromJSON Claude.V1.Messages.OutputTokensDetails
+ Claude.V1.Messages: instance Data.Aeson.Types.FromJSON.FromJSON Claude.V1.Messages.StopDetails
+ Claude.V1.Messages: instance Data.Aeson.Types.FromJSON.FromJSON Claude.V1.Messages.ThinkingDisplay
+ Claude.V1.Messages: instance Data.Aeson.Types.FromJSON.FromJSON Claude.V1.Messages.ToolChangeReference
+ Claude.V1.Messages: instance Data.Aeson.Types.ToJSON.ToJSON Claude.V1.Messages.Fallback
+ Claude.V1.Messages: instance Data.Aeson.Types.ToJSON.ToJSON Claude.V1.Messages.FallbackInfo
+ Claude.V1.Messages: instance Data.Aeson.Types.ToJSON.ToJSON Claude.V1.Messages.FallbackTrigger
+ Claude.V1.Messages: instance Data.Aeson.Types.ToJSON.ToJSON Claude.V1.Messages.Fallbacks
+ Claude.V1.Messages: instance Data.Aeson.Types.ToJSON.ToJSON Claude.V1.Messages.IterationUsage
+ Claude.V1.Messages: instance Data.Aeson.Types.ToJSON.ToJSON Claude.V1.Messages.OutputTokensDetails
+ Claude.V1.Messages: instance Data.Aeson.Types.ToJSON.ToJSON Claude.V1.Messages.StopDetails
+ Claude.V1.Messages: instance Data.Aeson.Types.ToJSON.ToJSON Claude.V1.Messages.ThinkingDisplay
+ Claude.V1.Messages: instance Data.Aeson.Types.ToJSON.ToJSON Claude.V1.Messages.ToolChangeReference
+ Claude.V1.Messages: instance GHC.Classes.Eq Claude.V1.Messages.Fallback
+ Claude.V1.Messages: instance GHC.Classes.Eq Claude.V1.Messages.FallbackInfo
+ Claude.V1.Messages: instance GHC.Classes.Eq Claude.V1.Messages.FallbackTrigger
+ Claude.V1.Messages: instance GHC.Classes.Eq Claude.V1.Messages.Fallbacks
+ Claude.V1.Messages: instance GHC.Classes.Eq Claude.V1.Messages.IterationUsage
+ Claude.V1.Messages: instance GHC.Classes.Eq Claude.V1.Messages.OutputTokensDetails
+ Claude.V1.Messages: instance GHC.Classes.Eq Claude.V1.Messages.StopDetails
+ Claude.V1.Messages: instance GHC.Classes.Eq Claude.V1.Messages.ThinkingDisplay
+ Claude.V1.Messages: instance GHC.Classes.Eq Claude.V1.Messages.ToolChangeReference
+ Claude.V1.Messages: instance GHC.Generics.Generic Claude.V1.Messages.Fallback
+ Claude.V1.Messages: instance GHC.Generics.Generic Claude.V1.Messages.FallbackInfo
+ Claude.V1.Messages: instance GHC.Generics.Generic Claude.V1.Messages.IterationUsage
+ Claude.V1.Messages: instance GHC.Generics.Generic Claude.V1.Messages.OutputTokensDetails
+ Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.Fallback
+ Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.FallbackInfo
+ Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.FallbackTrigger
+ Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.Fallbacks
+ Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.IterationUsage
+ Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.OutputTokensDetails
+ Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.StopDetails
+ Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.ThinkingDisplay
+ Claude.V1.Messages: instance GHC.Show.Show Claude.V1.Messages.ToolChangeReference
- 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: 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 -> Maybe Fallbacks -> CreateMessage
- Claude.V1.Messages: MessageDelta :: Maybe StopReason -> Maybe Text -> MessageDelta
+ Claude.V1.Messages: MessageDelta :: Maybe StopReason -> Maybe Text -> Maybe StopDetails -> MessageDelta
- Claude.V1.Messages: MessageResponse :: Text -> Text -> Role -> Vector ContentBlock -> Text -> Maybe StopReason -> Maybe Text -> Usage -> Maybe ContainerInfo -> MessageResponse
+ Claude.V1.Messages: MessageResponse :: Text -> Text -> Role -> Vector ContentBlock -> Text -> Maybe StopReason -> Maybe Text -> Maybe StopDetails -> Usage -> Maybe ContainerInfo -> MessageResponse
- Claude.V1.Messages: StreamUsage :: Natural -> StreamUsage
+ Claude.V1.Messages: StreamUsage :: Maybe Natural -> Natural -> Maybe Natural -> Maybe Natural -> Maybe ServerToolUseUsage -> Maybe OutputTokensDetails -> Maybe (Vector IterationUsage) -> StreamUsage
- Claude.V1.Messages: Usage :: Natural -> Natural -> Maybe Natural -> Maybe Natural -> Maybe ServerToolUseUsage -> Usage
+ Claude.V1.Messages: Usage :: Natural -> Natural -> Maybe Natural -> Maybe Natural -> Maybe ServerToolUseUsage -> Maybe Text -> Maybe OutputTokensDetails -> Maybe Text -> Maybe Speed -> Maybe (Vector IterationUsage) -> Usage
- Claude.V1.Messages: [inference_geo] :: CreateMessage -> Maybe Text
+ Claude.V1.Messages: [inference_geo] :: Usage -> Maybe Text
- Claude.V1.Messages: [max_tokens] :: CreateMessage -> Natural
+ Claude.V1.Messages: [max_tokens] :: Fallback -> Maybe Natural
- Claude.V1.Messages: [output_config] :: CreateMessage -> Maybe OutputConfig
+ Claude.V1.Messages: [output_config] :: Fallback -> Maybe OutputConfig
- Claude.V1.Messages: [speed] :: CreateMessage -> Maybe Speed
+ Claude.V1.Messages: [speed] :: Usage -> Maybe Speed
- Claude.V1.Messages: [trigger] :: ContextManagementEdit -> Maybe CompactionTrigger
+ Claude.V1.Messages: [trigger] :: ContentBlock -> FallbackTrigger

Files

CHANGELOG.md view
@@ -1,3 +1,17 @@+1.5.0:++- Add Claude 5 Messages API support:+  - `thinking.display` with summarized and omitted output modes+  - `thinking: { "type": "disabled" }`+  - refusal `stop_details` and `pause_turn` stop reasons+- Add beta server-side fallback request and response types:+  - default and explicit fallback chains+  - fallback transition content blocks+  - per-iteration usage+- Add beta mid-conversation tool changes with system messages and tool+  addition/removal content blocks+- Add live API coverage for Claude Sonnet 5, Claude Opus 5, and Claude Fable 5+ 1.4.0:  - Expand prompt caching support:
README.md view
@@ -24,7 +24,7 @@     let Methods{ createMessage } = makeMethods clientEnv (Text.pack key) (Just "2023-06-01")      MessageResponse{ content } <- createMessage _CreateMessage-        { model = "claude-sonnet-4-5-20250929"+        { model = "claude-sonnet-5"         , messages = [ Message{ role = User, content = [ textContent "Hello!" ] } ]         , max_tokens = 1024         }@@ -45,7 +45,7 @@  ```haskell MessageResponse{ usage } <- createMessage _CreateMessage-    { model = "claude-sonnet-4-5-20250929"+    { model = "claude-sonnet-5"     , max_tokens = 1024     , cache_control = Just (ephemeralCacheWithTTLDuration "1h")     , system = Just $ systemBlocks
claude.cabal view
@@ -1,6 +1,6 @@ cabal-version:      2.4 name:               claude-version:            1.4.0+version:            1.5.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,8 @@     Text.IO.putStrLn "  [Thinking]" printContentBlock (Messages.ContentBlock_Redacted_Thinking{}) =     Text.IO.putStrLn "  [Redacted thinking]"+printContentBlock (Messages.ContentBlock_Fallback{}) =+    Text.IO.putStrLn "  [Fallback]" printContentBlock (Messages.ContentBlock_Unknown{ Messages.type_ = t }) =     Text.IO.putStrLn $ "  [Unknown] " <> t 
examples/claude-tool-example/Main.hs view
@@ -190,5 +190,7 @@     Text.IO.putStrLn "[Thinking]" printContent (Messages.ContentBlock_Redacted_Thinking{}) =     Text.IO.putStrLn "[Redacted thinking]"+printContent (Messages.ContentBlock_Fallback{}) =+    Text.IO.putStrLn "[Fallback]" printContent (Messages.ContentBlock_Unknown{ Messages.type_ = t }) =     Text.IO.putStrLn $ "[Unknown block type: " <> t <> "]"
examples/claude-tool-search-example/Main.hs view
@@ -194,5 +194,7 @@     Text.IO.putStrLn "  [thinking]" printContent (Messages.ContentBlock_Redacted_Thinking{}) =     Text.IO.putStrLn "  [redacted_thinking]"+printContent (Messages.ContentBlock_Fallback{}) =+    Text.IO.putStrLn "  [fallback]" printContent (Messages.ContentBlock_Unknown{ Messages.type_ = t }) =     Text.IO.putStrLn $ "  [unknown] type: " <> t
src/Claude/V1/Messages.hs view
@@ -20,11 +20,17 @@     , inputTokensTrigger       -- * Request routing/speed     , Speed(..)+    , Fallback(..)+    , Fallbacks(..)       -- * Thinking     , Thinking(..)+    , ThinkingDisplay(..)       -- * Content types     , Content(..)     , ContentBlock(..)+    , ToolChangeReference(..)+    , FallbackInfo(..)+    , FallbackTrigger(..)     , TextContent(..)     , ImageSource(..)     , ToolUseContent(..)@@ -34,7 +40,10 @@     , Role(..)       -- * Response types     , StopReason(..)+    , StopDetails(..)     , Usage(..)+    , IterationUsage(..)+    , OutputTokensDetails(..)     , ServerToolUseUsage(..)       -- * Tool search response types     , ToolReference(..)@@ -124,16 +133,16 @@     , toolChoiceAny     , toolChoiceAuto     , toolChoiceTool-    , withToolCacheControl     , toolSearchBm25     , toolSearchRegex+    , withToolCacheControl     ) 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+data Role = User | Assistant | System     deriving stock (Eq, Generic, Show)  instance FromJSON Role where@@ -269,6 +278,19 @@     | 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 }+    | Content_Tool_Addition+        { tool :: ToolChangeReference+        , cache_control :: Maybe CacheControl+        }+    | Content_Tool_Removal+        { tool :: ToolChangeReference+        , cache_control :: Maybe CacheControl+        }+    | Content_Fallback+        { from_ :: FallbackInfo+        , to :: FallbackInfo+        , trigger :: FallbackTrigger+        }     deriving stock (Generic, Show)  -- | Create a text content block without cache control@@ -324,8 +346,49 @@     Just Content_Thinking{ thinking = t, signature = sig } contentBlockToContent (ContentBlock_Redacted_Thinking d) =     Just Content_Redacted_Thinking{ data_ = d }+contentBlockToContent (ContentBlock_Fallback fromModel toModel fallbackTrigger) =+    Just Content_Fallback+        { from_ = fromModel+        , to = toModel+        , trigger = fallbackTrigger+        } contentBlockToContent ContentBlock_Unknown{} = Nothing +-- | A tool reference used by mid-conversation tool changes.+data ToolChangeReference+    = ToolChangeReference { name :: Text }+    | MCPToolChangeReference { server_name :: Text, name :: Text }+    | MCPToolsetChangeReference { server_name :: Text }+    deriving stock (Eq, Show)++instance FromJSON ToolChangeReference where+    parseJSON = Aeson.withObject "ToolChangeReference" $ \o -> do+        t <- o Aeson..: "type"+        case (t :: Text) of+            "tool_reference" -> ToolChangeReference <$> o Aeson..: "name"+            "mcp_tool_reference" ->+                MCPToolChangeReference+                    <$> o Aeson..: "server_name"+                    <*> o Aeson..: "name"+            "mcp_toolset_reference" ->+                MCPToolsetChangeReference <$> o Aeson..: "server_name"+            _ -> fail $ "Unknown tool change reference type: " <> show t++instance ToJSON ToolChangeReference where+    toJSON (ToolChangeReference n) = Aeson.object+        [ "type" Aeson..= ("tool_reference" :: Text)+        , "name" Aeson..= n+        ]+    toJSON (MCPToolChangeReference serverName n) = Aeson.object+        [ "type" Aeson..= ("mcp_tool_reference" :: Text)+        , "server_name" Aeson..= serverName+        , "name" Aeson..= n+        ]+    toJSON (MCPToolsetChangeReference serverName) = Aeson.object+        [ "type" Aeson..= ("mcp_toolset_reference" :: Text)+        , "server_name" Aeson..= serverName+        ]+ -- | A reference to a tool found by tool search data ToolReference = ToolReference     { tool_name :: Text@@ -492,6 +555,11 @@         }     | ContentBlock_Thinking { thinking :: Text, signature :: Text }     | ContentBlock_Redacted_Thinking { data_ :: Text }+    | ContentBlock_Fallback+        { from_ :: FallbackInfo+        , to :: FallbackInfo+        , trigger :: FallbackTrigger+        }     | ContentBlock_Unknown { type_ :: Text, raw :: Value }     deriving stock (Generic, Show) @@ -528,6 +596,10 @@                 <*> o Aeson..: "signature"             "redacted_thinking" -> ContentBlock_Redacted_Thinking                 <$> o Aeson..: "data"+            "fallback" -> ContentBlock_Fallback+                <$> o Aeson..: "from"+                <*> o Aeson..: "to"+                <*> o Aeson..: "trigger"             _ -> pure (ContentBlock_Unknown t (Aeson.Object o))  instance ToJSON ContentBlock where@@ -566,8 +638,37 @@         [ "type" Aeson..= ("redacted_thinking" :: Text)         , "data" Aeson..= d         ]+    toJSON (ContentBlock_Fallback fromModel toModel fallbackTrigger) = Aeson.object+        [ "type" Aeson..= ("fallback" :: Text)+        , "from" Aeson..= fromModel+        , "to" Aeson..= toModel+        , "trigger" Aeson..= fallbackTrigger+        ]     toJSON (ContentBlock_Unknown _typeName rawVal) = rawVal +-- | A model participating in a server-side fallback transition.+data FallbackInfo = FallbackInfo+    { model :: Text+    } deriving stock (Eq, Generic, Show)+      deriving anyclass (FromJSON, ToJSON)++-- | The refusal that triggered a server-side fallback.+data FallbackTrigger = FallbackTrigger+    { category :: Maybe Text+    } deriving stock (Eq, Show)++instance FromJSON FallbackTrigger where+    parseJSON = Aeson.withObject "FallbackTrigger" $ \o -> do+        t <- o Aeson..: "type"+        if (t :: Text) == "refusal"+            then FallbackTrigger <$> o Aeson..:? "category"+            else fail $ "Unknown fallback trigger type: " <> show t++instance ToJSON FallbackTrigger where+    toJSON (FallbackTrigger fallbackCategory) = Aeson.object $+        [ "type" Aeson..= ("refusal" :: Text)+        ] <> maybe [] (\c -> ["category" Aeson..= c]) fallbackCategory+ -- | A message in the conversation -- -- The optional @cache_control@ field allows setting cache breakpoints at@@ -590,6 +691,7 @@     | Max_Tokens     | Stop_Sequence     | Tool_Use+    | Pause_Turn     | Refusal     -- ^ Model refused the request for safety reasons     | Model_Context_Window_Exceeded@@ -606,6 +708,27 @@ instance ToJSON StopReason where     toJSON = genericToJSON stopReasonOptions +-- | Structured information returned when Claude refuses a request.+data StopDetails = StopDetails+    { category :: Maybe Text+    , explanation :: Maybe Text+    } deriving stock (Eq, Show)++instance FromJSON StopDetails where+    parseJSON = Aeson.withObject "StopDetails" $ \o -> do+        t <- o Aeson..: "type"+        if (t :: Text) == "refusal"+            then StopDetails+                <$> o Aeson..:? "category"+                <*> o Aeson..:? "explanation"+            else fail $ "Unknown stop details type: " <> show t++instance ToJSON StopDetails where+    toJSON StopDetails{ category, explanation } = Aeson.object $+        [ "type" Aeson..= ("refusal" :: Text)+        ] <> maybe [] (\c -> ["category" Aeson..= c]) category+          <> maybe [] (\e -> ["explanation" Aeson..= e]) explanation+ -- | Server tool use usage information (e.g., tool search requests, web search requests) data ServerToolUseUsage = ServerToolUseUsage     { web_search_requests :: Maybe Natural@@ -618,6 +741,43 @@ instance ToJSON ServerToolUseUsage where     toJSON = genericToJSON aesonOptions +-- | Breakdown of output tokens spent on internal reasoning.+data OutputTokensDetails = OutputTokensDetails+    { thinking_tokens :: Natural+    } deriving stock (Eq, Generic, Show)+      deriving anyclass (FromJSON, ToJSON)++-- | Per-iteration usage returned by beta features such as server-side fallback.+data IterationUsage = IterationUsage+    { iteration_type :: Text+    , iteration_model :: Maybe Text+    , iteration_input_tokens :: Maybe Natural+    , iteration_output_tokens :: Maybe Natural+    , iteration_cache_creation_input_tokens :: Maybe Natural+    , iteration_cache_read_input_tokens :: Maybe Natural+    } deriving stock (Eq, Generic, Show)++instance FromJSON IterationUsage where+    parseJSON = Aeson.withObject "IterationUsage" $ \o ->+        IterationUsage+            <$> o Aeson..: "type"+            <*> o Aeson..:? "model"+            <*> o Aeson..:? "input_tokens"+            <*> o Aeson..:? "output_tokens"+            <*> o Aeson..:? "cache_creation_input_tokens"+            <*> o Aeson..:? "cache_read_input_tokens"++instance ToJSON IterationUsage where+    toJSON IterationUsage{..} = Aeson.object $+        [ "type" Aeson..= iteration_type+        ] <> maybe [] (\m -> ["model" Aeson..= m]) iteration_model+          <> maybe [] (\n -> ["input_tokens" Aeson..= n]) iteration_input_tokens+          <> maybe [] (\n -> ["output_tokens" Aeson..= n]) iteration_output_tokens+          <> maybe [] (\n -> ["cache_creation_input_tokens" Aeson..= n])+            iteration_cache_creation_input_tokens+          <> maybe [] (\n -> ["cache_read_input_tokens" Aeson..= n])+            iteration_cache_read_input_tokens+ -- | Token usage information data Usage = Usage     { input_tokens :: Natural@@ -625,6 +785,11 @@     , cache_creation_input_tokens :: Maybe Natural     , cache_read_input_tokens :: Maybe Natural     , server_tool_use :: Maybe ServerToolUseUsage+    , inference_geo :: Maybe Text+    , output_tokens_details :: Maybe OutputTokensDetails+    , service_tier :: Maybe Text+    , speed :: Maybe Speed+    , iterations :: Maybe (Vector IterationUsage)     } deriving stock (Generic, Show)  instance FromJSON Usage where@@ -646,6 +811,7 @@     , model :: Text     , stop_reason :: Maybe StopReason     , stop_sequence :: Maybe Text+    , stop_details :: Maybe StopDetails     , usage :: Usage     , container :: Maybe ContainerInfo     } deriving stock (Generic, Show)@@ -698,8 +864,8 @@ --              Good for simple tasks, classification, or high-volume use cases. -- * @"medium"@ — Balanced approach with moderate token savings. -- * @"high"@ — Full capability (the default when effort is omitted).+-- * @"xhigh"@ — Extended capability for long-horizon work on supported models. -- * @"max"@ — Absolute maximum capability, no constraints on token spending.---              Opus 4.6 only. -- -- Setting @"high"@ is equivalent to not setting effort at all. --@@ -721,9 +887,8 @@ -- @ data OutputConfig = OutputConfig     { effort :: Maybe Text-    -- ^ Effort level: @"low"@, @"medium"@, @"high"@, or @"max"@.+    -- ^ Effort level: @"low"@, @"medium"@, @"high"@, @"xhigh"@, or @"max"@.     -- Controls token spend vs thoroughness. @"high"@ is the default.-    -- @"max"@ is Opus 4.6 only.     -- See <https://platform.claude.com/docs/en/build-with-claude/effort>     , format :: Maybe OutputFormat     -- ^ Structured output format (use 'jsonSchemaFormat' to construct)@@ -848,15 +1013,66 @@     toJSON SpeedStandard = Aeson.String "standard"     toJSON SpeedFast = Aeson.String "fast" +-- | One model in a server-side fallback chain.+data Fallback = Fallback+    { model :: Text+    , max_tokens :: Maybe Natural+    , output_config :: Maybe OutputConfig+    , speed :: Maybe Speed+    , thinking :: Maybe Thinking+    } deriving stock (Eq, Generic, Show)++instance FromJSON Fallback where+    parseJSON = genericParseJSON aesonOptions++instance ToJSON Fallback where+    toJSON = genericToJSON aesonOptions++-- | Server-side fallback configuration (beta).+data Fallbacks+    = FallbacksDefault+    | FallbacksModels (Vector Fallback)+    deriving stock (Eq, Show)++instance FromJSON Fallbacks where+    parseJSON (Aeson.String "default") = pure FallbacksDefault+    parseJSON (Aeson.Array models) = FallbacksModels <$> traverse Aeson.parseJSON models+    parseJSON _ = fail "Fallbacks must be \"default\" or an array of fallback models"++instance ToJSON Fallbacks where+    toJSON FallbacksDefault = Aeson.String "default"+    toJSON (FallbacksModels models) = Aeson.toJSON models++-- | Controls whether readable thinking summaries are returned.+data ThinkingDisplay+    = ThinkingSummarized+    | ThinkingOmitted+    deriving stock (Eq, Show)++instance FromJSON ThinkingDisplay where+    parseJSON = Aeson.withText "ThinkingDisplay" $ \t -> case t of+        "summarized" -> pure ThinkingSummarized+        "omitted" -> pure ThinkingOmitted+        _ -> fail $ "Unknown thinking display: " <> show t++instance ToJSON ThinkingDisplay where+    toJSON ThinkingSummarized = Aeson.String "summarized"+    toJSON ThinkingOmitted = Aeson.String "omitted"+ -- | Thinking configuration for extended thinking ----- * 'ThinkingAdaptive': Let Claude decide when and how much to think (Opus 4.6+).+-- * 'ThinkingAdaptive': Let Claude decide when and how much to think. --   Use the effort parameter ('effortConfig') to guide thinking depth.+-- * 'ThinkingAdaptiveWithDisplay': Adaptive thinking with summarized or omitted output. -- * 'ThinkingEnabled': Enable thinking with a fixed token budget---   (all models; deprecated on Opus 4.6).+--   (older models only).+-- * 'ThinkingDisabled': Disable thinking on models that support it. -- -- @--- -- Adaptive thinking (recommended for Opus 4.6)+-- -- Adaptive thinking with readable summaries+-- thinking = Just $ ThinkingAdaptiveWithDisplay ThinkingSummarized+--+-- -- Adaptive thinking with the model's default display behavior -- thinking = Just ThinkingAdaptive -- -- -- Manual thinking with budget (older models)@@ -864,26 +1080,52 @@ -- @ data Thinking     = ThinkingAdaptive+    | ThinkingAdaptiveWithDisplay { display :: ThinkingDisplay }     | ThinkingEnabled { budget_tokens :: Natural }+    | ThinkingEnabledWithDisplay+        { budget_tokens :: Natural+        , display :: ThinkingDisplay+        }+    | ThinkingDisabled     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+            "adaptive" -> do+                display <- o Aeson..:? "display"+                pure $ maybe ThinkingAdaptive ThinkingAdaptiveWithDisplay display             "enabled" -> do                 budget <- o Aeson..: "budget_tokens"-                pure ThinkingEnabled{ budget_tokens = budget }+                display <- o Aeson..:? "display"+                pure $ case display of+                    Nothing -> ThinkingEnabled{ budget_tokens = budget }+                    Just d -> ThinkingEnabledWithDisplay+                        { budget_tokens = budget+                        , display = d+                        }+            "disabled" -> pure ThinkingDisabled             _ -> fail $ "Unknown thinking type: " <> show t  instance ToJSON Thinking where     toJSON ThinkingAdaptive = Aeson.object         [ "type" Aeson..= ("adaptive" :: Text) ]+    toJSON (ThinkingAdaptiveWithDisplay thinkingDisplay) = Aeson.object+        [ "type" Aeson..= ("adaptive" :: Text)+        , "display" Aeson..= thinkingDisplay+        ]     toJSON (ThinkingEnabled budget) = Aeson.object         [ "type" Aeson..= ("enabled" :: Text)         , "budget_tokens" Aeson..= budget         ]+    toJSON (ThinkingEnabledWithDisplay budget thinkingDisplay) = Aeson.object+        [ "type" Aeson..= ("enabled" :: Text)+        , "budget_tokens" Aeson..= budget+        , "display" Aeson..= thinkingDisplay+        ]+    toJSON ThinkingDisabled = Aeson.object+        [ "type" Aeson..= ("disabled" :: Text) ]  -- | Create a JSON schema output format jsonSchemaFormat :: Value -> OutputFormat@@ -923,8 +1165,9 @@     -- ^ 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.+    -- ^ Thinking configuration. Claude 5 models default to adaptive thinking.+    , fallbacks :: Maybe Fallbacks+    -- ^ Server-side refusal fallback configuration (beta).     } deriving stock (Generic, Show)  instance FromJSON CreateMessage where@@ -955,6 +1198,7 @@     , speed = Nothing     , output_config = Nothing     , thinking = Nothing+    , fallbacks = Nothing     }  -- | Text delta in streaming@@ -1004,6 +1248,7 @@ data MessageDelta = MessageDelta     { stop_reason :: Maybe StopReason     , stop_sequence :: Maybe Text+    , stop_details :: Maybe StopDetails     } deriving stock (Generic, Show)  instance FromJSON MessageDelta where@@ -1014,14 +1259,38 @@  -- | Usage in streaming message_delta events data StreamUsage = StreamUsage-    { output_tokens :: Natural-    } deriving stock (Generic, Show)+    { stream_input_tokens :: Maybe Natural+    , output_tokens :: Natural+    , stream_cache_creation_input_tokens :: Maybe Natural+    , stream_cache_read_input_tokens :: Maybe Natural+    , stream_server_tool_use :: Maybe ServerToolUseUsage+    , stream_output_tokens_details :: Maybe OutputTokensDetails+    , stream_iterations :: Maybe (Vector IterationUsage)+    } deriving stock (Show)  instance FromJSON StreamUsage where-    parseJSON = genericParseJSON aesonOptions+    parseJSON = Aeson.withObject "StreamUsage" $ \o ->+        StreamUsage+            <$> o Aeson..:? "input_tokens"+            <*> o Aeson..: "output_tokens"+            <*> o Aeson..:? "cache_creation_input_tokens"+            <*> o Aeson..:? "cache_read_input_tokens"+            <*> o Aeson..:? "server_tool_use"+            <*> o Aeson..:? "output_tokens_details"+            <*> o Aeson..:? "iterations"  instance ToJSON StreamUsage where-    toJSON = genericToJSON aesonOptions+    toJSON StreamUsage{..} = Aeson.object $+        [ "output_tokens" Aeson..= output_tokens+        ] <> maybe [] (\n -> ["input_tokens" Aeson..= n]) stream_input_tokens+          <> maybe [] (\n -> ["cache_creation_input_tokens" Aeson..= n])+            stream_cache_creation_input_tokens+          <> maybe [] (\n -> ["cache_read_input_tokens" Aeson..= n])+            stream_cache_read_input_tokens+          <> maybe [] (\u -> ["server_tool_use" Aeson..= u]) stream_server_tool_use+          <> maybe [] (\d -> ["output_tokens_details" Aeson..= d])+            stream_output_tokens_details+          <> maybe [] (\is -> ["iterations" Aeson..= is]) stream_iterations  -- | Streaming events for @\/v1\/messages@ data MessageStreamEvent
tasty/Main.hs view
@@ -17,6 +17,7 @@ import qualified Control.Concurrent as Concurrent import qualified Control.Exception as Exception import qualified Data.Aeson as Aeson+import qualified Data.Aeson.KeyMap as KeyMap import qualified Data.Aeson.Types as Aeson.Types import           Data.Foldable (toList) import qualified Data.IORef as IORef@@ -507,6 +508,183 @@                 HUnit.assertBool "Expected non-empty streamed text"                     (not (Text.null text)) +    let claude5WireTypesTest =+            HUnit.testCase "Claude 5 wire types" do+                HUnit.assertEqual+                    "Adaptive thinking should include display"+                    (Aeson.object+                        [ "type" Aeson..= ("adaptive" :: Text.Text)+                        , "display" Aeson..= ("summarized" :: Text.Text)+                        ])+                    (Aeson.toJSON+                        (Messages.ThinkingAdaptiveWithDisplay Messages.ThinkingSummarized))++                HUnit.assertEqual+                    "Disabled thinking should use the disabled wire value"+                    (Aeson.object ["type" Aeson..= ("disabled" :: Text.Text)])+                    (Aeson.toJSON Messages.ThinkingDisabled)++                HUnit.assertEqual+                    "Tool removal should reference a declared tool"+                    (Aeson.object+                        [ "type" Aeson..= ("tool_removal" :: Text.Text)+                        , "tool" Aeson..= Aeson.object+                            [ "type" Aeson..= ("tool_reference" :: Text.Text)+                            , "name" Aeson..= ("get_weather" :: Text.Text)+                            ]+                        ])+                    (Aeson.toJSON Messages.Content_Tool_Removal+                        { Messages.tool = Messages.ToolChangeReference "get_weather"+                        , Messages.cache_control = Nothing+                        })++                let streamUsage = Aeson.object+                        [ "input_tokens" Aeson..= (24 :: Int)+                        , "output_tokens" Aeson..= (12 :: Int)+                        , "output_tokens_details" Aeson..= Aeson.object+                            [ "thinking_tokens" Aeson..= (8 :: Int)+                            ]+                        , "iterations" Aeson..=+                            ([ Aeson.object+                                [ "type" Aeson..= ("fallback_message" :: Text.Text)+                                , "model" Aeson..= ("claude-sonnet-5" :: Text.Text)+                                , "input_tokens" Aeson..= (24 :: Int)+                                , "output_tokens" Aeson..= (12 :: Int)+                                ]+                             ] :: [Aeson.Value])+                        ]++                case Aeson.fromJSON streamUsage of+                    Aeson.Success Messages.StreamUsage+                        { Messages.stream_output_tokens_details =+                            Just Messages.OutputTokensDetails+                                { Messages.thinking_tokens = 8+                                }+                        , Messages.stream_iterations = Just [_]+                        } -> pure ()+                    result -> HUnit.assertFailure $+                        "Failed to decode Claude 5 streaming usage: " <> show result++                let fallbackRequest = Messages._CreateMessage+                        { Messages.model = "claude-fable-5"+                        , Messages.messages =+                            [ Messages.Message+                                { Messages.role = Messages.User+                                , Messages.content = [Messages.textContent "Hello"]+                                , Messages.cache_control = Nothing+                                }+                            ]+                        , Messages.fallbacks = Just Messages.FallbacksDefault+                        }++                case Aeson.toJSON fallbackRequest of+                    Aeson.Object o ->+                        HUnit.assertEqual+                            "Default fallbacks should serialize as a string"+                            (Just (Aeson.String "default"))+                            (KeyMap.lookup "fallbacks" o)+                    _ -> HUnit.assertFailure "CreateMessage should serialize as an object"++                let refusalResponse = Aeson.object+                        [ "id" Aeson..= ("msg_refusal" :: Text.Text)+                        , "type" Aeson..= ("message" :: Text.Text)+                        , "role" Aeson..= ("assistant" :: Text.Text)+                        , "model" Aeson..= ("claude-fable-5" :: Text.Text)+                        , "content" Aeson..= ([] :: [Aeson.Value])+                        , "stop_reason" Aeson..= ("refusal" :: Text.Text)+                        , "stop_sequence" Aeson..= Aeson.Null+                        , "stop_details" Aeson..= Aeson.object+                            [ "type" Aeson..= ("refusal" :: Text.Text)+                            , "category" Aeson..= ("reasoning_extraction" :: Text.Text)+                            , "explanation" Aeson..= ("Request declined" :: Text.Text)+                            ]+                        , "usage" Aeson..= Aeson.object+                            [ "input_tokens" Aeson..= (12 :: Int)+                            , "output_tokens" Aeson..= (0 :: Int)+                            ]+                        ]++                case Aeson.fromJSON refusalResponse of+                    Aeson.Success Messages.MessageResponse+                        { Messages.stop_reason = Just Messages.Refusal+                        , Messages.stop_details = Just Messages.StopDetails+                            { Messages.category = Just "reasoning_extraction"+                            }+                        } -> pure ()+                    result -> HUnit.assertFailure $+                        "Failed to decode Claude 5 refusal response: " <> show result++                let fallbackBlock = Aeson.object+                        [ "type" Aeson..= ("fallback" :: Text.Text)+                        , "from" Aeson..= Aeson.object+                            [ "model" Aeson..= ("claude-fable-5" :: Text.Text)+                            ]+                        , "to" Aeson..= Aeson.object+                            [ "model" Aeson..= ("claude-sonnet-5" :: Text.Text)+                            ]+                        , "trigger" Aeson..= Aeson.object+                            [ "type" Aeson..= ("refusal" :: Text.Text)+                            , "category" Aeson..= ("cyber" :: Text.Text)+                            ]+                        ]++                case Aeson.fromJSON fallbackBlock of+                    Aeson.Success Messages.ContentBlock_Fallback+                        { Messages.from_ = Messages.FallbackInfo "claude-fable-5"+                        , Messages.to = Messages.FallbackInfo "claude-sonnet-5"+                        } ->+                            case Messages.contentBlockToContent+                                (Messages.ContentBlock_Fallback+                                    { Messages.from_ =+                                        Messages.FallbackInfo "claude-fable-5"+                                    , Messages.to =+                                        Messages.FallbackInfo "claude-sonnet-5"+                                    , Messages.trigger =+                                        Messages.FallbackTrigger (Just "cyber")+                                    }) of+                                Just Messages.Content_Fallback{} -> pure ()+                                _ -> HUnit.assertFailure+                                    "Fallback transition should be replayable"+                    result -> HUnit.assertFailure $+                        "Failed to decode fallback transition: " <> show result++    let claude5ModelTests =+            Tasty.testGroup "Claude 5 models" $+                flip map ["claude-sonnet-5", "claude-opus-5", "claude-fable-5"] $ \modelName ->+                    HUnit.testCase (Text.unpack modelName) do+                        Messages.MessageResponse+                            { Messages.model = responseModel+                            , Messages.content = responseContent+                            } <-+                                createMessage+                                    Messages._CreateMessage+                                        { Messages.model = modelName+                                        , Messages.messages =+                                            [ Messages.Message+                                                { Messages.role = Messages.User+                                                , Messages.content =+                                                    [ Messages.textContent+                                                        "Respond with exactly: ok"+                                                    ]+                                                , Messages.cache_control = Nothing+                                                }+                                            ]+                                        , Messages.max_tokens = 1024+                                        , Messages.output_config =+                                            Just (Messages.effortConfig "low")+                                        , Messages.thinking = Just+                                            (Messages.ThinkingAdaptiveWithDisplay+                                                Messages.ThinkingOmitted)+                                        }++                        HUnit.assertEqual+                            "Response should come from the requested model"+                            modelName+                            responseModel+                        HUnit.assertBool+                            "Response should have content"+                            (not (null responseContent))+     let promptCachingLiveTest =             HUnit.testCase "Create message - prompt caching" do                 let largeSystemText =@@ -877,6 +1055,8 @@             , strictToolUseTest             , adaptiveThinkingTest             , adaptiveThinkingStreamingTest+            , claude5WireTypesTest+            , claude5ModelTests             , promptCachingLiveTest             , inferenceGeoLiveTest             , compactionLiveTest