Skip to main content

vv_agent/types/dict/
tools.rs

1use super::common::*;
2use super::*;
3
4impl ToolExecutionResult {
5    pub fn to_dict(&self) -> Value {
6        let mut payload = serde_json::Map::from_iter([
7            (
8                "tool_call_id".to_string(),
9                Value::String(self.tool_call_id.clone()),
10            ),
11            ("content".to_string(), Value::String(self.content.clone())),
12            (
13                "directive".to_string(),
14                Value::String(tool_directive_value(self.directive).to_string()),
15            ),
16            (
17                "status_code".to_string(),
18                Value::String(tool_result_status_value(self.status).to_string()),
19            ),
20        ]);
21        insert_optional_string(&mut payload, "error_code", &self.error_code);
22        if !self.metadata.is_empty() {
23            payload.insert("metadata".to_string(), metadata_to_value(&self.metadata));
24        }
25        insert_optional_string(&mut payload, "image_url", &self.image_url);
26        insert_optional_string(&mut payload, "image_path", &self.image_path);
27        Value::Object(payload)
28    }
29
30    pub fn from_dict(data: &Value) -> Result<Self, String> {
31        let object = expect_object(data, "ToolExecutionResult")?;
32        let required = ["tool_call_id", "content", "status_code", "directive"];
33        let allowed = [
34            "tool_call_id",
35            "content",
36            "status_code",
37            "directive",
38            "error_code",
39            "metadata",
40            "image_url",
41            "image_path",
42        ];
43        let mut missing = required
44            .iter()
45            .filter(|field| !object.contains_key(**field))
46            .copied()
47            .collect::<Vec<_>>();
48        let mut unknown = object
49            .keys()
50            .filter(|field| !allowed.contains(&field.as_str()))
51            .map(String::as_str)
52            .collect::<Vec<_>>();
53        missing.sort_unstable();
54        unknown.sort_unstable();
55        if !missing.is_empty() || !unknown.is_empty() {
56            return Err(format!(
57                "ToolExecutionResult fields do not match the current wire: missing={missing:?}, unknown={unknown:?}"
58            ));
59        }
60
61        let metadata = match object.get("metadata") {
62            None => Metadata::new(),
63            Some(Value::Object(metadata)) => metadata.clone().into_iter().collect(),
64            Some(_) => return Err("ToolExecutionResult metadata must be an object".to_string()),
65        };
66        Ok(Self {
67            tool_call_id: read_required_string(object, "tool_call_id")?.to_string(),
68            content: read_required_string(object, "content")?.to_string(),
69            status: parse_tool_result_status(read_required_string(object, "status_code")?)?,
70            directive: parse_tool_directive(read_required_string(object, "directive")?)?,
71            error_code: strict_optional_string(object, "error_code")?,
72            metadata,
73            image_url: strict_optional_string(object, "image_url")?,
74            image_path: strict_optional_string(object, "image_path")?,
75        })
76    }
77}
78
79fn strict_optional_string(
80    object: &serde_json::Map<String, Value>,
81    key: &str,
82) -> Result<Option<String>, String> {
83    match object.get(key) {
84        None | Some(Value::Null) => Ok(None),
85        Some(Value::String(value)) => Ok(Some(value.clone())),
86        Some(_) => Err(format!(
87            "ToolExecutionResult {key} must be a string or null"
88        )),
89    }
90}