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 +14/−0
- README.md +2/−2
- claude.cabal +1/−1
- examples/claude-programmatic-tool-calling-example/Main.hs +2/−0
- examples/claude-tool-example/Main.hs +2/−0
- examples/claude-tool-search-example/Main.hs +2/−0
- src/Claude/V1/Messages.hs +285/−16
- tasty/Main.hs +180/−0
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