1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
7pub struct ToolCall {
8 pub id: String,
9 pub r#type: String,
10 pub function: FunctionCall,
11 #[serde(default, skip_serializing_if = "Option::is_none")]
15 pub thought_signature: Option<String>,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
19pub struct FunctionCall {
20 pub name: String,
21 pub arguments: String,
23}
24
25impl ToolCall {
26 pub fn parsed_args(&self) -> std::result::Result<serde_json::Value, serde_json::Error> {
28 serde_json::from_str(&self.function.arguments)
29 }
30
31 pub fn from_llm(tc: &edgequake_llm::ToolCall) -> Self {
37 Self {
38 id: tc.id.clone(),
39 r#type: tc.call_type.clone(),
40 function: FunctionCall {
41 name: tc.function.name.clone(),
42 arguments: tc.function.arguments.clone(),
43 },
44 thought_signature: tc.thought_signature.clone(),
45 }
46 }
47
48 pub fn to_llm(&self) -> edgequake_llm::ToolCall {
50 edgequake_llm::ToolCall {
51 id: self.id.clone(),
52 call_type: self.r#type.clone(),
53 function: edgequake_llm::FunctionCall {
54 name: self.function.name.clone(),
55 arguments: self.function.arguments.clone(),
56 },
57 thought_signature: self.thought_signature.clone(),
58 }
59 }
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
64pub struct ToolSchema {
65 pub name: String,
66 pub description: String,
67 pub parameters: serde_json::Value,
68 #[serde(skip_serializing_if = "Option::is_none")]
69 pub strict: Option<bool>,
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 #[test]
77 fn tool_call_roundtrip() {
78 let tc = ToolCall {
79 id: "call_abc123".into(),
80 r#type: "function".into(),
81 function: FunctionCall {
82 name: "read_file".into(),
83 arguments: r#"{"path":"src/lib.rs","line_start":1}"#.into(),
84 },
85 thought_signature: None,
86 };
87 let json = serde_json::to_string(&tc).expect("serialize");
88 let deser: ToolCall = serde_json::from_str(&json).expect("deserialize");
89 assert_eq!(tc, deser);
90 }
91
92 #[test]
93 fn parsed_args_valid_json() {
94 let tc = ToolCall {
95 id: "1".into(),
96 r#type: "function".into(),
97 function: FunctionCall {
98 name: "write_file".into(),
99 arguments: r#"{"path":"out.txt","content":"hello"}"#.into(),
100 },
101 thought_signature: None,
102 };
103 let args = tc.parsed_args().expect("valid json");
104 assert_eq!(args["path"], "out.txt");
105 assert_eq!(args["content"], "hello");
106 }
107
108 #[test]
109 fn parsed_args_invalid_json() {
110 let tc = ToolCall {
111 id: "1".into(),
112 r#type: "function".into(),
113 function: FunctionCall {
114 name: "bad".into(),
115 arguments: "not json".into(),
116 },
117 thought_signature: None,
118 };
119 assert!(tc.parsed_args().is_err());
120 }
121
122 #[test]
123 fn tool_schema_roundtrip() {
124 let schema = ToolSchema {
125 name: "read_file".into(),
126 description: "Read a file".into(),
127 parameters: serde_json::json!({
128 "type": "object",
129 "properties": {
130 "path": { "type": "string" }
131 },
132 "required": ["path"]
133 }),
134 strict: Some(true),
135 };
136 let json = serde_json::to_string(&schema).expect("serialize");
137 let deser: ToolSchema = serde_json::from_str(&json).expect("deserialize");
138 assert_eq!(schema, deser);
139 }
140}