Properly extract errors from the Anthropic API (#15534)

Kirill Bulatov created

Before, we missed "successful" responses with the API errors, now they
are properly shown in the assistant panel.


![image](https://github.com/user-attachments/assets/0c0936af-86c2-4def-9a58-25d5e0912b97)


Release Notes:

- N/A

Change summary

crates/anthropic/src/anthropic.rs | 14 ++++++++++++--
crates/assistant/src/context.rs   |  6 +++---
2 files changed, 15 insertions(+), 5 deletions(-)

Detailed changes

crates/anthropic/src/anthropic.rs 🔗

@@ -173,9 +173,9 @@ pub async fn stream_completion(
         let body_str = std::str::from_utf8(&body)?;
 
         match serde_json::from_str::<Event>(body_str) {
+            Ok(Event::Error { error }) => Err(api_error_to_err(error)),
             Ok(_) => Err(anyhow!(
-                "Unexpected success response while expecting an error: {}",
-                body_str,
+                "Unexpected success response while expecting an error: '{body_str}'",
             )),
             Err(_) => Err(anyhow!(
                 "Failed to connect to API: {} {}",
@@ -200,6 +200,7 @@ pub fn extract_text_from_events(
                     ContentDelta::TextDelta { text } => Some(Ok(text)),
                     _ => None,
                 },
+                Event::Error { error } => Some(Err(api_error_to_err(error))),
                 _ => None,
             },
             Err(error) => Some(Err(error)),
@@ -207,6 +208,15 @@ pub fn extract_text_from_events(
     })
 }
 
+fn api_error_to_err(
+    ApiError {
+        error_type,
+        message,
+    }: ApiError,
+) -> anyhow::Error {
+    anyhow!("API error. Type: '{error_type}', message: '{message}'",)
+}
+
 #[derive(Debug, Serialize, Deserialize)]
 pub struct Message {
     pub role: Role,

crates/assistant/src/context.rs 🔗

@@ -273,7 +273,7 @@ impl ContextOperation {
     }
 }
 
-#[derive(Clone)]
+#[derive(Debug, Clone)]
 pub enum ContextEvent {
     MessagesEdited,
     SummaryChanged,
@@ -2188,7 +2188,7 @@ impl ContextVersion {
     }
 }
 
-#[derive(Clone)]
+#[derive(Debug, Clone)]
 pub struct PendingSlashCommand {
     pub name: String,
     pub argument: Option<String>,
@@ -2196,7 +2196,7 @@ pub struct PendingSlashCommand {
     pub source_range: Range<language::Anchor>,
 }
 
-#[derive(Clone)]
+#[derive(Debug, Clone)]
 pub enum PendingSlashCommandStatus {
     Idle,
     Running { _task: Shared<Task<()>> },