use anyhow::Result;
use tokio::sync::mpsc;
use super::state::GenerationStatus;
use super::stream_event::StreamEvent;
use crate::models::{ErrorCategory, MessageRole, UserFacingError};
use crate::tui::App;
fn is_thinking_unsupported_error(message: &str) -> bool {
message.contains("does not support thinking")
}
fn is_vision_unsupported_error(message: &str) -> bool {
let lower = message.to_lowercase();
lower.contains("does not support images")
|| lower.contains("images not supported")
|| lower.contains("does not support vision")
|| lower.contains("is not a multimodal model")
|| lower.contains("unsupported content type")
}
#[derive(Debug, Clone)]
#[must_use]
pub enum StreamStatus {
Streaming,
Complete {
tool_calls: Vec<crate::models::ToolCall>,
},
Error(UserFacingError),
}
pub async fn process_stream_chunks(
app: &mut App,
rx: &mut mpsc::Receiver<StreamEvent>,
) -> Result<StreamStatus> {
if !app.app_state.is_generating() {
return Ok(StreamStatus::Streaming);
}
while let Ok(event) = rx.try_recv() {
match event {
StreamEvent::Chunk(text) => {
app.push_response(&text);
if app.app_state.generation_status() != Some(GenerationStatus::Streaming) {
app.transition_to_streaming();
}
},
StreamEvent::ToolCalls(calls) => {
app.operation_state.accumulated_tool_calls.extend(calls);
},
StreamEvent::Done { total_tokens } => {
app.set_final_tokens(total_tokens);
let tool_calls = std::mem::take(&mut app.operation_state.accumulated_tool_calls);
let response_text = app.take_response();
if !response_text.is_empty() {
app.add_message(MessageRole::Assistant, response_text);
}
return Ok(StreamStatus::Complete { tool_calls });
},
StreamEvent::Error(user_error) => {
app.clear_response();
if is_thinking_unsupported_error(&user_error.message) {
app.model_state.disable_thinking_support();
app.set_status("Model does not support thinking - disabled");
app.add_message(
MessageRole::System,
"This model does not support thinking mode. Thinking has been disabled. Please try your request again.".to_string()
);
return Ok(StreamStatus::Error(user_error));
}
if is_vision_unsupported_error(&user_error.message) {
app.model_state.vision_supported = Some(false);
for msg in &mut app.session_state.messages {
msg.images = None;
}
app.set_status("Model does not support images - disabled");
app.add_message(
MessageRole::System,
"This model does not support images. Image paste has been disabled for this session.".to_string()
);
return Ok(StreamStatus::Error(user_error));
}
let status_prefix = match user_error.category {
ErrorCategory::Connection => "Connection",
ErrorCategory::Auth => "Auth",
ErrorCategory::Config => "Config",
ErrorCategory::NotFound => "Not Found",
ErrorCategory::Temporary => "Temporary",
ErrorCategory::Internal => "Error",
};
app.set_status(format!("[{}] {}", status_prefix, user_error.summary));
let error_display = format!(
"{}\n\nSuggestion: {}",
user_error.message, user_error.suggestion
);
app.add_message(MessageRole::System, error_display);
return Ok(StreamStatus::Error(user_error));
},
}
}
Ok(StreamStatus::Streaming)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stream_status_variants() {
let streaming = StreamStatus::Streaming;
assert!(matches!(streaming, StreamStatus::Streaming));
let complete = StreamStatus::Complete { tool_calls: vec![] };
assert!(matches!(complete, StreamStatus::Complete { .. }));
let error = StreamStatus::Error(UserFacingError {
summary: "Test error".to_string(),
message: "A test error occurred".to_string(),
suggestion: "This is just a test".to_string(),
category: ErrorCategory::Internal,
recoverable: false,
});
assert!(matches!(error, StreamStatus::Error(_)));
}
#[test]
fn test_thinking_unsupported_detection() {
assert!(is_thinking_unsupported_error(
"this model does not support thinking"
));
assert!(is_thinking_unsupported_error(
"Model X does not support thinking mode"
));
assert!(!is_thinking_unsupported_error("connection refused"));
assert!(!is_thinking_unsupported_error(""));
}
#[test]
fn test_vision_unsupported_detection() {
assert!(is_vision_unsupported_error(
"this model does not support images"
));
assert!(is_vision_unsupported_error(
"images not supported by this model"
));
assert!(is_vision_unsupported_error(
"llama3 is not a multimodal model"
));
assert!(is_vision_unsupported_error(
"unsupported content type 'image_url'"
));
assert!(!is_vision_unsupported_error("connection refused"));
assert!(!is_vision_unsupported_error("rate limit exceeded"));
assert!(!is_vision_unsupported_error(""));
}
}