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