use rho_tools::tool_card::{ToolBody, ToolCard, ToolFact, ToolFamily, ToolHeader, ToolStatus};
use serde_json::Value;
use super::{format::string_arg, ToolBodySyntax, ToolBodyWindow};
const SCRIPT_LANGUAGE: &str = "python";
pub(super) fn body_syntax(window: ToolBodyWindow) -> ToolBodySyntax {
ToolBodySyntax::Code {
language: SCRIPT_LANGUAGE.into(),
window,
}
}
pub(super) fn preview_card(
arguments: &Value,
status: ToolStatus,
primary: Option<String>,
) -> ToolCard {
let lines = string_arg(arguments, "script")
.map(|script| script.trim_end().lines().map(str::to_string).collect())
.unwrap_or_default();
script_card(lines, status, primary)
}
pub(super) fn streaming_card(lines: Vec<String>) -> ToolCard {
script_card(lines, ToolStatus::Running, None)
}
fn script_card(lines: Vec<String>, status: ToolStatus, primary: Option<String>) -> ToolCard {
ToolCard::new(
status,
ToolFamily::Default,
ToolHeader::call(crate::tools::code_mode::CODEMODE_TOOL_NAME, primary),
)
.with_body(ToolBody::Lines(lines))
}
pub(super) fn finished_card(
arguments: &Value,
content: &str,
ok: bool,
data: Option<&Value>,
) -> ToolCard {
let calls = data
.and_then(|data| data.get("calls"))
.and_then(Value::as_u64);
let primary = match calls {
None | Some(0) => None,
Some(1) => Some("1 call".into()),
Some(count) => Some(format!("{count} calls")),
};
let mut card = preview_card(arguments, ToolStatus::from_finished(ok), primary);
if !ok {
let reason = data
.and_then(|data| data.get("error"))
.and_then(Value::as_str)
.filter(|error| !error.trim().is_empty())
.map(|error| format!("script failed: {error}"))
.or_else(|| failure_reason(content));
if let Some(reason) = reason {
card.push_fact(ToolFact::Error { text: reason });
}
}
card
}
fn failure_reason(content: &str) -> Option<String> {
let marker = "script failed: ";
let start = content.rfind(marker)?;
if content[start + marker.len()..].trim().is_empty() {
return None;
}
Some(content[start..].trim_end().to_string())
}
#[cfg(test)]
#[path = "interactive_presenter_codemode_tests.rs"]
mod tests;