diff --git a/src-tauri/src/services/chat_service.rs b/src-tauri/src/services/chat_service.rs index 68c4b38..e5e44d0 100644 --- a/src-tauri/src/services/chat_service.rs +++ b/src-tauri/src/services/chat_service.rs @@ -669,8 +669,9 @@ async fn stream_anthropic_sse( // subsequent `data:` line. Some gateways omit the `"type"` field from // the JSON payload, so we fall back to the SSE event name. let mut current_event = String::new(); + let mut message_stop_received = false; - loop { + 'outer: loop { tokio::select! { _ = cancel_token.cancelled() => { return Err(AppError::Cancelled); @@ -688,7 +689,8 @@ async fn stream_anthropic_sse( } if parse_anthropic_sse_line(&line, &mut current_event, on_token, &mut output)? { - return Ok(output); + message_stop_received = true; + break 'outer; } } } @@ -699,6 +701,12 @@ async fn stream_anthropic_sse( } } + if !message_stop_received { + return Err(AppError::Http( + "Stream ended without completion signal — connection may have been interrupted. Please retry.".to_string(), + )); + } + Ok(output) } @@ -734,10 +742,7 @@ fn parse_anthropic_sse_line( }; let payload = payload.trim(); - let value: Value = match serde_json::from_str(payload) { - Ok(v) => v, - Err(_) => return Ok(false), - }; + let value: Value = serde_json::from_str(payload)?; // Prefer `"type"` from the JSON payload; fall back to the preceding // `event:` line when the gateway strips it. diff --git a/src-tauri/src/tools/executor.rs b/src-tauri/src/tools/executor.rs index c63ea5d..673fa6d 100644 --- a/src-tauri/src/tools/executor.rs +++ b/src-tauri/src/tools/executor.rs @@ -6,6 +6,7 @@ use std::time::Duration; use globset::GlobSet; use regex::Regex; use serde::{Deserialize, Serialize}; +use serde_json::Value; use tokio::process::Command; use walkdir::WalkDir; @@ -34,7 +35,10 @@ fn sensitive_globset() -> &'static GlobSet { } builder.build().unwrap_or_else(|error| { log::error!("sensitive_globset build failed: {error} — all file access will require permission"); - GlobSetBuilder::new().build().expect("empty GlobSet always builds") + match GlobSetBuilder::new().build() { + Ok(globset) => globset, + Err(inner_error) => panic!("empty GlobSet build failed: {inner_error}"), + } }) }) }