Skip to main content

vv_agent/types/
tool_calls.rs

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, Serialize, Deserialize)]
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 ToolExecutionResult {
110    pub fn success(tool_call_id: impl Into<String>, content: impl Into<String>) -> Self {
111        Self {
112            tool_call_id: tool_call_id.into(),
113            content: content.into(),
114            status: ToolResultStatus::Success,
115            directive: ToolDirective::Continue,
116            error_code: None,
117            metadata: Metadata::new(),
118            image_url: None,
119            image_path: None,
120        }
121    }
122
123    pub fn error(tool_call_id: impl Into<String>, content: impl Into<String>) -> Self {
124        Self {
125            status: ToolResultStatus::Error,
126            ..Self::success(tool_call_id, content)
127        }
128    }
129
130    pub fn to_message(&self) -> Message {
131        Message::tool(self.content.clone(), self.tool_call_id.clone())
132    }
133
134    pub fn to_tool_message(&self) -> Message {
135        self.to_message()
136    }
137}