vv_agent/types/dict/
tools.rs1use 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}