@@ -22,7 +22,6 @@ use std::collections::{
2222 HashSet ,
2323 VecDeque ,
2424} ;
25- use std:: error:: Error ;
2625use std:: io:: {
2726 IsTerminal ,
2827 Read ,
@@ -66,7 +65,6 @@ use crossterm::{
6665 terminal,
6766} ;
6867use eyre:: {
69- Chain ,
7068 ErrReport ,
7169 Result ,
7270 bail,
@@ -158,8 +156,10 @@ use crate::mcp_client::{
158156use crate :: platform:: Context ;
159157use crate :: telemetry:: core:: ToolUseEventBuilder ;
160158use crate :: telemetry:: {
159+ ReasonCode ,
161160 TelemetryResult ,
162161 TelemetryThread ,
162+ get_error_reason,
163163} ;
164164
165165#[ derive( Debug , Clone , PartialEq , Eq , Default , Args ) ]
@@ -494,6 +494,21 @@ pub enum ChatError {
494494 GetPromptError ( #[ from] GetPromptError ) ,
495495}
496496
497+ impl ReasonCode for ChatError {
498+ fn reason_code ( & self ) -> String {
499+ match self {
500+ ChatError :: Client ( e) => e. reason_code ( ) ,
501+ ChatError :: ResponseStream ( e) => e. reason_code ( ) ,
502+ ChatError :: Std ( _) => "StdIoError" . to_string ( ) ,
503+ ChatError :: Readline ( _) => "ReadlineError" . to_string ( ) ,
504+ ChatError :: Custom ( _) => "GenericError" . to_string ( ) ,
505+ ChatError :: Interrupted { .. } => "Interrupted" . to_string ( ) ,
506+ ChatError :: NonInteractiveToolApproval => "NonInteractiveToolApprovalError" . to_string ( ) ,
507+ ChatError :: GetPromptError ( _) => "GetPromptError" . to_string ( ) ,
508+ }
509+ }
510+ }
511+
497512pub struct ChatContext {
498513 ctx : Arc < Context > ,
499514 /// The [Write] destination for printing conversation text.
@@ -869,7 +884,7 @@ impl ChatContext {
869884 ChatState :: HandleResponseStream ( response) => tokio:: select! {
870885 res = self . handle_response( database, telemetry, response) => res,
871886 Ok ( _) = ctrl_c_stream => {
872- self . send_chat_telemetry( database, telemetry, None , TelemetryResult :: Cancelled , None ) . await ;
887+ self . send_chat_telemetry( database, telemetry, None , TelemetryResult :: Cancelled , None , None ) . await ;
873888
874889 Err ( ChatError :: Interrupted { tool_uses: None } )
875890 }
@@ -894,7 +909,8 @@ impl ChatContext {
894909 match result {
895910 Ok ( state) => Ok ( state) ,
896911 Err ( e) => {
897- self . send_error_telemetry ( database, telemetry, get_error_string ( & e) )
912+ let ( reason, reason_desc) = get_error_reason ( & e) ;
913+ self . send_error_telemetry ( database, telemetry, reason, Some ( reason_desc) )
898914 . await ;
899915
900916 macro_rules! print_err {
@@ -1089,12 +1105,14 @@ impl ChatContext {
10891105 let response = match response {
10901106 Ok ( res) => res,
10911107 Err ( e) => {
1108+ let ( reason, reason_desc) = get_error_reason ( & e) ;
10921109 self . send_chat_telemetry (
10931110 database,
10941111 telemetry,
10951112 None ,
10961113 TelemetryResult :: Failed ,
1097- Some ( get_error_string ( & e) ) ,
1114+ Some ( reason) ,
1115+ Some ( reason_desc) ,
10981116 )
10991117 . await ;
11001118 match e {
@@ -1137,12 +1155,14 @@ impl ChatContext {
11371155 if let Some ( request_id) = & err. request_id {
11381156 self . failed_request_ids . push ( request_id. clone ( ) ) ;
11391157 } ;
1158+ let ( reason, reason_desc) = get_error_reason ( & err) ;
11401159 self . send_chat_telemetry (
11411160 database,
11421161 telemetry,
11431162 err. request_id . clone ( ) ,
11441163 TelemetryResult :: Failed ,
1145- Some ( get_error_string ( & err) ) ,
1164+ Some ( reason) ,
1165+ Some ( reason_desc) ,
11461166 )
11471167 . await ;
11481168 return Err ( err. into ( ) ) ;
@@ -1160,7 +1180,8 @@ impl ChatContext {
11601180 cursor:: Show
11611181 ) ?;
11621182 }
1163- self . send_chat_telemetry ( database, telemetry, request_id, TelemetryResult :: Succeeded , None )
1183+
1184+ self . send_chat_telemetry ( database, telemetry, request_id, TelemetryResult :: Succeeded , None , None )
11641185 . await ;
11651186
11661187 self . conversation_state . replace_history_with_summary ( summary. clone ( ) ) ;
@@ -3266,12 +3287,14 @@ impl ChatContext {
32663287 self . failed_request_ids . push ( request_id. clone ( ) ) ;
32673288 } ;
32683289
3290+ let ( reason, reason_desc) = get_error_reason ( & recv_error) ;
32693291 self . send_chat_telemetry (
32703292 database,
32713293 telemetry,
32723294 recv_error. request_id . clone ( ) ,
32733295 TelemetryResult :: Failed ,
3274- Some ( get_error_string ( & recv_error) ) ,
3296+ Some ( reason) ,
3297+ Some ( reason_desc) ,
32753298 )
32763299 . await ;
32773300
@@ -3383,7 +3406,7 @@ impl ChatContext {
33833406 }
33843407
33853408 if ended {
3386- self . send_chat_telemetry ( database, telemetry, request_id, TelemetryResult :: Succeeded , None )
3409+ self . send_chat_telemetry ( database, telemetry, request_id, TelemetryResult :: Succeeded , None , None )
33873410 . await ;
33883411
33893412 if self . interactive
@@ -3680,6 +3703,7 @@ impl ChatContext {
36803703 request_id : Option < String > ,
36813704 result : TelemetryResult ,
36823705 reason : Option < String > ,
3706+ reason_desc : Option < String > ,
36833707 ) {
36843708 telemetry
36853709 . send_chat_added_message (
@@ -3690,19 +3714,27 @@ impl ChatContext {
36903714 self . conversation_state . context_message_length ( ) ,
36913715 result,
36923716 reason,
3717+ reason_desc,
36933718 )
36943719 . await
36953720 . ok ( ) ;
36963721 }
36973722
3698- async fn send_error_telemetry ( & self , database : & Database , telemetry : & TelemetryThread , reason : String ) {
3723+ async fn send_error_telemetry (
3724+ & self ,
3725+ database : & Database ,
3726+ telemetry : & TelemetryThread ,
3727+ reason : String ,
3728+ reason_desc : Option < String > ,
3729+ ) {
36993730 telemetry
37003731 . send_response_error (
37013732 database,
37023733 self . conversation_state . conversation_id ( ) . to_owned ( ) ,
37033734 self . conversation_state . context_message_length ( ) ,
37043735 TelemetryResult :: Failed ,
37053736 Some ( reason) ,
3737+ reason_desc,
37063738 )
37073739 . await
37083740 . ok ( ) ;
@@ -3805,22 +3837,6 @@ fn create_stream(model_responses: serde_json::Value) -> StreamingClient {
38053837 StreamingClient :: mock ( mock)
38063838}
38073839
3808- /// Returns surface error + root cause as a string. If there is only one error
3809- /// in the chain, return that as a string.
3810- fn get_error_string ( error : & ( dyn Error + ' static ) ) -> String {
3811- let err_chain = Chain :: new ( error) ;
3812-
3813- if err_chain. len ( ) > 1 {
3814- format ! (
3815- "'{}' caused by: {}" ,
3816- error,
3817- err_chain. last( ) . map_or( "UNKNOWN" . to_string( ) , |e| e. to_string( ) )
3818- )
3819- } else {
3820- error. to_string ( )
3821- }
3822- }
3823-
38243840#[ cfg( test) ]
38253841mod tests {
38263842 use super :: * ;
0 commit comments