1use std::collections::{HashMap, HashSet};
4
5use crate::value::Value;
6
7#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
9pub struct ParamSchema {
10 pub name: String,
12 pub param_type: String,
14 pub required: bool,
16 pub default: Option<Value>,
18 pub description: String,
20 pub aliases: Vec<String>,
22}
23
24impl ParamSchema {
25 pub fn required(name: impl Into<String>, param_type: impl Into<String>, description: impl Into<String>) -> Self {
27 Self {
28 name: name.into(),
29 param_type: param_type.into(),
30 required: true,
31 default: None,
32 description: description.into(),
33 aliases: Vec::new(),
34 }
35 }
36
37 pub fn optional(name: impl Into<String>, param_type: impl Into<String>, default: Value, description: impl Into<String>) -> Self {
39 Self {
40 name: name.into(),
41 param_type: param_type.into(),
42 required: false,
43 default: Some(default),
44 description: description.into(),
45 aliases: Vec::new(),
46 }
47 }
48
49 pub fn with_aliases(mut self, aliases: impl IntoIterator<Item = impl Into<String>>) -> Self {
53 self.aliases = aliases.into_iter().map(Into::into).collect();
54 self
55 }
56
57 pub fn matches_flag(&self, flag: &str) -> bool {
59 if self.name == flag {
60 return true;
61 }
62 self.aliases.iter().any(|a| a == flag)
63 }
64}
65
66#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
68pub struct Example {
69 pub description: String,
71 pub code: String,
73}
74
75impl Example {
76 pub fn new(description: impl Into<String>, code: impl Into<String>) -> Self {
78 Self {
79 description: description.into(),
80 code: code.into(),
81 }
82 }
83}
84
85#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
87pub struct ToolSchema {
88 pub name: String,
90 pub description: String,
92 pub params: Vec<ParamSchema>,
94 pub examples: Vec<Example>,
96 pub map_positionals: bool,
100}
101
102impl ToolSchema {
103 pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
105 Self {
106 name: name.into(),
107 description: description.into(),
108 params: Vec::new(),
109 examples: Vec::new(),
110 map_positionals: false,
111 }
112 }
113
114 pub fn with_positional_mapping(mut self) -> Self {
116 self.map_positionals = true;
117 self
118 }
119
120 pub fn param(mut self, param: ParamSchema) -> Self {
122 self.params.push(param);
123 self
124 }
125
126 pub fn example(mut self, description: impl Into<String>, code: impl Into<String>) -> Self {
128 self.examples.push(Example::new(description, code));
129 self
130 }
131}
132
133#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
135pub struct ToolArgs {
136 pub positional: Vec<Value>,
138 pub named: HashMap<String, Value>,
140 pub flags: HashSet<String>,
142}
143
144impl ToolArgs {
145 pub fn new() -> Self {
147 Self::default()
148 }
149
150 pub fn get_positional(&self, index: usize) -> Option<&Value> {
152 self.positional.get(index)
153 }
154
155 pub fn get_named(&self, key: &str) -> Option<&Value> {
157 self.named.get(key)
158 }
159
160 pub fn get(&self, name: &str, positional_index: usize) -> Option<&Value> {
164 self.named.get(name).or_else(|| self.positional.get(positional_index))
165 }
166
167 pub fn get_string(&self, name: &str, positional_index: usize) -> Option<String> {
169 self.get(name, positional_index).and_then(|v| match v {
170 Value::String(s) => Some(s.clone()),
171 Value::Int(i) => Some(i.to_string()),
172 Value::Float(f) => Some(f.to_string()),
173 Value::Bool(b) => Some(b.to_string()),
174 _ => None,
175 })
176 }
177
178 pub fn get_bool(&self, name: &str, positional_index: usize) -> Option<bool> {
180 self.get(name, positional_index).and_then(|v| match v {
181 Value::Bool(b) => Some(*b),
182 Value::String(s) => match s.as_str() {
183 "true" | "yes" | "1" => Some(true),
184 "false" | "no" | "0" => Some(false),
185 _ => None,
186 },
187 Value::Int(i) => Some(*i != 0),
188 _ => None,
189 })
190 }
191
192 pub fn has_flag(&self, name: &str) -> bool {
194 if self.flags.contains(name) {
196 return true;
197 }
198 self.named.get(name).is_some_and(|v| match v {
200 Value::Bool(b) => *b,
201 Value::String(s) => !s.is_empty() && s != "false" && s != "0",
202 _ => true,
203 })
204 }
205}