use std::collections::BTreeMap;
use crate::types::{
CompletionReason, Message, ToolCall, ToolDirective, ToolExecutionResult, ToolResultStatus,
};
pub(crate) fn needs_tool_call_id(value: &str) -> bool {
let stripped = value.trim();
stripped.is_empty() || stripped == "pending"
}
pub(crate) fn apply_tool_use_behavior(
task: &crate::types::AgentTask,
call: &ToolCall,
result: &mut ToolExecutionResult,
) -> Option<CompletionReason> {
if result.directive != ToolDirective::Continue || result.status != ToolResultStatus::Success {
return None;
}
let behavior = task
.metadata
.get("_vv_agent_tool_use_behavior")
.and_then(serde_json::Value::as_str)
.unwrap_or("run_llm_again");
let should_stop = match behavior {
"stop_on_first_tool" => true,
"stop_at_tool_names" => task
.metadata
.get("_vv_agent_stop_at_tool_names")
.and_then(serde_json::Value::as_array)
.is_some_and(|names| {
names
.iter()
.any(|name| name.as_str() == Some(call.name.as_str()))
}),
_ => false,
};
if should_stop {
result.directive = ToolDirective::Finish;
return Some(if behavior == "stop_on_first_tool" {
CompletionReason::StopOnFirstTool
} else {
CompletionReason::StopAtToolName
});
}
None
}
pub(crate) fn skipped_tool_result(
call: &ToolCall,
error_code: &str,
message: &str,
) -> ToolExecutionResult {
ToolExecutionResult {
tool_call_id: call.id.clone(),
content: serde_json::json!({
"ok": false,
"error": message,
"error_code": error_code,
})
.to_string(),
status: ToolResultStatus::Error,
directive: ToolDirective::Continue,
error_code: Some(error_code.to_string()),
metadata: BTreeMap::new(),
image_url: None,
image_path: None,
}
}
pub(super) fn image_notification_from_tool_result(
result: &ToolExecutionResult,
include_image: bool,
) -> Option<Message> {
if !include_image {
return None;
}
if let Some(image_url) = &result.image_url {
let content = result
.image_path
.as_deref()
.map(|image_path| format!("[Image loaded] {image_path}"))
.unwrap_or_default();
let mut image_message = Message::user(content);
image_message.image_url = Some(image_url.clone());
image_message.metadata = result.metadata.clone();
return Some(image_message);
}
result
.image_path
.as_deref()
.map(|image_path| Message::user(format!("[Image loaded] {image_path}")))
}