1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4use super::{Message, Metadata, ToolArguments, ToolDirective, ToolResultStatus};
5
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
7pub struct ToolCall {
8 pub id: String,
9 pub name: String,
10 pub arguments: ToolArguments,
11 pub extra_content: Option<Value>,
12}
13
14impl ToolCall {
15 pub fn new(id: impl Into<String>, name: impl Into<String>, arguments: ToolArguments) -> Self {
16 Self {
17 id: id.into(),
18 name: name.into(),
19 arguments,
20 extra_content: None,
21 }
22 }
23
24 pub fn from_raw_arguments(
25 id: impl Into<String>,
26 name: impl Into<String>,
27 raw_arguments: Value,
28 ) -> Self {
29 let id = id.into();
30 let name = name.into();
31 match parse_raw_tool_arguments(&raw_arguments) {
32 Ok(arguments) => Self {
33 id,
34 name,
35 arguments,
36 extra_content: None,
37 },
38 Err((error_code, error)) => Self {
39 id,
40 name,
41 arguments: ToolArguments::new(),
42 extra_content: Some(Value::Object(
43 [
44 ("raw_arguments".to_string(), raw_arguments),
45 ("argument_error_code".to_string(), Value::String(error_code)),
46 ("argument_error".to_string(), Value::String(error)),
47 ]
48 .into_iter()
49 .collect(),
50 )),
51 },
52 }
53 }
54}
55
56fn parse_raw_tool_arguments(raw_arguments: &Value) -> Result<ToolArguments, (String, String)> {
57 match raw_arguments {
58 Value::Null => Ok(ToolArguments::new()),
59 Value::Object(object) => Ok(object.clone().into_iter().collect()),
60 Value::String(raw) => {
61 let stripped = raw.trim();
62 if stripped.is_empty() {
63 return Ok(ToolArguments::new());
64 }
65 let parsed = serde_json::from_str::<Value>(stripped).map_err(|error| {
66 (
67 "invalid_arguments_json".to_string(),
68 format!("Invalid tool arguments JSON: {error}"),
69 )
70 })?;
71 match parsed {
72 Value::Object(object) => Ok(object.into_iter().collect()),
73 _ => Err((
74 "invalid_arguments_payload".to_string(),
75 "Tool arguments must decode to an object".to_string(),
76 )),
77 }
78 }
79 other => Err((
80 "invalid_arguments_type".to_string(),
81 format!("Unsupported tool argument type: {}", json_type_name(other)),
82 )),
83 }
84}
85
86fn json_type_name(value: &Value) -> &'static str {
87 match value {
88 Value::Null => "null",
89 Value::Bool(_) => "bool",
90 Value::Number(_) => "number",
91 Value::String(_) => "string",
92 Value::Array(_) => "array",
93 Value::Object(_) => "object",
94 }
95}
96
97#[derive(Debug, Clone, PartialEq)]
98pub struct ToolExecutionResult {
99 pub tool_call_id: String,
100 pub content: String,
101 pub status: ToolResultStatus,
102 pub directive: ToolDirective,
103 pub error_code: Option<String>,
104 pub metadata: Metadata,
105 pub image_url: Option<String>,
106 pub image_path: Option<String>,
107}
108
109impl Serialize for ToolExecutionResult {
110 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
111 where
112 S: serde::Serializer,
113 {
114 self.to_dict().serialize(serializer)
115 }
116}
117
118impl<'de> Deserialize<'de> for ToolExecutionResult {
119 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
120 where
121 D: serde::Deserializer<'de>,
122 {
123 let value = Value::deserialize(deserializer)?;
124 Self::from_dict(&value).map_err(serde::de::Error::custom)
125 }
126}
127
128impl ToolExecutionResult {
129 pub fn success(tool_call_id: impl Into<String>, content: impl Into<String>) -> Self {
130 Self {
131 tool_call_id: tool_call_id.into(),
132 content: content.into(),
133 status: ToolResultStatus::Success,
134 directive: ToolDirective::Continue,
135 error_code: None,
136 metadata: Metadata::new(),
137 image_url: None,
138 image_path: None,
139 }
140 }
141
142 pub fn error(tool_call_id: impl Into<String>, content: impl Into<String>) -> Self {
143 Self {
144 status: ToolResultStatus::Error,
145 ..Self::success(tool_call_id, content)
146 }
147 }
148
149 pub fn to_message(&self) -> Message {
150 Message::tool(self.content.clone(), self.tool_call_id.clone())
151 }
152
153 pub fn to_tool_message(&self) -> Message {
154 self.to_message()
155 }
156}