agent_stream_kit/
definition.rs

1use std::collections::HashMap;
2use std::ops::Not;
3
4use serde::{Deserialize, Serialize};
5
6use super::agent::Agent;
7use super::askit::ASKit;
8use super::config::AgentConfigs;
9use super::error::AgentError;
10use super::value::AgentValue;
11
12pub type AgentDefinitions = HashMap<String, AgentDefinition>;
13
14#[derive(Debug, Default, Serialize, Deserialize, Clone)]
15pub struct AgentDefinition {
16    pub kind: String,
17
18    pub name: String,
19
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub title: Option<String>,
22
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub description: Option<String>,
25
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub category: Option<String>,
28
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub inputs: Option<Vec<String>>,
31
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub outputs: Option<Vec<String>>,
34
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub default_configs: Option<AgentDefaultConfigs>,
37
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub global_configs: Option<AgentGlobalConfigs>,
40
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub display_configs: Option<AgentDisplayConfigs>,
43
44    #[serde(default, skip_serializing_if = "<&bool>::not")]
45    pub native_thread: bool,
46
47    #[serde(skip)]
48    pub new_boxed: Option<AgentNewBoxedFn>,
49}
50
51pub type AgentDefaultConfigs = Vec<(String, AgentConfigEntry)>;
52pub type AgentGlobalConfigs = Vec<(String, AgentConfigEntry)>;
53
54#[derive(Debug, Default, Serialize, Deserialize, Clone)]
55pub struct AgentConfigEntry {
56    pub value: AgentValue,
57
58    #[serde(rename = "type")]
59    pub type_: Option<String>,
60
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub title: Option<String>,
63
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub description: Option<String>,
66
67    /// Indicates whether this configuration entry should be hidden from the user interface.
68    /// If set to `true`, the entry will be hidden. The default behavior is to show the entry.
69    #[serde(default, skip_serializing_if = "<&bool>::not")]
70    pub hidden: bool,
71}
72
73pub type AgentDisplayConfigs = Vec<(String, AgentDisplayConfigEntry)>;
74
75#[derive(Debug, Default, Serialize, Deserialize, Clone)]
76pub struct AgentDisplayConfigEntry {
77    #[serde(rename = "type")]
78    pub type_: Option<String>,
79
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub title: Option<String>,
82
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub description: Option<String>,
85
86    #[serde(default, skip_serializing_if = "<&bool>::not")]
87    pub hide_title: bool,
88}
89
90// #[derive(Debug, Default, Serialize, Deserialize, Clone)]
91// pub struct CommandConfig {
92//     pub cmd: String,
93//     pub args: Option<Vec<String>>,
94
95//     pub dir: Option<String>,
96// }
97
98pub type AgentNewBoxedFn = fn(
99    askit: ASKit,
100    id: String,
101    def_name: String,
102    configs: Option<AgentConfigs>,
103) -> Result<Box<dyn Agent + Send + Sync>, AgentError>;
104
105impl AgentDefinition {
106    pub fn new(
107        kind: impl Into<String>,
108        name: impl Into<String>,
109        new_boxed: Option<AgentNewBoxedFn>,
110    ) -> Self {
111        Self {
112            kind: kind.into(),
113            name: name.into(),
114            new_boxed,
115            ..Default::default()
116        }
117    }
118
119    pub fn title(mut self, title: &str) -> Self {
120        self.title = Some(title.into());
121        self
122    }
123
124    pub fn description(mut self, description: &str) -> Self {
125        self.description = Some(description.into());
126        self
127    }
128
129    pub fn category(mut self, category: &str) -> Self {
130        self.category = Some(category.into());
131        self
132    }
133
134    pub fn inputs(mut self, inputs: Vec<&str>) -> Self {
135        self.inputs = Some(inputs.into_iter().map(|x| x.into()).collect());
136        self
137    }
138
139    pub fn outputs(mut self, outputs: Vec<&str>) -> Self {
140        self.outputs = Some(outputs.into_iter().map(|x| x.into()).collect());
141        self
142    }
143
144    // Default Configs
145
146    pub fn default_configs(mut self, configs: Vec<(&str, AgentConfigEntry)>) -> Self {
147        self.default_configs = Some(configs.into_iter().map(|(k, v)| (k.into(), v)).collect());
148        self
149    }
150
151    pub fn unit_config(self, key: &str) -> Self {
152        self.unit_config_with(key, |entry| entry)
153    }
154
155    pub fn unit_config_with<F>(self, key: &str, f: F) -> Self
156    where
157        F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
158    {
159        self.config_type_with(key, (), "unit", f)
160    }
161
162    pub fn boolean_config(self, key: &str, default: bool) -> Self {
163        self.boolean_config_with(key, default, |entry| entry)
164    }
165
166    pub fn boolean_config_with<F>(self, key: &str, default: bool, f: F) -> Self
167    where
168        F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
169    {
170        self.config_type_with(key, default, "boolean", f)
171    }
172
173    pub fn boolean_config_default(self, key: &str) -> Self {
174        self.boolean_config(key, false)
175    }
176
177    pub fn integer_config(self, key: &str, default: i64) -> Self {
178        self.integer_config_with(key, default, |entry| entry)
179    }
180
181    pub fn integer_config_with<F>(self, key: &str, default: i64, f: F) -> Self
182    where
183        F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
184    {
185        self.config_type_with(key, default, "integer", f)
186    }
187
188    pub fn integer_config_default(self, key: &str) -> Self {
189        self.integer_config(key, 0)
190    }
191
192    pub fn number_config(self, key: &str, default: f64) -> Self {
193        self.number_config_with(key, default, |entry| entry)
194    }
195
196    pub fn number_config_with<F>(self, key: &str, default: f64, f: F) -> Self
197    where
198        F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
199    {
200        self.config_type_with(key, default, "number", f)
201    }
202
203    pub fn number_config_default(self, key: &str) -> Self {
204        self.number_config(key, 0.0)
205    }
206
207    pub fn string_config(self, key: &str, default: impl Into<String>) -> Self {
208        self.string_config_with(key, default, |entry| entry)
209    }
210
211    pub fn string_config_with<F>(self, key: &str, default: impl Into<String>, f: F) -> Self
212    where
213        F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
214    {
215        let default = default.into();
216        self.config_type_with(key, AgentValue::string(default), "string", f)
217    }
218
219    pub fn string_config_default(self, key: &str) -> Self {
220        self.string_config(key, "")
221    }
222
223    pub fn text_config(self, key: &str, default: impl Into<String>) -> Self {
224        self.text_config_with(key, default, |entry| entry)
225    }
226
227    pub fn text_config_with<F>(self, key: &str, default: impl Into<String>, f: F) -> Self
228    where
229        F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
230    {
231        let default = default.into();
232        self.config_type_with(key, AgentValue::string(default), "text", f)
233    }
234
235    pub fn text_config_default(self, key: &str) -> Self {
236        self.text_config(key, "")
237    }
238
239    pub fn object_config<V: Into<AgentValue>>(self, key: &str, default: V) -> Self {
240        self.object_config_with(key, default, |entry| entry)
241    }
242
243    pub fn object_config_with<V: Into<AgentValue>, F>(self, key: &str, default: V, f: F) -> Self
244    where
245        F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
246    {
247        self.config_type_with(key, default, "object", f)
248    }
249
250    pub fn object_config_default(self, key: &str) -> Self {
251        self.object_config(key, AgentValue::object_default())
252    }
253
254    pub fn custom_config_with<V: Into<AgentValue>, F>(
255        self,
256        key: &str,
257        default: V,
258        type_: &str,
259        f: F,
260    ) -> Self
261    where
262        F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
263    {
264        self.config_type_with(key, default, type_, f)
265    }
266
267    fn config_type_with<V: Into<AgentValue>, F>(
268        mut self,
269        key: &str,
270        default: V,
271        type_: &str,
272        f: F,
273    ) -> Self
274    where
275        F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
276    {
277        let entry = AgentConfigEntry::new(default, type_);
278        self.push_default_config_entry(key.into(), f(entry));
279        self
280    }
281
282    fn push_default_config_entry(&mut self, key: String, entry: AgentConfigEntry) {
283        if let Some(configs) = self.default_configs.as_mut() {
284            configs.push((key, entry));
285        } else {
286            self.default_configs = Some(vec![(key, entry)]);
287        }
288    }
289
290    // Global Configs
291
292    pub fn global_configs(mut self, configs: Vec<(&str, AgentConfigEntry)>) -> Self {
293        self.global_configs = Some(configs.into_iter().map(|(k, v)| (k.into(), v)).collect());
294        self
295    }
296
297    pub fn unit_global_config(self, key: &str) -> Self {
298        self.unit_global_config_with(key, |entry| entry)
299    }
300
301    pub fn unit_global_config_with<F>(self, key: &str, f: F) -> Self
302    where
303        F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
304    {
305        self.global_config_type_with(key, (), "unit", f)
306    }
307
308    pub fn boolean_global_config(self, key: &str, default: bool) -> Self {
309        self.boolean_global_config_with(key, default, |entry| entry)
310    }
311
312    pub fn boolean_global_config_with<F>(self, key: &str, default: bool, f: F) -> Self
313    where
314        F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
315    {
316        self.global_config_type_with(key, default, "boolean", f)
317    }
318
319    pub fn integer_global_config(self, key: &str, default: i64) -> Self {
320        self.integer_global_config_with(key, default, |entry| entry)
321    }
322
323    pub fn integer_global_config_with<F>(self, key: &str, default: i64, f: F) -> Self
324    where
325        F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
326    {
327        self.global_config_type_with(key, default, "integer", f)
328    }
329
330    pub fn number_global_config(self, key: &str, default: f64) -> Self {
331        self.number_global_config_with(key, default, |entry| entry)
332    }
333
334    pub fn number_global_config_with<F>(self, key: &str, default: f64, f: F) -> Self
335    where
336        F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
337    {
338        self.global_config_type_with(key, default, "number", f)
339    }
340
341    pub fn string_global_config(self, key: &str, default: impl Into<String>) -> Self {
342        self.string_global_config_with(key, default, |entry| entry)
343    }
344
345    pub fn string_global_config_with<F>(self, key: &str, default: impl Into<String>, f: F) -> Self
346    where
347        F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
348    {
349        let default = default.into();
350        self.global_config_type_with(key, AgentValue::string(default), "string", f)
351    }
352
353    pub fn text_global_config(self, key: &str, default: impl Into<String>) -> Self {
354        self.text_global_config_with(key, default, |entry| entry)
355    }
356
357    pub fn text_global_config_with<F>(self, key: &str, default: impl Into<String>, f: F) -> Self
358    where
359        F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
360    {
361        let default = default.into();
362        self.global_config_type_with(key, AgentValue::string(default), "text", f)
363    }
364
365    pub fn object_global_config<V: Into<AgentValue>>(self, key: &str, default: V) -> Self {
366        self.object_global_config_with(key, default, |entry| entry)
367    }
368
369    pub fn object_global_config_with<V: Into<AgentValue>, F>(
370        self,
371        key: &str,
372        default: V,
373        f: F,
374    ) -> Self
375    where
376        F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
377    {
378        self.global_config_type_with(key, default, "object", f)
379    }
380
381    pub fn custom_global_config_with<V: Into<AgentValue>, F>(
382        self,
383        key: &str,
384        default: V,
385        type_: &str,
386        f: F,
387    ) -> Self
388    where
389        F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
390    {
391        self.global_config_type_with(key, default, type_, f)
392    }
393
394    fn global_config_type_with<V: Into<AgentValue>, F>(
395        mut self,
396        key: &str,
397        default: V,
398        type_: &str,
399        f: F,
400    ) -> Self
401    where
402        F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
403    {
404        let entry = AgentConfigEntry::new(default, type_);
405        self.push_global_config_entry(key.into(), f(entry));
406        self
407    }
408
409    fn push_global_config_entry(&mut self, key: String, entry: AgentConfigEntry) {
410        if let Some(configs) = self.global_configs.as_mut() {
411            configs.push((key, entry));
412        } else {
413            self.global_configs = Some(vec![(key, entry)]);
414        }
415    }
416
417    // Display Configs
418
419    pub fn display_configs(mut self, configs: Vec<(&str, AgentDisplayConfigEntry)>) -> Self {
420        self.display_configs = Some(configs.into_iter().map(|(k, v)| (k.into(), v)).collect());
421        self
422    }
423
424    pub fn unit_display_config(self, key: &str) -> Self {
425        self.unit_display_config_with(key, |entry| entry)
426    }
427
428    pub fn unit_display_config_with<F>(self, key: &str, f: F) -> Self
429    where
430        F: FnOnce(AgentDisplayConfigEntry) -> AgentDisplayConfigEntry,
431    {
432        self.display_config_type_with(key, "unit", f)
433    }
434
435    pub fn boolean_display_config(self, key: &str) -> Self {
436        self.boolean_display_config_with(key, |entry| entry)
437    }
438
439    pub fn boolean_display_config_with<F>(self, key: &str, f: F) -> Self
440    where
441        F: FnOnce(AgentDisplayConfigEntry) -> AgentDisplayConfigEntry,
442    {
443        self.display_config_type_with(key, "boolean", f)
444    }
445
446    pub fn integer_display_config(self, key: &str) -> Self {
447        self.integer_display_config_with(key, |entry| entry)
448    }
449
450    pub fn integer_display_config_with<F>(self, key: &str, f: F) -> Self
451    where
452        F: FnOnce(AgentDisplayConfigEntry) -> AgentDisplayConfigEntry,
453    {
454        self.display_config_type_with(key, "integer", f)
455    }
456
457    pub fn number_display_config(self, key: &str) -> Self {
458        self.number_display_config_with(key, |entry| entry)
459    }
460
461    pub fn number_display_config_with<F>(self, key: &str, f: F) -> Self
462    where
463        F: FnOnce(AgentDisplayConfigEntry) -> AgentDisplayConfigEntry,
464    {
465        self.display_config_type_with(key, "number", f)
466    }
467
468    pub fn string_display_config(self, key: &str) -> Self {
469        self.string_display_config_with(key, |entry| entry)
470    }
471
472    pub fn string_display_config_with<F>(self, key: &str, f: F) -> Self
473    where
474        F: FnOnce(AgentDisplayConfigEntry) -> AgentDisplayConfigEntry,
475    {
476        self.display_config_type_with(key, "string", f)
477    }
478
479    pub fn text_display_config(self, key: &str) -> Self {
480        self.text_display_config_with(key, |entry| entry)
481    }
482
483    pub fn text_display_config_with<F>(self, key: &str, f: F) -> Self
484    where
485        F: FnOnce(AgentDisplayConfigEntry) -> AgentDisplayConfigEntry,
486    {
487        self.display_config_type_with(key, "text", f)
488    }
489
490    pub fn object_display_config(self, key: &str) -> Self {
491        self.object_display_config_with(key, |entry| entry)
492    }
493
494    pub fn object_display_config_with<F>(self, key: &str, f: F) -> Self
495    where
496        F: FnOnce(AgentDisplayConfigEntry) -> AgentDisplayConfigEntry,
497    {
498        self.display_config_type_with(key, "object", f)
499    }
500
501    pub fn custom_display_config_with<F>(self, key: &str, type_: &str, f: F) -> Self
502    where
503        F: FnOnce(AgentDisplayConfigEntry) -> AgentDisplayConfigEntry,
504    {
505        self.display_config_type_with(key, type_, f)
506    }
507
508    fn display_config_type_with<F>(mut self, key: &str, type_: &str, f: F) -> Self
509    where
510        F: FnOnce(AgentDisplayConfigEntry) -> AgentDisplayConfigEntry,
511    {
512        let entry = AgentDisplayConfigEntry::new(type_);
513        self.push_display_config_entry(key.into(), f(entry));
514        self
515    }
516
517    fn push_display_config_entry(&mut self, key: String, entry: AgentDisplayConfigEntry) {
518        if let Some(configs) = self.display_configs.as_mut() {
519            configs.push((key, entry));
520        } else {
521            self.display_configs = Some(vec![(key, entry)]);
522        }
523    }
524
525    pub fn use_native_thread(mut self) -> Self {
526        self.native_thread = true;
527        self
528    }
529}
530
531impl AgentConfigEntry {
532    pub fn new<V: Into<AgentValue>>(value: V, type_: &str) -> Self {
533        Self {
534            value: value.into(),
535            type_: Some(type_.into()),
536            ..Default::default()
537        }
538    }
539
540    pub fn title(mut self, title: &str) -> Self {
541        self.title = Some(title.into());
542        self
543    }
544
545    pub fn description(mut self, description: &str) -> Self {
546        self.description = Some(description.into());
547        self
548    }
549
550    pub fn hidden(mut self) -> Self {
551        self.hidden = true;
552        self
553    }
554}
555
556impl AgentDisplayConfigEntry {
557    pub fn new(type_: &str) -> Self {
558        Self {
559            type_: Some(type_.into()),
560            ..Default::default()
561        }
562    }
563
564    pub fn hide_title(mut self) -> Self {
565        self.hide_title = true;
566        self
567    }
568
569    #[allow(unused)]
570    pub fn title(mut self, title: &str) -> Self {
571        self.title = Some(title.into());
572        self
573    }
574
575    #[allow(unused)]
576    pub fn description(mut self, description: &str) -> Self {
577        self.description = Some(description.into());
578        self
579    }
580}
581
582#[cfg(test)]
583mod tests {
584    use super::*;
585
586    #[test]
587    fn test_agent_definition() {
588        let def = AgentDefinition::default();
589        assert_eq!(def.name, "");
590    }
591
592    #[test]
593    fn test_agent_definition_new_default() {
594        let def = AgentDefinition::new(
595            "test",
596            "echo",
597            Some(|_app, _id, _def_name, _configs| {
598                Err(AgentError::NotImplemented("Echo agent".into()))
599            }),
600        );
601
602        assert_eq!(def.kind, "test");
603        assert_eq!(def.name, "echo");
604        assert!(def.title.is_none());
605        assert!(def.category.is_none());
606        assert!(def.inputs.is_none());
607        assert!(def.outputs.is_none());
608        assert!(def.display_configs.is_none());
609    }
610
611    #[test]
612    fn test_agent_definition_new() {
613        let def = echo_agent_definition();
614
615        assert_eq!(def.kind, "test");
616        assert_eq!(def.name, "echo");
617        assert_eq!(def.title.unwrap(), "Echo");
618        assert_eq!(def.category.unwrap(), "Test");
619        assert_eq!(def.inputs.unwrap(), vec!["in"]);
620        assert_eq!(def.outputs.unwrap(), vec!["out"]);
621        let display_configs = def.display_configs.unwrap();
622        assert_eq!(display_configs.len(), 2);
623        let entry = &display_configs[0];
624        assert_eq!(entry.0, "value");
625        assert_eq!(entry.1.type_.as_ref().unwrap(), "string");
626        assert_eq!(entry.1.title.as_ref().unwrap(), "display_title");
627        assert_eq!(entry.1.description.as_ref().unwrap(), "display_description");
628        assert_eq!(entry.1.hide_title, false);
629        let entry = &display_configs[1];
630        assert_eq!(entry.0, "hide_title_value");
631        assert_eq!(entry.1.type_.as_ref().unwrap(), "integer");
632        assert_eq!(entry.1.title, None);
633        assert_eq!(entry.1.description, None);
634        assert_eq!(entry.1.hide_title, true);
635    }
636
637    #[test]
638    fn test_serialize_agent_definition() {
639        let def = AgentDefinition::new(
640            "test",
641            "echo",
642            Some(|_app, _id, _def_name, _configs| {
643                Err(AgentError::NotImplemented("Echo agent".into()))
644            }),
645        );
646        let json = serde_json::to_string(&def).unwrap();
647        assert_eq!(json, r#"{"kind":"test","name":"echo"}"#);
648    }
649
650    #[test]
651    fn test_serialize_echo_agent_definition() {
652        let def = echo_agent_definition();
653        let json = serde_json::to_string(&def).unwrap();
654        print!("{}", json);
655        assert_eq!(
656            json,
657            r#"{"kind":"test","name":"echo","title":"Echo","category":"Test","inputs":["in"],"outputs":["out"],"display_configs":[["value",{"type":"string","title":"display_title","description":"display_description"}],["hide_title_value",{"type":"integer","hide_title":true}]]}"#
658        );
659    }
660
661    #[test]
662    fn test_deserialize_echo_agent_definition() {
663        let json = r#"{"kind":"test","name":"echo","title":"Echo","category":"Test","inputs":["in"],"outputs":["out"],"display_configs":[["value",{"type":"string","title":"display_title","description":"display_description"}],["hide_title_value",{"type":"integer","hide_title":true}]]}"#;
664        let def: AgentDefinition = serde_json::from_str(json).unwrap();
665        assert_eq!(def.kind, "test");
666        assert_eq!(def.name, "echo");
667        assert_eq!(def.title.unwrap(), "Echo");
668        assert_eq!(def.category.unwrap(), "Test");
669        assert_eq!(def.inputs.unwrap(), vec!["in"]);
670        assert_eq!(def.outputs.unwrap(), vec!["out"]);
671        let display_configs = def.display_configs.unwrap();
672        assert_eq!(display_configs.len(), 2);
673        let entry = &display_configs[0];
674        assert_eq!(entry.0, "value");
675        assert_eq!(entry.1.type_.as_ref().unwrap(), "string");
676        assert_eq!(entry.1.title.as_ref().unwrap(), "display_title");
677        assert_eq!(entry.1.description.as_ref().unwrap(), "display_description");
678        assert_eq!(entry.1.hide_title, false);
679        let entry = &display_configs[1];
680        assert_eq!(entry.0, "hide_title_value");
681        assert_eq!(entry.1.type_.as_ref().unwrap(), "integer");
682        assert_eq!(entry.1.title, None);
683        assert_eq!(entry.1.description, None);
684        assert_eq!(entry.1.hide_title, true);
685    }
686
687    #[test]
688    fn test_default_config_helpers() {
689        let custom_object_value =
690            AgentValue::object([("key".to_string(), AgentValue::string("value"))].into());
691
692        let def = AgentDefinition::new("test", "helpers", None)
693            .unit_config("unit_value")
694            .boolean_config_default("boolean_value")
695            .boolean_config("boolean_custom", true)
696            .integer_config_default("integer_value")
697            .integer_config("integer_custom", 42)
698            .number_config_default("number_value")
699            .number_config("number_custom", 1.5)
700            .string_config_default("string_default")
701            .string_config("string_value", "value")
702            .text_config_default("text_value")
703            .text_config("text_custom", "custom")
704            .object_config_default("object_value")
705            .object_config("object_custom", custom_object_value.clone());
706
707        let configs = def
708            .default_configs
709            .clone()
710            .expect("default configs should exist");
711        assert_eq!(configs.len(), 13);
712        let config_map: std::collections::HashMap<_, _> = configs.into_iter().collect();
713
714        let unit_entry = config_map.get("unit_value").unwrap();
715        assert_eq!(unit_entry.type_.as_deref(), Some("unit"));
716        assert_eq!(unit_entry.value, AgentValue::unit());
717
718        let boolean_entry = config_map.get("boolean_value").unwrap();
719        assert_eq!(boolean_entry.type_.as_deref(), Some("boolean"));
720        assert_eq!(boolean_entry.value, AgentValue::boolean(false));
721
722        let boolean_custom_entry = config_map.get("boolean_custom").unwrap();
723        assert_eq!(boolean_custom_entry.type_.as_deref(), Some("boolean"));
724        assert_eq!(boolean_custom_entry.value, AgentValue::boolean(true));
725
726        let integer_entry = config_map.get("integer_value").unwrap();
727        assert_eq!(integer_entry.type_.as_deref(), Some("integer"));
728        assert_eq!(integer_entry.value, AgentValue::integer(0));
729
730        let integer_custom_entry = config_map.get("integer_custom").unwrap();
731        assert_eq!(integer_custom_entry.type_.as_deref(), Some("integer"));
732        assert_eq!(integer_custom_entry.value, AgentValue::integer(42));
733
734        let number_entry = config_map.get("number_value").unwrap();
735        assert_eq!(number_entry.type_.as_deref(), Some("number"));
736        assert_eq!(number_entry.value, AgentValue::number(0.0));
737
738        let number_custom_entry = config_map.get("number_custom").unwrap();
739        assert_eq!(number_custom_entry.type_.as_deref(), Some("number"));
740        assert_eq!(number_custom_entry.value, AgentValue::number(1.5));
741
742        let string_default_entry = config_map.get("string_default").unwrap();
743        assert_eq!(string_default_entry.type_.as_deref(), Some("string"));
744        assert_eq!(string_default_entry.value, AgentValue::string(""));
745
746        let string_entry = config_map.get("string_value").unwrap();
747        assert_eq!(string_entry.type_.as_deref(), Some("string"));
748        assert_eq!(string_entry.value, AgentValue::string("value"));
749
750        let text_entry = config_map.get("text_value").unwrap();
751        assert_eq!(text_entry.type_.as_deref(), Some("text"));
752        assert_eq!(text_entry.value, AgentValue::string(""));
753
754        let text_custom_entry = config_map.get("text_custom").unwrap();
755        assert_eq!(text_custom_entry.type_.as_deref(), Some("text"));
756        assert_eq!(text_custom_entry.value, AgentValue::string("custom"));
757
758        let object_entry = config_map.get("object_value").unwrap();
759        assert_eq!(object_entry.type_.as_deref(), Some("object"));
760        assert_eq!(object_entry.value, AgentValue::object_default());
761
762        let object_custom_entry = config_map.get("object_custom").unwrap();
763        assert_eq!(object_custom_entry.type_.as_deref(), Some("object"));
764        assert_eq!(object_custom_entry.value, custom_object_value);
765    }
766
767    #[test]
768    fn test_global_config_helpers() {
769        let custom_object_value =
770            AgentValue::object([("key".to_string(), AgentValue::string("value"))].into());
771
772        let def = AgentDefinition::new("test", "helpers", None)
773            .unit_global_config("global_unit")
774            .boolean_global_config("global_boolean", true)
775            .integer_global_config("global_integer", 42)
776            .number_global_config("global_number", 1.5)
777            .string_global_config("global_string", "value")
778            .text_global_config("global_text", "global")
779            .object_global_config("global_object", custom_object_value.clone());
780
781        let global_configs = def.global_configs.expect("global configs should exist");
782        assert_eq!(global_configs.len(), 7);
783        let config_map: std::collections::HashMap<_, _> = global_configs.into_iter().collect();
784
785        let entry = config_map.get("global_unit").unwrap();
786        assert_eq!(entry.type_.as_deref(), Some("unit"));
787        assert_eq!(entry.value, AgentValue::unit());
788
789        let entry = config_map.get("global_boolean").unwrap();
790        assert_eq!(entry.type_.as_deref(), Some("boolean"));
791        assert_eq!(entry.value, AgentValue::boolean(true));
792
793        let entry = config_map.get("global_integer").unwrap();
794        assert_eq!(entry.type_.as_deref(), Some("integer"));
795        assert_eq!(entry.value, AgentValue::integer(42));
796
797        let entry = config_map.get("global_number").unwrap();
798        assert_eq!(entry.type_.as_deref(), Some("number"));
799        assert_eq!(entry.value, AgentValue::number(1.5));
800
801        let entry = config_map.get("global_string").unwrap();
802        assert_eq!(entry.type_.as_deref(), Some("string"));
803        assert_eq!(entry.value, AgentValue::string("value"));
804
805        let entry = config_map.get("global_text").unwrap();
806        assert_eq!(entry.type_.as_deref(), Some("text"));
807        assert_eq!(entry.value, AgentValue::string("global"));
808
809        let entry = config_map.get("global_object").unwrap();
810        assert_eq!(entry.type_.as_deref(), Some("object"));
811        assert_eq!(entry.value, custom_object_value);
812    }
813
814    #[test]
815    fn test_display_config_helpers() {
816        let def = AgentDefinition::new("test", "helpers", None)
817            .unit_display_config("display_unit")
818            .boolean_display_config("display_boolean")
819            .integer_display_config("display_integer")
820            .number_display_config("display_number")
821            .string_display_config("display_string")
822            .text_display_config("display_text")
823            .object_display_config("display_object");
824
825        let display_configs = def.display_configs.expect("display configs should exist");
826        assert_eq!(display_configs.len(), 7);
827        let config_map: std::collections::HashMap<_, _> = display_configs.into_iter().collect();
828
829        assert_eq!(
830            config_map.get("display_unit").unwrap().type_.as_deref(),
831            Some("unit")
832        );
833        assert_eq!(
834            config_map.get("display_boolean").unwrap().type_.as_deref(),
835            Some("boolean")
836        );
837        assert_eq!(
838            config_map.get("display_integer").unwrap().type_.as_deref(),
839            Some("integer")
840        );
841        assert_eq!(
842            config_map.get("display_number").unwrap().type_.as_deref(),
843            Some("number")
844        );
845        assert_eq!(
846            config_map.get("display_string").unwrap().type_.as_deref(),
847            Some("string")
848        );
849        assert_eq!(
850            config_map.get("display_text").unwrap().type_.as_deref(),
851            Some("text")
852        );
853        assert_eq!(
854            config_map.get("display_object").unwrap().type_.as_deref(),
855            Some("object")
856        );
857
858        for entry in config_map.values() {
859            assert!(!entry.hide_title);
860        }
861    }
862
863    #[test]
864    fn test_config_helper_customization() {
865        let def = AgentDefinition::new("test", "custom", None)
866            .integer_config_with("custom_default", 1, |entry| entry.title("Custom"))
867            .text_global_config_with("custom_global", "value", |entry| {
868                entry.description("Global Desc")
869            })
870            .text_display_config_with("custom_display", |entry| entry.title("Display"));
871
872        let default_entry = def
873            .default_configs
874            .as_ref()
875            .unwrap()
876            .iter()
877            .find(|(k, _)| k == "custom_default")
878            .map(|(_, v)| v)
879            .unwrap();
880        assert_eq!(default_entry.title.as_deref(), Some("Custom"));
881
882        let global_entry = def
883            .global_configs
884            .as_ref()
885            .unwrap()
886            .iter()
887            .find(|(k, _)| k == "custom_global")
888            .map(|(_, v)| v)
889            .unwrap();
890        assert_eq!(global_entry.description.as_deref(), Some("Global Desc"));
891
892        let display_entry = def
893            .display_configs
894            .as_ref()
895            .unwrap()
896            .iter()
897            .find(|(k, _)| k == "custom_display")
898            .map(|(_, v)| v)
899            .unwrap();
900        assert_eq!(display_entry.title.as_deref(), Some("Display"));
901    }
902
903    fn echo_agent_definition() -> AgentDefinition {
904        AgentDefinition::new(
905            "test",
906            "echo",
907            Some(|_app, _id, _def_name, _configs| {
908                Err(AgentError::NotImplemented("Echo agent".into()))
909            }),
910        )
911        .title("Echo")
912        .category("Test")
913        .inputs(vec!["in"])
914        .outputs(vec!["out"])
915        .string_display_config_with("value", |entry| {
916            entry
917                .title("display_title")
918                .description("display_description")
919        })
920        .integer_display_config_with("hide_title_value", |entry| entry.hide_title())
921    }
922}