1use indexmap::IndexMap;
7use serde::{Deserialize, Serialize};
8use serde_json::Value as JsonValue;
9use std::collections::HashMap;
10
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
16pub struct ObjectJsonSchema {
17 #[serde(rename = "type")]
19 pub schema_type: String,
20
21 pub properties: IndexMap<String, JsonValue>,
23
24 #[serde(skip_serializing_if = "Vec::is_empty", default)]
26 pub required: Vec<String>,
27
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub description: Option<String>,
31
32 #[serde(
34 rename = "additionalProperties",
35 skip_serializing_if = "Option::is_none"
36 )]
37 pub additional_properties: Option<bool>,
38
39 #[serde(flatten)]
41 pub extra: HashMap<String, JsonValue>,
42}
43
44impl ObjectJsonSchema {
45 #[must_use]
47 pub fn new() -> Self {
48 Self {
49 schema_type: "object".to_string(),
50 properties: IndexMap::new(),
51 required: Vec::new(),
52 description: None,
53 additional_properties: None,
54 extra: HashMap::new(),
55 }
56 }
57
58 #[must_use]
60 pub fn with_property(mut self, name: &str, schema: JsonValue, required: bool) -> Self {
61 self.properties.insert(name.to_string(), schema);
62 if required {
63 self.required.push(name.to_string());
64 }
65 self
66 }
67
68 pub fn add_property(&mut self, name: &str, schema: JsonValue, required: bool) {
70 self.properties.insert(name.to_string(), schema);
71 if required && !self.required.contains(&name.to_string()) {
72 self.required.push(name.to_string());
73 }
74 }
75
76 #[must_use]
78 pub fn with_description(mut self, desc: &str) -> Self {
79 self.description = Some(desc.to_string());
80 self
81 }
82
83 #[must_use]
85 pub fn with_additional_properties(mut self, allowed: bool) -> Self {
86 self.additional_properties = Some(allowed);
87 self
88 }
89
90 #[must_use]
92 pub fn with_extra(mut self, key: &str, value: JsonValue) -> Self {
93 self.extra.insert(key.to_string(), value);
94 self
95 }
96
97 #[must_use]
99 pub fn is_required(&self, name: &str) -> bool {
100 self.required.contains(&name.to_string())
101 }
102
103 #[must_use]
105 pub fn get_property(&self, name: &str) -> Option<&JsonValue> {
106 self.properties.get(name)
107 }
108
109 #[must_use]
111 pub fn property_count(&self) -> usize {
112 self.properties.len()
113 }
114
115 #[must_use]
117 pub fn is_empty(&self) -> bool {
118 self.properties.is_empty()
119 }
120
121 pub fn to_json(&self) -> Result<JsonValue, serde_json::Error> {
123 serde_json::to_value(self)
124 }
125}
126
127impl Default for ObjectJsonSchema {
128 fn default() -> Self {
129 Self::new()
130 }
131}
132
133impl TryFrom<JsonValue> for ObjectJsonSchema {
134 type Error = serde_json::Error;
135
136 fn try_from(value: JsonValue) -> Result<Self, Self::Error> {
137 serde_json::from_value(value)
138 }
139}
140
141impl From<ObjectJsonSchema> for JsonValue {
142 fn from(schema: ObjectJsonSchema) -> Self {
143 serde_json::to_value(schema).unwrap_or(JsonValue::Null)
144 }
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
152pub struct ToolDefinition {
153 pub name: String,
155
156 pub description: String,
158
159 pub parameters_json_schema: JsonValue,
161
162 #[serde(skip_serializing_if = "Option::is_none")]
164 pub strict: Option<bool>,
165
166 #[serde(skip_serializing_if = "Option::is_none")]
168 pub outer_typed_dict_key: Option<String>,
169}
170
171impl ToolDefinition {
172 #[must_use]
174 pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
175 Self {
176 name: name.into(),
177 description: description.into(),
178 parameters_json_schema: crate::schema::SchemaBuilder::new()
179 .build()
180 .expect("SchemaBuilder JSON serialization failed"),
181 strict: None,
182 outer_typed_dict_key: None,
183 }
184 }
185
186 #[must_use]
188 pub fn with_parameters(mut self, schema: impl Into<JsonValue>) -> Self {
189 self.parameters_json_schema = schema.into();
190 self
191 }
192
193 #[must_use]
195 pub fn with_strict(mut self, strict: bool) -> Self {
196 self.strict = Some(strict);
197 self
198 }
199
200 #[must_use]
202 pub fn with_outer_typed_dict_key(mut self, key: impl Into<String>) -> Self {
203 self.outer_typed_dict_key = Some(key.into());
204 self
205 }
206
207 #[must_use]
209 pub fn name(&self) -> &str {
210 &self.name
211 }
212
213 #[must_use]
215 pub fn description(&self) -> &str {
216 &self.description
217 }
218
219 #[must_use]
221 pub fn parameters(&self) -> &JsonValue {
222 &self.parameters_json_schema
223 }
224
225 #[must_use]
227 pub fn is_strict(&self) -> bool {
228 self.strict.unwrap_or(false)
229 }
230
231 #[must_use]
233 pub fn to_openai_function(&self) -> JsonValue {
234 let mut func = serde_json::json!({
235 "type": "function",
236 "function": {
237 "name": self.name,
238 "description": self.description,
239 "parameters": self.parameters_json_schema.clone()
240 }
241 });
242
243 if let Some(strict) = self.strict {
244 func["function"]["strict"] = JsonValue::Bool(strict);
245 }
246
247 func
248 }
249
250 #[must_use]
252 pub fn to_anthropic_tool(&self) -> JsonValue {
253 serde_json::json!({
254 "name": self.name,
255 "description": self.description,
256 "input_schema": self.parameters_json_schema.clone()
257 })
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264
265 #[test]
266 fn test_object_json_schema_new() {
267 let schema = ObjectJsonSchema::new();
268 assert_eq!(schema.schema_type, "object");
269 assert!(schema.properties.is_empty());
270 assert!(schema.required.is_empty());
271 }
272
273 #[test]
274 fn test_object_json_schema_with_property() {
275 let schema = ObjectJsonSchema::new()
276 .with_property("name", serde_json::json!({"type": "string"}), true)
277 .with_property("age", serde_json::json!({"type": "integer"}), false);
278
279 assert_eq!(schema.property_count(), 2);
280 assert!(schema.is_required("name"));
281 assert!(!schema.is_required("age"));
282 }
283
284 #[test]
285 fn test_tool_definition_new() {
286 let def = ToolDefinition::new("get_weather", "Get the current weather");
287 assert_eq!(def.name(), "get_weather");
288 assert_eq!(def.description(), "Get the current weather");
289 let properties = def
290 .parameters()
291 .get("properties")
292 .and_then(|value| value.as_object())
293 .unwrap();
294 assert!(properties.is_empty());
295 }
296
297 #[test]
298 fn test_tool_definition_with_parameters() {
299 let params = crate::schema::SchemaBuilder::new()
300 .string("location", "City name", true)
301 .enum_values(
302 "unit",
303 "Temperature unit",
304 &["celsius", "fahrenheit"],
305 false,
306 )
307 .build()
308 .expect("SchemaBuilder JSON serialization failed");
309
310 let def = ToolDefinition::new("get_weather", "Get weather")
311 .with_parameters(params)
312 .with_strict(true);
313
314 assert!(def.is_strict());
315 let properties = def
316 .parameters()
317 .get("properties")
318 .and_then(|value| value.as_object())
319 .unwrap();
320 assert_eq!(properties.len(), 2);
321 }
322
323 #[test]
324 fn test_to_openai_function() {
325 let def = ToolDefinition::new("test", "Test tool")
326 .with_parameters(
327 crate::schema::SchemaBuilder::new()
328 .string("x", "A value", true)
329 .build()
330 .expect("SchemaBuilder JSON serialization failed"),
331 )
332 .with_strict(true);
333
334 let func = def.to_openai_function();
335 assert_eq!(func["type"], "function");
336 assert_eq!(func["function"]["name"], "test");
337 assert_eq!(func["function"]["strict"], true);
338 }
339
340 #[test]
341 fn test_to_anthropic_tool() {
342 let def = ToolDefinition::new("test", "Test tool");
343 let tool = def.to_anthropic_tool();
344 assert_eq!(tool["name"], "test");
345 assert!(tool.get("input_schema").is_some());
346 }
347
348 #[test]
349 fn test_serde_roundtrip() {
350 let schema = ObjectJsonSchema::new()
351 .with_property("x", serde_json::json!({"type": "string"}), true)
352 .with_description("Test schema");
353
354 let json = serde_json::to_string(&schema).unwrap();
355 let parsed: ObjectJsonSchema = serde_json::from_str(&json).unwrap();
356 assert_eq!(schema, parsed);
357 }
358}