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