use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use std::collections::HashMap;
use systemprompt_identifiers::{AiRequestId, AiToolCallId, McpExecutionId, McpServerId};
use systemprompt_traits::parse_database_datetime;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCall {
pub ai_tool_call_id: AiToolCallId,
pub name: String,
pub arguments: JsonValue,
}
pub use rmcp::model::CallToolResult;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolExecution {
pub id: McpExecutionId,
pub request_id: AiRequestId,
pub sequence: i32,
pub tool_name: String,
pub service_id: McpServerId,
pub input: JsonValue,
pub output: Option<JsonValue>,
pub status: String,
pub execution_time_ms: Option<i32>,
pub error_message: Option<String>,
pub created_at: DateTime<Utc>,
}
impl ToolExecution {
pub fn from_json_row(row: &HashMap<String, JsonValue>) -> anyhow::Result<Self> {
let id = row
.get("id")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Missing id"))
.map(McpExecutionId::new)?;
let request_id = row
.get("request_id")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Missing request_id"))
.map(AiRequestId::new)?;
let sequence = row
.get("sequence")
.and_then(serde_json::Value::as_i64)
.ok_or_else(|| anyhow::anyhow!("Missing sequence"))
.and_then(|i| i32::try_from(i).map_err(|_| anyhow::anyhow!("Sequence out of range")))?;
let tool_name = row
.get("tool_name")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Missing tool_name"))?
.to_string();
let service_id = row
.get("service_id")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Missing service_id"))
.map(McpServerId::new)?;
let input = row
.get("input")
.and_then(|v| v.as_str())
.and_then(|s| {
serde_json::from_str(s)
.map_err(|e| {
tracing::warn!(error = %e, raw = %s, "Failed to parse tool input JSON");
e
})
.ok()
})
.unwrap_or(JsonValue::Null);
let output = row.get("output").and_then(|v| v.as_str()).and_then(|s| {
serde_json::from_str(s)
.map_err(|e| {
tracing::warn!(error = %e, raw = %s, "Failed to parse tool output JSON");
e
})
.ok()
});
let status = row
.get("status")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Missing status"))?
.to_string();
let execution_time_ms = row
.get("execution_time_ms")
.and_then(serde_json::Value::as_i64)
.and_then(|i| i32::try_from(i).ok());
let error_message = row
.get("error_message")
.and_then(|v| v.as_str())
.map(String::from);
let created_at = row
.get("created_at")
.and_then(parse_database_datetime)
.ok_or_else(|| anyhow::anyhow!("Missing or invalid created_at"))?;
Ok(Self {
id,
request_id,
sequence,
tool_name,
service_id,
input,
output,
status,
execution_time_ms,
error_message,
created_at,
})
}
}