Skip to main content

systemprompt_models/ai/tools/
tool_call.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use serde_json::Value as JsonValue;
4use std::collections::HashMap;
5use systemprompt_identifiers::{AiToolCallId, McpServerId};
6use systemprompt_traits::parse_database_datetime;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct ToolCall {
10    pub ai_tool_call_id: AiToolCallId,
11    pub name: String,
12    pub arguments: JsonValue,
13}
14
15pub use rmcp::model::CallToolResult;
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct ToolExecution {
19    pub id: String,
20    pub request_id: String,
21    pub sequence: i32,
22    pub tool_name: String,
23    pub service_id: McpServerId,
24    pub input: JsonValue,
25    pub output: Option<JsonValue>,
26    pub status: String,
27    pub execution_time_ms: Option<i32>,
28    pub error_message: Option<String>,
29    pub created_at: DateTime<Utc>,
30}
31
32impl ToolExecution {
33    pub fn from_json_row(row: &HashMap<String, JsonValue>) -> anyhow::Result<Self> {
34        let id = row
35            .get("id")
36            .and_then(|v| v.as_str())
37            .ok_or_else(|| anyhow::anyhow!("Missing id"))?
38            .to_string();
39
40        let request_id = row
41            .get("request_id")
42            .and_then(|v| v.as_str())
43            .ok_or_else(|| anyhow::anyhow!("Missing request_id"))?
44            .to_string();
45
46        let sequence = row
47            .get("sequence")
48            .and_then(serde_json::Value::as_i64)
49            .ok_or_else(|| anyhow::anyhow!("Missing sequence"))
50            .and_then(|i| i32::try_from(i).map_err(|_| anyhow::anyhow!("Sequence out of range")))?;
51
52        let tool_name = row
53            .get("tool_name")
54            .and_then(|v| v.as_str())
55            .ok_or_else(|| anyhow::anyhow!("Missing tool_name"))?
56            .to_string();
57
58        let service_id = row
59            .get("service_id")
60            .and_then(|v| v.as_str())
61            .ok_or_else(|| anyhow::anyhow!("Missing service_id"))
62            .map(McpServerId::new)?;
63
64        let input = row
65            .get("input")
66            .and_then(|v| v.as_str())
67            .and_then(|s| {
68                serde_json::from_str(s)
69                    .map_err(|e| {
70                        tracing::warn!(error = %e, raw = %s, "Failed to parse tool input JSON");
71                        e
72                    })
73                    .ok()
74            })
75            .unwrap_or(JsonValue::Null);
76
77        let output = row.get("output").and_then(|v| v.as_str()).and_then(|s| {
78            serde_json::from_str(s)
79                .map_err(|e| {
80                    tracing::warn!(error = %e, raw = %s, "Failed to parse tool output JSON");
81                    e
82                })
83                .ok()
84        });
85
86        let status = row
87            .get("status")
88            .and_then(|v| v.as_str())
89            .ok_or_else(|| anyhow::anyhow!("Missing status"))?
90            .to_string();
91
92        let execution_time_ms = row
93            .get("execution_time_ms")
94            .and_then(serde_json::Value::as_i64)
95            .and_then(|i| i32::try_from(i).ok());
96
97        let error_message = row
98            .get("error_message")
99            .and_then(|v| v.as_str())
100            .map(String::from);
101
102        let created_at = row
103            .get("created_at")
104            .and_then(parse_database_datetime)
105            .ok_or_else(|| anyhow::anyhow!("Missing or invalid created_at"))?;
106
107        Ok(Self {
108            id,
109            request_id,
110            sequence,
111            tool_name,
112            service_id,
113            input,
114            output,
115            status,
116            execution_time_ms,
117            error_message,
118            created_at,
119        })
120    }
121}