diff --git a/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-stable.json.zst b/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-stable.json.zst index 7e34c1d753..e9b07c3ca4 100644 Binary files a/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-stable.json.zst and b/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-stable.json.zst differ diff --git a/codex-rs/app-server-protocol/src/protocol/v2/shared.rs b/codex-rs/app-server-protocol/src/protocol/v2/shared.rs index 0e045e61e4..a1ba74b0e4 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/shared.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/shared.rs @@ -142,6 +142,7 @@ impl From for CodexErrorInfo { CoreCodexErrorInfo::InternalServerError => CodexErrorInfo::InternalServerError, CoreCodexErrorInfo::Unauthorized => CodexErrorInfo::Unauthorized, CoreCodexErrorInfo::BadRequest => CodexErrorInfo::BadRequest, + CoreCodexErrorInfo::InvalidPrompt => CodexErrorInfo::Other, CoreCodexErrorInfo::ThreadRollbackFailed => CodexErrorInfo::ThreadRollbackFailed, CoreCodexErrorInfo::SandboxError => CodexErrorInfo::SandboxError, CoreCodexErrorInfo::ResponseStreamDisconnected { http_status_code } => { diff --git a/codex-rs/app-server-protocol/src/protocol/v2/tests.rs b/codex-rs/app-server-protocol/src/protocol/v2/tests.rs index 99f7003fe6..7b8f7913e7 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/tests.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/tests.rs @@ -4729,6 +4729,7 @@ fn core_error_info_converts_to_camel_case() { for (core, expected) in [ (CoreCodexErrorInfo::CyberPolicy, json!("cyberPolicy")), (CoreCodexErrorInfo::BioPolicy, json!("other")), + (CoreCodexErrorInfo::InvalidPrompt, json!("other")), ( CoreCodexErrorInfo::RateLimitExceeded, json!("rateLimitExceeded"), diff --git a/codex-rs/cli/src/doctor.rs b/codex-rs/cli/src/doctor.rs index 444d6e4929..6cc337a881 100644 --- a/codex-rs/cli/src/doctor.rs +++ b/codex-rs/cli/src/doctor.rs @@ -2436,6 +2436,7 @@ fn websocket_error_detail(err: &ApiError) -> String { | ApiError::RateLimitExceeded { .. } | ApiError::RateLimit(_) | ApiError::InvalidRequest { .. } + | ApiError::InvalidPrompt { .. } | ApiError::CyberPolicy { .. } | ApiError::BioPolicy { .. } | ApiError::MisalignmentPolicyViolation { .. } diff --git a/codex-rs/codex-api/src/api_bridge.rs b/codex-rs/codex-api/src/api_bridge.rs index d6ff0270ba..15c9cb9245 100644 --- a/codex-rs/codex-api/src/api_bridge.rs +++ b/codex-rs/codex-api/src/api_bridge.rs @@ -53,6 +53,9 @@ pub fn map_api_error(err: ApiError) -> CodexErr { }) } ApiError::InvalidRequest { message } => CodexErr::InvalidRequest(message), + ApiError::InvalidPrompt { message } => { + CodexErr::new(CodexErrorDetails::InvalidPrompt { message }) + } ApiError::CyberPolicy { message } => { CodexErr::new(CodexErrorDetails::CyberPolicy { message }) } @@ -118,11 +121,16 @@ pub fn map_api_error(err: ApiError) -> CodexErr { if status == http::StatusCode::BAD_REQUEST { if let Ok(parsed) = serde_json::from_str::(&body_text) && let Some(error) = parsed.get("error") - && let Some(code @ (CYBER_POLICY_ERROR_CODE | BIO_POLICY_ERROR_CODE)) = - error.get("code").and_then(Value::as_str) + && let Some( + code @ (CYBER_POLICY_ERROR_CODE + | BIO_POLICY_ERROR_CODE + | INVALID_PROMPT_ERROR_CODE), + ) = error.get("code").and_then(Value::as_str) { let fallback_message = if code == BIO_POLICY_ERROR_CODE { BIO_POLICY_FALLBACK_MESSAGE + } else if code == INVALID_PROMPT_ERROR_CODE { + INVALID_PROMPT_FALLBACK_MESSAGE } else { CYBER_POLICY_FALLBACK_MESSAGE }; @@ -134,6 +142,8 @@ pub fn map_api_error(err: ApiError) -> CodexErr { .unwrap_or_else(|| fallback_message.to_string()); if code == BIO_POLICY_ERROR_CODE { CodexErr::new(CodexErrorDetails::BioPolicy { message }) + } else if code == INVALID_PROMPT_ERROR_CODE { + CodexErr::new(CodexErrorDetails::InvalidPrompt { message }) } else { CodexErr::new(CodexErrorDetails::CyberPolicy { message }) } @@ -234,6 +244,8 @@ const OAI_REQUEST_ID_HEADER: &str = "x-oai-request-id"; const CF_RAY_HEADER: &str = "cf-ray"; const X_OPENAI_AUTHORIZATION_ERROR_HEADER: &str = "x-openai-authorization-error"; const X_ERROR_JSON_HEADER: &str = "x-error-json"; +const INVALID_PROMPT_ERROR_CODE: &str = "invalid_prompt"; +const INVALID_PROMPT_FALLBACK_MESSAGE: &str = "Invalid request."; const CYBER_POLICY_ERROR_CODE: &str = "cyber_policy"; const CYBER_POLICY_FALLBACK_MESSAGE: &str = "This request has been flagged for possible cybersecurity risk."; diff --git a/codex-rs/codex-api/src/api_bridge_tests.rs b/codex-rs/codex-api/src/api_bridge_tests.rs index 595232d2d9..660262ea97 100644 --- a/codex-rs/codex-api/src/api_bridge_tests.rs +++ b/codex-rs/codex-api/src/api_bridge_tests.rs @@ -185,48 +185,72 @@ fn map_api_error_uses_cyber_policy_fallback_for_missing_message() { } #[test] -fn map_api_error_preserves_bio_policy() { - let err = map_api_error(ApiError::BioPolicy { - message: "This request was blocked by bio policy.".to_string(), - }); - assert_eq!(err.to_codex_protocol_error(), CodexErrorInfo::BioPolicy); - assert_eq!(err.to_string(), "This request was blocked by bio policy."); - assert_eq!(err.retry_delay(/*retry_count*/ 1), None); +fn map_api_error_preserves_typed_errors() { + let message = "This request was rejected."; + for (error, expected_info) in [ + ( + ApiError::BioPolicy { + message: message.to_string(), + }, + CodexErrorInfo::BioPolicy, + ), + ( + ApiError::InvalidPrompt { + message: message.to_string(), + }, + CodexErrorInfo::InvalidPrompt, + ), + ] { + let err = map_api_error(error); + assert_eq!(err.to_codex_protocol_error(), expected_info); + assert_eq!(err.to_string(), message); + assert_eq!(err.retry_delay(/*retry_count*/ 1), None); + } } #[test] -fn map_api_error_maps_http_and_wrapped_websocket_bio_policy() { - for wrapped in [false, true] { - for message in [ - Some("This request was blocked by bio policy."), - None, - Some(""), - Some(" "), - ] { - let mut body = serde_json::json!({"error": {"code": "bio_policy"}}); - if let Some(message) = message { - body["error"]["message"] = serde_json::json!(message); - } - if wrapped { - body["type"] = serde_json::json!("error"); - body["status"] = serde_json::json!(400); - } - let err = map_api_error(ApiError::Transport(TransportError::Http { - status: http::StatusCode::BAD_REQUEST, - url: None, - headers: None, - body: Some(body.to_string()), - })); +fn map_api_error_maps_http_and_wrapped_websocket_typed_errors() { + for (code, expected_info, fallback) in [ + ( + "bio_policy", + CodexErrorInfo::BioPolicy, + "This content was flagged for possible biological risk.", + ), + ( + "invalid_prompt", + CodexErrorInfo::InvalidPrompt, + "Invalid request.", + ), + ] { + for wrapped in [false, true] { + for (message, expected) in [ + ( + Some("This request was rejected."), + "This request was rejected.", + ), + (None, fallback), + (Some(""), fallback), + (Some(" "), fallback), + ] { + let mut body = serde_json::json!({"error": {"code": code}}); + if let Some(message) = message { + body["error"]["message"] = serde_json::json!(message); + } + if wrapped { + body["type"] = serde_json::json!("error"); + body["status"] = serde_json::json!(400); + } + let err = map_api_error(ApiError::Transport(TransportError::Http { + status: http::StatusCode::BAD_REQUEST, + url: None, + headers: None, + body: Some(body.to_string()), + })); - let expected = message - .filter(|message| !message.trim().is_empty()) - .unwrap_or("This content was flagged for possible biological risk."); - let CodexErrorDetails::BioPolicy { message } = err.details() else { - panic!("expected CodexErrorDetails::BioPolicy, got {err:?}"); - }; - assert_eq!(message, expected); - assert_eq!(err.to_codex_protocol_error(), CodexErrorInfo::BioPolicy); - assert_eq!(err.retry_delay(/*retry_count*/ 1), None); + assert_eq!(err.to_string(), expected); + assert_eq!(err.to_codex_protocol_error(), expected_info); + assert_eq!(err.retry_delay(/*retry_count*/ 1), None); + } } } } @@ -359,25 +383,27 @@ fn map_api_error_preserves_misalignment_details_from_wrapped_websocket_error() { } #[test] -fn map_api_error_keeps_unknown_400_errors_generic() { - let body = serde_json::json!({ - "error": { - "message": "Some other bad request.", - "code": "some_other_policy" - } - }) - .to_string(); - let err = map_api_error(ApiError::Transport(TransportError::Http { - status: http::StatusCode::BAD_REQUEST, - url: Some("http://example.com/v1/responses".to_string()), - headers: None, - body: Some(body.clone()), - })); +fn map_api_error_keeps_other_400_errors_generic() { + for code in ["invalid_request", "some_other_policy"] { + let body = serde_json::json!({ + "error": { + "message": "Some other bad request.", + "code": code + } + }) + .to_string(); + let err = map_api_error(ApiError::Transport(TransportError::Http { + status: http::StatusCode::BAD_REQUEST, + url: Some("http://example.com/v1/responses".to_string()), + headers: None, + body: Some(body.clone()), + })); - let CodexErrorDetails::InvalidRequest(message) = err.details() else { - panic!("expected CodexErrorDetails::InvalidRequest, got {err:?}"); - }; - assert_eq!(message, &body); + let CodexErrorDetails::InvalidRequest(message) = err.details() else { + panic!("expected CodexErrorDetails::InvalidRequest, got {err:?}"); + }; + assert_eq!(message, &body); + } } #[test] diff --git a/codex-rs/codex-api/src/error.rs b/codex-rs/codex-api/src/error.rs index 9c282ba803..a353a59b8b 100644 --- a/codex-rs/codex-api/src/error.rs +++ b/codex-rs/codex-api/src/error.rs @@ -33,6 +33,8 @@ pub enum ApiError { RateLimit(String), #[error("invalid request: {message}")] InvalidRequest { message: String }, + #[error("invalid prompt: {message}")] + InvalidPrompt { message: String }, #[error("cyber policy: {message}")] CyberPolicy { message: String }, #[error("bio policy: {message}")] diff --git a/codex-rs/codex-api/src/sse/responses.rs b/codex-rs/codex-api/src/sse/responses.rs index 3f867d5d0b..3baf9346e9 100644 --- a/codex-rs/codex-api/src/sse/responses.rs +++ b/codex-rs/codex-api/src/sse/responses.rs @@ -455,7 +455,7 @@ pub fn process_responses_event( let message = error .message .unwrap_or_else(|| "Invalid request.".to_string()); - response_error = ApiError::InvalidRequest { message }; + response_error = ApiError::InvalidPrompt { message }; } else if is_server_overloaded_error(&error) { response_error = ApiError::ServerOverloaded; } else { @@ -1404,7 +1404,7 @@ mod tests { assert_eq!(events.len(), 1); match (code, &events[0]) { - ("invalid_prompt", Err(ApiError::InvalidRequest { message })) + ("invalid_prompt", Err(ApiError::InvalidPrompt { message })) | ("bio_policy", Err(ApiError::BioPolicy { message })) => { assert_eq!(message, expected_message); } @@ -1414,23 +1414,35 @@ mod tests { } #[tokio::test] - async fn bio_policy_error_uses_fallback_for_missing_or_blank_message() { - for message in [None, Some(""), Some(" ")] { - let mut event = json!({ - "type": "response.failed", - "response": { "error": { "code": "bio_policy" } }, - }); - if let Some(message) = message { - event["response"]["error"]["message"] = json!(message); - } - let sse = format!("event: response.failed\ndata: {event}\n\n"); - let events = collect_events(&[sse.as_bytes()]).await; - match events.as_slice() { - [Err(ApiError::BioPolicy { message })] => assert_eq!( - message, - "This content was flagged for possible biological risk." - ), - other => panic!("unexpected events: {other:?}"), + async fn typed_errors_handle_missing_or_blank_message() { + for (code, fallback) in [ + ( + "bio_policy", + "This content was flagged for possible biological risk.", + ), + ("invalid_prompt", "Invalid request."), + ] { + for message in [None, Some(""), Some(" ")] { + let mut event = json!({ + "type": "response.failed", + "response": { "error": { "code": code } }, + }); + if let Some(message) = message { + event["response"]["error"]["message"] = json!(message); + } + let expected = match (code, message) { + ("invalid_prompt", Some(message)) => message, + _ => fallback, + }; + let sse = format!("event: response.failed\ndata: {event}\n\n"); + let events = collect_events(&[sse.as_bytes()]).await; + match (code, events.as_slice()) { + ("bio_policy", [Err(ApiError::BioPolicy { message })]) + | ("invalid_prompt", [Err(ApiError::InvalidPrompt { message })]) => { + assert_eq!(message, expected); + } + other => panic!("unexpected events: {other:?}"), + } } } } diff --git a/codex-rs/core/tests/suite/safety_check_downgrade.rs b/codex-rs/core/tests/suite/safety_check_downgrade.rs index efd37e51b6..d571575f8b 100644 --- a/codex-rs/core/tests/suite/safety_check_downgrade.rs +++ b/codex-rs/core/tests/suite/safety_check_downgrade.rs @@ -39,6 +39,7 @@ const TRUSTED_ACCESS_FOR_CYBER_VERIFICATION: &str = "trusted_access_for_cyber"; const CYBER_POLICY_MESSAGE: &str = "This request has been flagged for potentially high-risk cyber activity."; const BIO_POLICY_MESSAGE: &str = "This request has been flagged for possible biological risk."; +const INVALID_PROMPT_MESSAGE: &str = "This prompt was rejected."; fn disabled_text_turn(test: &TestCodex, text: &str) -> TurnInputRequest { let (sandbox_policy, permission_profile) = @@ -108,48 +109,59 @@ async fn openai_model_header_mismatch_emits_warning_event() -> Result<()> { #[test_case::test_case("cyber_policy", CYBER_POLICY_MESSAGE, CodexErrorInfo::CyberPolicy; "cyber")] #[test_case::test_case("bio_policy", BIO_POLICY_MESSAGE, CodexErrorInfo::BioPolicy; "bio")] +#[test_case::test_case("invalid_prompt", INVALID_PROMPT_MESSAGE, CodexErrorInfo::InvalidPrompt; "invalid_prompt")] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn policy_response_emits_typed_error_without_retry( +async fn error_response_emits_typed_error_without_retry( code: &str, message: &str, error_info: CodexErrorInfo, ) -> Result<()> { skip_if_no_network!(Ok(())); - let server = start_mock_server().await; - let response = ResponseTemplate::new(400).set_body_json(serde_json::json!({ - "error": { - "message": message, - "type": "invalid_request", - "param": null, - "code": code - } - })); - let mock = mount_response_once(&server, response).await; + let error = serde_json::json!({ + "message": message, + "type": "invalid_request", + "param": null, + "code": code + }); + for response in [ + ResponseTemplate::new(400).set_body_json(serde_json::json!({"error": error})), + sse_response(sse(vec![serde_json::json!({ + "type": "response.failed", + "response": {"error": error} + })])), + ] { + let server = start_mock_server().await; + let mock = mount_response_once(&server, response).await; - let mut builder = test_codex().with_model(REQUESTED_MODEL); - let test = builder.build_with_auto_env(&server).await?; + let mut builder = test_codex().with_model(REQUESTED_MODEL); + let test = builder.build_with_auto_env(&server).await?; - test.codex - .start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text { - text: "trigger policy error".to_string(), - text_elements: Vec::new(), - }])) - .await?; + test.codex + .start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text { + text: "trigger error".to_string(), + text_elements: Vec::new(), + }])) + .await?; - let error = wait_for_event(&test.codex, |event| matches!(event, EventMsg::Error(_))).await; - let EventMsg::Error(error) = error else { - panic!("expected error event"); - }; - assert_eq!(error.message, message); - assert_eq!(error.codex_error_info, Some(error_info)); + let error = wait_for_event(&test.codex, |event| matches!(event, EventMsg::Error(_))).await; + let EventMsg::Error(error) = error else { + panic!("expected error event"); + }; + assert_eq!(error.message, message); + assert_eq!(error.codex_error_info.as_ref(), Some(&error_info)); + assert_eq!( + serde_json::to_value(&error.codex_error_info)?, + serde_json::json!(code) + ); - let _ = wait_for_event(&test.codex, |event| { - matches!(event, EventMsg::TurnComplete(_)) - }) - .await; + let _ = wait_for_event(&test.codex, |event| { + matches!(event, EventMsg::TurnComplete(_)) + }) + .await; - mock.single_request(); + mock.single_request(); + } Ok(()) } diff --git a/codex-rs/ext/guardian-reviewer/src/retry.rs b/codex-rs/ext/guardian-reviewer/src/retry.rs index e1af53f466..0c485ac3bc 100644 --- a/codex-rs/ext/guardian-reviewer/src/retry.rs +++ b/codex-rs/ext/guardian-reviewer/src/retry.rs @@ -104,6 +104,7 @@ fn should_retry_guardian_review(outcome: &GuardianReviewOutcome) -> bool { | CodexErrorInfo::MisalignmentPolicyViolation | CodexErrorInfo::Unauthorized | CodexErrorInfo::BadRequest + | CodexErrorInfo::InvalidPrompt | CodexErrorInfo::SandboxError | CodexErrorInfo::ActiveTurnNotSteerable { .. } | CodexErrorInfo::ThreadRollbackFailed diff --git a/codex-rs/ext/guardian-reviewer/src/retry_tests.rs b/codex-rs/ext/guardian-reviewer/src/retry_tests.rs index cdc80f2490..ffb053031f 100644 --- a/codex-rs/ext/guardian-reviewer/src/retry_tests.rs +++ b/codex-rs/ext/guardian-reviewer/src/retry_tests.rs @@ -99,6 +99,13 @@ fn guardian_review_retry_only_retries_transient_session_and_parse_errors() { )), false, ), + ( + GuardianReviewOutcome::Error(GuardianReviewError::session_with_error_info( + anyhow::anyhow!("invalid prompt"), + CodexErrorInfo::InvalidPrompt, + )), + false, + ), ( GuardianReviewOutcome::Error(GuardianReviewError::parse(anyhow::anyhow!("parse"))), true, diff --git a/codex-rs/ext/guardian-v2/src/async_scorer/metrics.rs b/codex-rs/ext/guardian-v2/src/async_scorer/metrics.rs index 21fbd94e57..5841259415 100644 --- a/codex-rs/ext/guardian-v2/src/async_scorer/metrics.rs +++ b/codex-rs/ext/guardian-v2/src/async_scorer/metrics.rs @@ -48,7 +48,7 @@ pub(super) fn sampler_failure_reason(error: &LunaSamplerError) -> &'static str { ApiError::UsageNotIncluded => "usage_not_included", ApiError::Retryable { .. } => "retryable_api_error", ApiError::RateLimitExceeded { .. } | ApiError::RateLimit(_) => "rate_limit", - ApiError::InvalidRequest { .. } => "invalid_request", + ApiError::InvalidRequest { .. } | ApiError::InvalidPrompt { .. } => "invalid_request", ApiError::CyberPolicy { .. } | ApiError::BioPolicy { .. } | ApiError::MisalignmentPolicyViolation { .. } => "policy_error", diff --git a/codex-rs/ext/guardian-v2/src/async_scorer/sampler/execution.rs b/codex-rs/ext/guardian-v2/src/async_scorer/sampler/execution.rs index 0e50a96e90..0265fb647f 100644 --- a/codex-rs/ext/guardian-v2/src/async_scorer/sampler/execution.rs +++ b/codex-rs/ext/guardian-v2/src/async_scorer/sampler/execution.rs @@ -92,6 +92,7 @@ impl SamplingExecution { | ApiError::UsageNotIncluded | ApiError::RateLimit(_) | ApiError::InvalidRequest { .. } + | ApiError::InvalidPrompt { .. } | ApiError::MisalignmentPolicyViolation { .. } | ApiError::CyberPolicy { .. } | ApiError::BioPolicy { .. }, diff --git a/codex-rs/protocol/src/codex_error_info.rs b/codex-rs/protocol/src/codex_error_info.rs index 35536f91fd..fd883e0558 100644 --- a/codex-rs/protocol/src/codex_error_info.rs +++ b/codex-rs/protocol/src/codex_error_info.rs @@ -34,6 +34,7 @@ enum CodexErrorInfoWire { InternalServerError, Unauthorized, BadRequest, + InvalidPrompt, SandboxError, ResponseStreamDisconnected { http_status_code: Option, diff --git a/codex-rs/protocol/src/error.rs b/codex-rs/protocol/src/error.rs index 609cdcab95..14825e4db6 100644 --- a/codex-rs/protocol/src/error.rs +++ b/codex-rs/protocol/src/error.rs @@ -126,6 +126,8 @@ pub enum CodexErrorDetails { /// Invalid request. #[error("{0}")] InvalidRequest(String), + #[error("{message}")] + InvalidPrompt { message: String }, /// Multiple registered tools share the same effective name. #[error("duplicate tool: {0}")] ToolCollision(String), @@ -387,6 +389,7 @@ impl CodexErr { | CodexErrorDetails::QuotaExceeded | CodexErrorDetails::InvalidImageRequest() | CodexErrorDetails::InvalidRequest(_) + | CodexErrorDetails::InvalidPrompt { .. } | CodexErrorDetails::ToolCollision(_) | CodexErrorDetails::RefreshTokenFailed(_) | CodexErrorDetails::UnsupportedOperation(_) @@ -452,6 +455,7 @@ impl CodexErr { CodexErrorDetails::ServerOverloaded => CodexErrorInfo::ServerOverloaded, CodexErrorDetails::CyberPolicy { .. } => CodexErrorInfo::CyberPolicy, CodexErrorDetails::BioPolicy { .. } => CodexErrorInfo::BioPolicy, + CodexErrorDetails::InvalidPrompt { .. } => CodexErrorInfo::InvalidPrompt, CodexErrorDetails::MisalignmentPolicyViolation { .. } => { CodexErrorInfo::MisalignmentPolicyViolation } diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index f5231dbca8..3b44039af6 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -1861,6 +1861,7 @@ pub enum CodexErrorInfo { InternalServerError, Unauthorized, BadRequest, + InvalidPrompt, SandboxError, /// The response SSE stream disconnected in the middle of a turnbefore completion. ResponseStreamDisconnected { @@ -1898,6 +1899,7 @@ impl CodexErrorInfo { | Self::InternalServerError | Self::Unauthorized | Self::BadRequest + | Self::InvalidPrompt | Self::SandboxError | Self::ResponseStreamDisconnected { .. } | Self::ResponseTooManyFailedAttempts { .. } diff --git a/codex-rs/response-debug-context/src/lib.rs b/codex-rs/response-debug-context/src/lib.rs index 1a32b91cd9..9014af3e80 100644 --- a/codex-rs/response-debug-context/src/lib.rs +++ b/codex-rs/response-debug-context/src/lib.rs @@ -80,7 +80,9 @@ pub fn telemetry_api_error_message(error: &ApiError) -> String { ApiError::Retryable { .. } => "retryable error".to_string(), ApiError::RateLimitExceeded { .. } => "rate limit exceeded".to_string(), ApiError::RateLimit(_) => "rate limit".to_string(), - ApiError::InvalidRequest { .. } => "invalid request".to_string(), + ApiError::InvalidRequest { .. } | ApiError::InvalidPrompt { .. } => { + "invalid request".to_string() + } ApiError::CyberPolicy { .. } => "cyber policy".to_string(), ApiError::BioPolicy { .. } => "bio policy".to_string(), ApiError::MisalignmentPolicyViolation { .. } => "misalignment policy violation".to_string(),