use crate::message::{ToolCall, ToolResultData};
use serde_json::Value;
#[must_use]
pub fn part_text(part: &Value) -> Option<&str> {
part["text"]
.as_str()
.or_else(|| part["input_text"].as_str())
.or_else(|| part["output_text"].as_str())
}
#[must_use]
pub fn extract_text(content: Option<&Value>) -> String {
match content {
Some(Value::Array(parts)) => parts
.iter()
.filter_map(part_text)
.map(str::to_string)
.collect::<Vec<_>>()
.join("\n"),
Some(Value::String(text)) => text.clone(),
_ => String::new(),
}
}
#[must_use]
pub fn parse_tool_arguments(raw: &Value) -> Value {
if let Some(text) = raw.as_str()
&& let Ok(parsed) = serde_json::from_str::<Value>(text)
{
return parsed;
}
raw.clone()
}
#[must_use]
pub fn build_tool_call(
id: impl Into<String>,
name: impl Into<String>,
arguments: Option<&Value>,
) -> ToolCall {
let arguments = arguments.map_or(Value::Null, parse_tool_arguments);
ToolCall {
id: id.into(),
name: name.into(),
arguments,
}
}
#[must_use]
pub fn build_tool_result(
call_id: impl Into<String>,
fallback_name: impl Into<String>,
content_text: impl Into<String>,
is_error: bool,
) -> ToolResultData {
ToolResultData {
call_id: call_id.into(),
tool_name: fallback_name.into(),
content: content_text.into(),
is_error,
}
}
#[must_use]
pub fn extract_tool_result_from_part(part: &Value) -> Option<ToolResultData> {
let is_result = matches!(
part["type"].as_str(),
Some("tool_result" | "toolResult" | "toolResponse")
);
if !is_result && part.get("toolResult").is_none() {
return None;
}
let tool_name = part["name"]
.as_str()
.or_else(|| part["toolName"].as_str())
.or_else(|| part["call_id"].as_str())
.unwrap_or("tool")
.to_string();
let call_id = part["tool_use_id"]
.as_str()
.or_else(|| part["call_id"].as_str())
.or_else(|| part["id"].as_str())
.unwrap_or("")
.to_string();
let content = if let Some(content) = part.get("content") {
extract_text(Some(content))
} else if let Some(content) = part.get("output") {
extract_text(Some(content))
} else if let Some(tool_result_value) = part.get("toolResult") {
extract_text(
tool_result_value
.get("value")
.and_then(|v| v.get("content")),
)
} else {
String::new()
};
let is_error = part["isError"].as_bool().unwrap_or(false)
|| part["is_error"].as_bool().unwrap_or(false)
|| matches!(
part["status"].as_str(),
Some("error" | "failed" | "failure")
)
|| part["toolResult"]["status"]
.as_str()
.is_some_and(|status| status == "error");
Some(build_tool_result(call_id, tool_name, content, is_error))
}