agent_stream_kit/
definition.rs

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