sgr_agent/
schema_simplifier.rs1use serde_json::Value;
7
8pub fn simplify(schema: &Value) -> String {
13 let mut lines = Vec::new();
14 simplify_object(schema, &mut lines, 0);
15 lines.join("\n")
16}
17
18fn simplify_object(schema: &Value, lines: &mut Vec<String>, indent: usize) {
19 let required: Vec<&str> = schema
20 .get("required")
21 .and_then(|r| r.as_array())
22 .map(|arr| arr.iter().filter_map(|v| v.as_str()).collect())
23 .unwrap_or_default();
24
25 let properties = match schema.get("properties").and_then(|p| p.as_object()) {
26 Some(p) => p,
27 None => return,
28 };
29
30 let prefix = " ".repeat(indent);
31 for (name, prop) in properties {
32 let req_label = if required.contains(&name.as_str()) {
33 "required"
34 } else {
35 "optional"
36 };
37
38 let type_str = format_type(prop);
39 let constraints = format_constraints(prop);
40 let desc = prop
41 .get("description")
42 .and_then(|d| d.as_str())
43 .unwrap_or("");
44
45 let mut parts = vec![req_label.to_string(), type_str];
46 if !constraints.is_empty() {
47 parts.push(constraints);
48 }
49 let suffix = if desc.is_empty() {
50 String::new()
51 } else {
52 format!(": {}", desc)
53 };
54
55 lines.push(format!(
56 "{}- {} ({}){}",
57 prefix,
58 name,
59 parts.join(", "),
60 suffix
61 ));
62
63 if prop.get("type").and_then(|t| t.as_str()) == Some("object") {
65 simplify_object(prop, lines, indent + 1);
66 }
67
68 if prop.get("type").and_then(|t| t.as_str()) == Some("array")
70 && let Some(items) = prop.get("items")
71 && items.get("type").and_then(|t| t.as_str()) == Some("object")
72 {
73 let item_prefix = " ".repeat(indent + 1);
74 lines.push(format!("{} Each item:", item_prefix));
75 simplify_object(items, lines, indent + 2);
76 }
77 }
78}
79
80fn format_type(prop: &Value) -> String {
81 match prop.get("type").and_then(|t| t.as_str()) {
82 Some("array") => {
83 let item_type = prop
84 .get("items")
85 .and_then(|i| i.get("type"))
86 .and_then(|t| t.as_str())
87 .unwrap_or("any");
88 format!("array of {}", item_type)
89 }
90 Some(t) => t.to_string(),
91 None => {
92 if prop.get("enum").is_some() {
94 "enum".to_string()
95 } else if prop.get("oneOf").is_some() || prop.get("anyOf").is_some() {
96 "union".to_string()
97 } else {
98 "any".to_string()
99 }
100 }
101 }
102}
103
104fn format_constraints(prop: &Value) -> String {
105 let mut parts = Vec::new();
106
107 if let Some(Value::Array(variants)) = prop.get("enum") {
108 let vals: Vec<String> = variants
109 .iter()
110 .map(|v| match v {
111 Value::String(s) => format!("\"{}\"", s),
112 _ => v.to_string(),
113 })
114 .collect();
115 parts.push(format!("one of: {}", vals.join(" | ")));
116 }
117
118 if let Some(min) = prop.get("minimum") {
119 parts.push(format!("min: {}", min));
120 }
121 if let Some(max) = prop.get("maximum") {
122 parts.push(format!("max: {}", max));
123 }
124 if let Some(min) = prop.get("minLength") {
125 parts.push(format!("minLength: {}", min));
126 }
127 if let Some(max) = prop.get("maxLength") {
128 parts.push(format!("maxLength: {}", max));
129 }
130 if let Some(pat) = prop.get("pattern").and_then(|p| p.as_str()) {
131 parts.push(format!("pattern: {}", pat));
132 }
133 if let Some(def) = prop.get("default") {
134 parts.push(format!("default: {}", def));
135 }
136
137 parts.join(", ")
138}
139
140pub fn simplify_tool(name: &str, description: &str, schema: &Value) -> String {
142 let params = simplify(schema);
143 if params.is_empty() {
144 format!("### {}\n{}\nNo parameters.", name, description)
145 } else {
146 format!("### {}\n{}\nParameters:\n{}", name, description, params)
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153 use serde_json::json;
154
155 #[test]
156 fn simplify_basic_object() {
157 let schema = json!({
158 "type": "object",
159 "properties": {
160 "path": { "type": "string", "description": "File path to read" },
161 "line": { "type": "integer", "description": "Start line" }
162 },
163 "required": ["path"]
164 });
165 let result = simplify(&schema);
166 assert!(result.contains("- path (required, string): File path to read"));
167 assert!(result.contains("- line (optional, integer): Start line"));
168 }
169
170 #[test]
171 fn simplify_with_enum() {
172 let schema = json!({
173 "type": "object",
174 "properties": {
175 "mode": {
176 "type": "string",
177 "enum": ["read", "write", "append"],
178 "description": "File open mode"
179 }
180 },
181 "required": ["mode"]
182 });
183 let result = simplify(&schema);
184 assert!(result.contains("one of:"));
185 assert!(result.contains("\"read\""));
186 }
187
188 #[test]
189 fn simplify_nested_object() {
190 let schema = json!({
191 "type": "object",
192 "properties": {
193 "config": {
194 "type": "object",
195 "properties": {
196 "timeout": { "type": "integer", "description": "Timeout in ms" }
197 },
198 "required": ["timeout"]
199 }
200 }
201 });
202 let result = simplify(&schema);
203 assert!(result.contains("- config (optional, object)"));
204 assert!(result.contains(" - timeout (required, integer): Timeout in ms"));
205 }
206
207 #[test]
208 fn simplify_array_type() {
209 let schema = json!({
210 "type": "object",
211 "properties": {
212 "tags": {
213 "type": "array",
214 "items": { "type": "string" },
215 "description": "List of tags"
216 }
217 }
218 });
219 let result = simplify(&schema);
220 assert!(result.contains("array of string"));
221 }
222
223 #[test]
224 fn simplify_constraints() {
225 let schema = json!({
226 "type": "object",
227 "properties": {
228 "count": {
229 "type": "integer",
230 "minimum": 1,
231 "maximum": 100,
232 "description": "Item count"
233 }
234 },
235 "required": ["count"]
236 });
237 let result = simplify(&schema);
238 assert!(result.contains("min: 1"));
239 assert!(result.contains("max: 100"));
240 }
241
242 #[test]
243 fn simplify_empty_schema() {
244 let schema = json!({"type": "object"});
245 let result = simplify(&schema);
246 assert!(result.is_empty());
247 }
248
249 #[test]
250 fn simplify_tool_full() {
251 let schema = json!({
252 "type": "object",
253 "properties": {
254 "command": { "type": "string", "description": "Shell command" }
255 },
256 "required": ["command"]
257 });
258 let result = simplify_tool("bash", "Run a shell command", &schema);
259 assert!(result.contains("### bash"));
260 assert!(result.contains("Run a shell command"));
261 assert!(result.contains("- command (required, string): Shell command"));
262 }
263}