@@ -1356,11 +1356,16 @@ fn completion_error_from_response_error(
13561356 error : & ResponseError ,
13571357 provider : language_model_core:: LanguageModelProviderName ,
13581358) -> LanguageModelCompletionError {
1359- let category = response_error_category ( error. code . as_deref ( ) , None , & error. message ) ;
1359+ let category = response_error_category (
1360+ error. code . as_deref ( ) ,
1361+ error. error_type . as_deref ( ) ,
1362+ None ,
1363+ & error. message ,
1364+ ) ;
13601365 LanguageModelCompletionError :: from_provider_response (
13611366 provider,
13621367 None ,
1363- error. code . clone ( ) ,
1368+ error. code . clone ( ) . or_else ( || error . error_type . clone ( ) ) ,
13641369 error. message . clone ( ) ,
13651370 None ,
13661371 category,
@@ -1369,41 +1374,52 @@ fn completion_error_from_response_error(
13691374
13701375pub ( crate ) fn response_error_category (
13711376 code : Option < & str > ,
1377+ error_type : Option < & str > ,
13721378 status : Option < StatusCode > ,
13731379 message : & str ,
13741380) -> ProviderErrorCategory {
1375- match code {
1376- Some ( "context_length_exceeded" | "request_too_large" ) => {
1381+ code. and_then ( response_error_category_from_discriminator)
1382+ . or_else ( || error_type. and_then ( response_error_category_from_discriminator) )
1383+ . unwrap_or_else ( || {
1384+ status
1385+ . map ( |status| ProviderErrorCategory :: from_http_status ( status, message) )
1386+ . unwrap_or ( ProviderErrorCategory :: Other )
1387+ } )
1388+ }
1389+
1390+ fn response_error_category_from_discriminator (
1391+ discriminator : & str ,
1392+ ) -> Option < ProviderErrorCategory > {
1393+ let category = match discriminator {
1394+ "context_length_exceeded" | "request_too_large" => {
13771395 ProviderErrorCategory :: PromptTooLarge { tokens : None }
13781396 }
1379- Some ( "invalid_encrypted_content" ) => ProviderErrorCategory :: InvalidEncryptedContent ,
1380- Some ( "invalid_request_error" ) => ProviderErrorCategory :: InvalidRequest ,
1381- Some ( "authentication_error" ) => ProviderErrorCategory :: Authentication ,
1382- Some (
1383- "billing_error"
1384- | "payment_required_error"
1385- | "credit_balance_exhausted"
1386- | "insufficient_quota"
1387- | "organization_spend_limit_exceeded"
1388- | "project_spend_limit_exceeded"
1389- | "organization_usage_limit_exceeded" ,
1390- ) => ProviderErrorCategory :: PaymentRequired ,
1391- Some ( "permission_error" ) => ProviderErrorCategory :: Permission ,
1392- Some ( "cyber_policy" | "invalid_prompt" ) => ProviderErrorCategory :: ContentPolicy ,
1393- Some ( "not_found_error" ) => ProviderErrorCategory :: EndpointNotFound ,
1394- Some ( "conflict_error" ) => ProviderErrorCategory :: Conflict ,
1395- Some ( "rate_limit_error" | "rate_limit_exceeded" ) => ProviderErrorCategory :: RateLimit ,
1396- Some ( "timeout_error" | "request_timed_out" ) => ProviderErrorCategory :: Timeout ,
1397- Some ( "api_error" | "internal_server_error" | "server_error" ) => {
1397+ "invalid_encrypted_content" => ProviderErrorCategory :: InvalidEncryptedContent ,
1398+ "invalid_request_error" => ProviderErrorCategory :: InvalidRequest ,
1399+ "authentication_error" => ProviderErrorCategory :: Authentication ,
1400+ "billing_error"
1401+ | "payment_required_error"
1402+ | "credit_balance_exhausted"
1403+ | "insufficient_quota"
1404+ | "organization_spend_limit_exceeded"
1405+ | "project_spend_limit_exceeded"
1406+ | "organization_usage_limit_exceeded"
1407+ | "usage_limit_reached" => ProviderErrorCategory :: PaymentRequired ,
1408+ "permission_error" => ProviderErrorCategory :: Permission ,
1409+ "cyber_policy" | "invalid_prompt" => ProviderErrorCategory :: ContentPolicy ,
1410+ "not_found_error" => ProviderErrorCategory :: EndpointNotFound ,
1411+ "conflict_error" => ProviderErrorCategory :: Conflict ,
1412+ "rate_limit_error" | "rate_limit_exceeded" => ProviderErrorCategory :: RateLimit ,
1413+ "timeout_error" | "request_timed_out" => ProviderErrorCategory :: Timeout ,
1414+ "api_error" | "internal_server_error" | "server_error" => {
13981415 ProviderErrorCategory :: InternalServer
13991416 }
1400- Some ( "overloaded_error" | "server_is_overloaded" | "slow_down" ) => {
1417+ "overloaded_error" | "server_is_overloaded" | "slow_down" => {
14011418 ProviderErrorCategory :: Overloaded
14021419 }
1403- Some ( _) | None => status
1404- . map ( |status| ProviderErrorCategory :: from_http_status ( status, message) )
1405- . unwrap_or ( ProviderErrorCategory :: Other ) ,
1406- }
1420+ _ => return None ,
1421+ } ;
1422+ Some ( category)
14071423}
14081424
14091425fn response_error_message ( error : & ResponseError ) -> String {
@@ -2855,6 +2871,7 @@ mod tests {
28552871 status : Some ( "failed" . into ( ) ) ,
28562872 error : Some ( ResponseError {
28572873 code : Some ( "server_error" . into ( ) ) ,
2874+ error_type : None ,
28582875 message : "The model failed to generate a response." . into ( ) ,
28592876 param : None ,
28602877 } ) ,
@@ -2947,6 +2964,35 @@ mod tests {
29472964 ) ) ;
29482965 }
29492966
2967+ #[ test]
2968+ fn responses_stream_preserves_and_classifies_type_without_code ( ) {
2969+ let event = serde_json:: from_value :: < ResponsesStreamEvent > ( json ! ( {
2970+ "type" : "error" ,
2971+ "error" : {
2972+ "type" : "usage_limit_reached" ,
2973+ "message" : "The usage limit has been reached" ,
2974+ "plan_type" : "plus"
2975+ }
2976+ } ) )
2977+ . expect ( "nested usage limit error event" ) ;
2978+
2979+ let mut mapper = OpenAiResponseEventMapper :: new ( OPEN_AI_PROVIDER_ID ) ;
2980+ let mapped = mapper. map_event ( event) ;
2981+
2982+ assert_eq ! ( mapped. len( ) , 1 ) ;
2983+ let error = mapped. into_iter ( ) . next ( ) . unwrap ( ) . unwrap_err ( ) ;
2984+ assert ! ( matches!(
2985+ error,
2986+ LanguageModelCompletionError :: ProviderRejection {
2987+ code: Some ( code) ,
2988+ message,
2989+ category: ProviderErrorCategory :: PaymentRequired ,
2990+ ..
2991+ } if code == "usage_limit_reached"
2992+ && message == "The usage limit has been reached"
2993+ ) ) ;
2994+ }
2995+
29502996 #[ test]
29512997 fn responses_stream_maps_billing_codes_to_payment_required ( ) {
29522998 for code in [
@@ -2959,17 +3005,30 @@ mod tests {
29593005 "organization_usage_limit_exceeded" ,
29603006 ] {
29613007 assert_eq ! (
2962- response_error_category( Some ( code) , None , "" ) ,
3008+ response_error_category( Some ( code) , None , None , "" ) ,
29633009 ProviderErrorCategory :: PaymentRequired ,
29643010 "{code}"
29653011 ) ;
29663012 }
29673013 }
29683014
3015+ #[ test]
3016+ fn responses_stream_uses_known_type_when_code_is_unknown ( ) {
3017+ assert_eq ! (
3018+ response_error_category(
3019+ Some ( "provider_specific_code" ) ,
3020+ Some ( "usage_limit_reached" ) ,
3021+ Some ( StatusCode :: TOO_MANY_REQUESTS ) ,
3022+ "" ,
3023+ ) ,
3024+ ProviderErrorCategory :: PaymentRequired
3025+ ) ;
3026+ }
3027+
29693028 #[ test]
29703029 fn responses_stream_maps_invalid_prompt_to_content_policy ( ) {
29713030 assert_eq ! (
2972- response_error_category( Some ( "invalid_prompt" ) , None , "" ) ,
3031+ response_error_category( Some ( "invalid_prompt" ) , None , None , "" ) ,
29733032 ProviderErrorCategory :: ContentPolicy
29743033 ) ;
29753034 }
@@ -2978,7 +3037,7 @@ mod tests {
29783037 fn responses_stream_maps_overload_codes_to_overloaded ( ) {
29793038 for code in [ "overloaded_error" , "server_is_overloaded" , "slow_down" ] {
29803039 assert_eq ! (
2981- response_error_category( Some ( code) , None , "" ) ,
3040+ response_error_category( Some ( code) , None , None , "" ) ,
29823041 ProviderErrorCategory :: Overloaded ,
29833042 "{code}"
29843043 ) ;
@@ -3021,6 +3080,7 @@ mod tests {
30213080 status : Some ( "failed" . into ( ) ) ,
30223081 error : Some ( ResponseError {
30233082 code : Some ( "context_length_exceeded" . into ( ) ) ,
3083+ error_type : None ,
30243084 message : "Your input exceeds the context window of this model." . into ( ) ,
30253085 param : Some ( "input" . into ( ) ) ,
30263086 } ) ,
0 commit comments