@@ -35,6 +35,7 @@ use crate::ProviderAdapter;
3535use crate :: ProviderCapabilities ;
3636use crate :: ProviderHttpOptions ;
3737use crate :: hosted_tools:: append_anthropic_hosted_tools;
38+ use crate :: http:: invalid_status_error;
3839use crate :: merge_extra_body;
3940
4041/// <https://platform.claude.com/docs/en/api/messages>
@@ -121,6 +122,8 @@ struct AnthropicInputMessage {
121122enum AnthropicInputContentBlock {
122123 #[ serde( rename = "text" ) ]
123124 Text { text : String } ,
125+ #[ serde( rename = "thinking" ) ]
126+ Thinking { thinking : String } ,
124127 #[ serde( rename = "tool_use" ) ]
125128 ToolUse {
126129 id : String ,
@@ -271,9 +274,22 @@ impl ModelProviderSDK for AnthropicProvider {
271274 . request_builder ( & body)
272275 . send ( )
273276 . await
274- . context ( "failed to send anthropic request" ) ?
275- . error_for_status ( )
276- . context ( "anthropic request failed" ) ?;
277+ . context ( "failed to send anthropic request" ) ?;
278+ let response = match response. error_for_status_ref ( ) {
279+ Ok ( _) => response,
280+ Err ( _) => {
281+ let status = response. status ( ) ;
282+ return Err ( invalid_status_error (
283+ "anthropic" ,
284+ & request. model ,
285+ "request" ,
286+ status,
287+ response,
288+ & body,
289+ )
290+ . await ) ;
291+ }
292+ } ;
277293
278294 let value: Value = response
279295 . json ( )
@@ -311,9 +327,24 @@ impl ModelProviderSDK for AnthropicProvider {
311327
312328 futures:: pin_mut!( event_source) ;
313329 while let Some ( event) = event_source. next( ) . await {
314- let event = event. map_err( |error| {
315- anyhow:: anyhow!( "anthropic stream error for model {}: {error}" , request. model)
316- } ) ?;
330+ let event = match event {
331+ Ok ( event) => event,
332+ Err ( reqwest_eventsource:: Error :: InvalidStatusCode ( status, response) ) => {
333+ Err ( invalid_status_error(
334+ "anthropic" ,
335+ & request. model,
336+ "stream" ,
337+ status,
338+ response,
339+ & body,
340+ )
341+ . await ) ?
342+ }
343+ Err ( error) => Err ( anyhow:: anyhow!(
344+ "anthropic stream error for model {}: {error}" ,
345+ request. model
346+ ) ) ?,
347+ } ;
317348
318349 match event {
319350 Event :: Open => { }
@@ -944,9 +975,9 @@ fn build_message(message: &RequestMessage) -> AnthropicInputMessage {
944975fn build_content_block ( block : & RequestContent ) -> AnthropicInputContentBlock {
945976 match block {
946977 RequestContent :: Text { text } => AnthropicInputContentBlock :: Text { text : text. clone ( ) } ,
947- RequestContent :: Reasoning { text } => {
948- AnthropicInputContentBlock :: Text { text : text . clone ( ) }
949- }
978+ RequestContent :: Reasoning { text } => AnthropicInputContentBlock :: Thinking {
979+ thinking : text. clone ( ) ,
980+ } ,
950981 RequestContent :: ToolUse { id, name, input } => AnthropicInputContentBlock :: ToolUse {
951982 id : id. clone ( ) ,
952983 name : name. clone ( ) ,
@@ -1186,6 +1217,58 @@ mod tests {
11861217 assert_eq ! ( body[ "tools" ] [ 0 ] [ "name" ] , json!( "get_weather" ) ) ;
11871218 }
11881219
1220+ #[ test]
1221+ fn build_request_serializes_reasoning_as_thinking_block ( ) {
1222+ let request = ModelRequest {
1223+ model : "deepseek-v4-flash" . to_string ( ) ,
1224+ system : None ,
1225+ messages : vec ! [ RequestMessage {
1226+ role: "assistant" . to_string( ) ,
1227+ content: vec![
1228+ RequestContent :: Reasoning {
1229+ text: "Need to inspect the file first." . to_string( ) ,
1230+ } ,
1231+ RequestContent :: Text {
1232+ text: "I'll read the README." . to_string( ) ,
1233+ } ,
1234+ RequestContent :: ToolUse {
1235+ id: "toolu_123" . to_string( ) ,
1236+ name: "read" . to_string( ) ,
1237+ input: json!( { "path" : "README.md" } ) ,
1238+ } ,
1239+ ] ,
1240+ } ] ,
1241+ max_tokens : 1024 ,
1242+ tools : None ,
1243+ sampling : SamplingControls :: default ( ) ,
1244+ thinking : Some ( "enabled" . to_string ( ) ) ,
1245+ reasoning_effort : None ,
1246+ extra_body : None ,
1247+ } ;
1248+
1249+ let body = build_request ( & request, true ) ;
1250+
1251+ assert_eq ! (
1252+ body[ "messages" ] [ 0 ] [ "content" ] ,
1253+ json!( [
1254+ {
1255+ "type" : "thinking" ,
1256+ "thinking" : "Need to inspect the file first."
1257+ } ,
1258+ {
1259+ "type" : "text" ,
1260+ "text" : "I'll read the README."
1261+ } ,
1262+ {
1263+ "type" : "tool_use" ,
1264+ "id" : "toolu_123" ,
1265+ "name" : "read" ,
1266+ "input" : { "path" : "README.md" }
1267+ }
1268+ ] )
1269+ ) ;
1270+ }
1271+
11891272 #[ test]
11901273 fn parse_response_extracts_text_tool_use_reasoning_and_usage ( ) {
11911274 let response = parse_response ( json ! ( {
0 commit comments