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            #[allow(deprecated)]
414            enabled: false,
415            disabled: false,
416            extensions: FnvIndexMap::default(),
417        }
418    }
419}
420
421impl AgentConfigSpec {
422    pub fn new<V: Into<AgentValue>>(value: V, type_: &str) -> Self {
423        Self {
424            value: value.into(),
425            type_: Some(type_.into()),
426            ..Default::default()
427        }
428    }
429
430    pub fn title(mut self, title: &str) -> Self {
431        self.title = Some(title.into());
432        self
433    }
434
435    pub fn hide_title(mut self) -> Self {
436        self.hide_title = true;
437        self
438    }
439
440    pub fn description(mut self, description: &str) -> Self {
441        self.description = Some(description.into());
442        self
443    }
444
445    pub fn hidden(mut self) -> Self {
446        self.hidden = true;
447        self
448    }
449
450    pub fn readonly(mut self) -> Self {
451        self.readonly = true;
452        self
453    }
454}
455
456#[cfg(test)]
457mod tests {
458    use super::*;
459
460    #[test]
461    fn test_agent_definition() {
462        let def = AgentDefinition::default();
463        assert_eq!(def.name, "");
464    }
465
466    #[test]
467    fn test_agent_definition_new_default() {
468        let def = AgentDefinition::new(
469            "test",
470            "echo",
471            Some(|_app, _id, _spec| Err(AgentError::NotImplemented("Echo agent".into()))),
472        );
473
474        assert_eq!(def.kind, "test");
475        assert_eq!(def.name, "echo");
476        assert!(def.title.is_none());
477        assert!(def.category.is_none());
478        assert!(def.inputs.is_none());
479        assert!(def.outputs.is_none());
480        assert!(def.configs.is_none());
481    }
482
483    #[test]
484    fn test_agent_definition_new() {
485        let def = echo_agent_definition();
486
487        assert_eq!(def.kind, "test");
488        assert_eq!(def.name, "echo");
489        assert_eq!(def.title.unwrap(), "Echo");
490        assert_eq!(def.category.unwrap(), "Test");
491        assert_eq!(def.inputs.unwrap(), vec!["in"]);
492        assert_eq!(def.outputs.unwrap(), vec!["out"]);
493        let default_configs = def.configs.unwrap();
494        assert_eq!(default_configs.len(), 2);
495        let entry = default_configs.get("value").unwrap();
496        assert_eq!(entry.value, AgentValue::string("abc"));
497        assert_eq!(entry.type_.as_ref().unwrap(), "string");
498        assert_eq!(entry.title.as_ref().unwrap(), "display_title");
499        assert_eq!(entry.description.as_ref().unwrap(), "display_description");
500        assert_eq!(entry.hide_title, false);
501        assert_eq!(entry.readonly, true);
502        let entry = default_configs.get("hide_title_value").unwrap();
503        assert_eq!(entry.value, AgentValue::integer(1));
504        assert_eq!(entry.type_.as_ref().unwrap(), "integer");
505        assert_eq!(entry.title, None);
506        assert_eq!(entry.description, None);
507        assert_eq!(entry.hide_title, true);
508        assert_eq!(entry.readonly, true);
509    }
510
511    #[test]
512    fn test_serialize_agent_definition() {
513        let def = AgentDefinition::new(
514            "test",
515            "echo",
516            Some(|_app, _id, _spec| Err(AgentError::NotImplemented("Echo agent".into()))),
517        );
518        let json = serde_json::to_string(&def).unwrap();
519        assert_eq!(json, r#"{"kind":"test","name":"echo"}"#);
520    }
521
522    #[test]
523    fn test_serialize_echo_agent_definition() {
524        let def = echo_agent_definition();
525        let json = serde_json::to_string(&def).unwrap();
526        print!("{}", json);
527        assert_eq!(
528            json,
529            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}}}"#
530        );
531    }
532
533    #[test]
534    fn test_deserialize_echo_agent_definition() {
535        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}}}"#;
536        let def: AgentDefinition = serde_json::from_str(json).unwrap();
537        assert_eq!(def.kind, "test");
538        assert_eq!(def.name, "echo");
539        assert_eq!(def.title.unwrap(), "Echo");
540        assert_eq!(def.category.unwrap(), "Test");
541        assert_eq!(def.inputs.unwrap(), vec!["in"]);
542        assert_eq!(def.outputs.unwrap(), vec!["out"]);
543        let default_configs = def.configs.unwrap();
544        assert_eq!(default_configs.len(), 2);
545        let (key, entry) = default_configs.get_index(0).unwrap();
546        assert_eq!(key, "value");
547        assert_eq!(entry.type_.as_ref().unwrap(), "string");
548        assert_eq!(entry.title.as_ref().unwrap(), "display_title");
549        assert_eq!(entry.description.as_ref().unwrap(), "display_description");
550        assert_eq!(entry.hide_title, false);
551        let (key, entry) = default_configs.get_index(1).unwrap();
552        assert_eq!(key, "hide_title_value");
553        assert_eq!(entry.type_.as_ref().unwrap(), "integer");
554        assert_eq!(entry.title, None);
555        assert_eq!(entry.description, None);
556        assert_eq!(entry.hide_title, true);
557    }
558
559    #[test]
560    fn test_default_config_helpers() {
561        let custom_object_value =
562            AgentValue::object([("key".to_string(), AgentValue::string("value"))].into());
563
564        let def = AgentDefinition::new("test", "helpers", None)
565            .unit_config("unit_value")
566            .boolean_config_default("boolean_value")
567            .boolean_config("boolean_custom", true)
568            .integer_config_default("integer_value")
569            .integer_config("integer_custom", 42)
570            .number_config_default("number_value")
571            .number_config("number_custom", 1.5)
572            .string_config_default("string_default")
573            .string_config("string_value", "value")
574            .text_config_default("text_value")
575            .text_config("text_custom", "custom")
576            .object_config_default("object_value")
577            .object_config("object_custom", custom_object_value.clone());
578
579        let configs = def.configs.clone().expect("default configs should exist");
580        assert_eq!(configs.len(), 13);
581        let config_map: std::collections::HashMap<_, _> = configs.into_iter().collect();
582
583        let unit_entry = config_map.get("unit_value").unwrap();
584        assert_eq!(unit_entry.type_.as_deref(), Some("unit"));
585        assert_eq!(unit_entry.value, AgentValue::unit());
586
587        let boolean_entry = config_map.get("boolean_value").unwrap();
588        assert_eq!(boolean_entry.type_.as_deref(), Some("boolean"));
589        assert_eq!(boolean_entry.value, AgentValue::boolean(false));
590
591        let boolean_custom_entry = config_map.get("boolean_custom").unwrap();
592        assert_eq!(boolean_custom_entry.type_.as_deref(), Some("boolean"));
593        assert_eq!(boolean_custom_entry.value, AgentValue::boolean(true));
594
595        let integer_entry = config_map.get("integer_value").unwrap();
596        assert_eq!(integer_entry.type_.as_deref(), Some("integer"));
597        assert_eq!(integer_entry.value, AgentValue::integer(0));
598
599        let integer_custom_entry = config_map.get("integer_custom").unwrap();
600        assert_eq!(integer_custom_entry.type_.as_deref(), Some("integer"));
601        assert_eq!(integer_custom_entry.value, AgentValue::integer(42));
602
603        let number_entry = config_map.get("number_value").unwrap();
604        assert_eq!(number_entry.type_.as_deref(), Some("number"));
605        assert_eq!(number_entry.value, AgentValue::number(0.0));
606
607        let number_custom_entry = config_map.get("number_custom").unwrap();
608        assert_eq!(number_custom_entry.type_.as_deref(), Some("number"));
609        assert_eq!(number_custom_entry.value, AgentValue::number(1.5));
610
611        let string_default_entry = config_map.get("string_default").unwrap();
612        assert_eq!(string_default_entry.type_.as_deref(), Some("string"));
613        assert_eq!(string_default_entry.value, AgentValue::string(""));
614
615        let string_entry = config_map.get("string_value").unwrap();
616        assert_eq!(string_entry.type_.as_deref(), Some("string"));
617        assert_eq!(string_entry.value, AgentValue::string("value"));
618
619        let text_entry = config_map.get("text_value").unwrap();
620        assert_eq!(text_entry.type_.as_deref(), Some("text"));
621        assert_eq!(text_entry.value, AgentValue::string(""));
622
623        let text_custom_entry = config_map.get("text_custom").unwrap();
624        assert_eq!(text_custom_entry.type_.as_deref(), Some("text"));
625        assert_eq!(text_custom_entry.value, AgentValue::string("custom"));
626
627        let object_entry = config_map.get("object_value").unwrap();
628        assert_eq!(object_entry.type_.as_deref(), Some("object"));
629        assert_eq!(object_entry.value, AgentValue::object_default());
630
631        let object_custom_entry = config_map.get("object_custom").unwrap();
632        assert_eq!(object_custom_entry.type_.as_deref(), Some("object"));
633        assert_eq!(object_custom_entry.value, custom_object_value);
634    }
635
636    #[test]
637    fn test_global_config_helpers() {
638        let custom_object_value =
639            AgentValue::object([("key".to_string(), AgentValue::string("value"))].into());
640
641        let def = AgentDefinition::new("test", "helpers", None)
642            .unit_global_config("global_unit")
643            .boolean_global_config("global_boolean", true)
644            .integer_global_config("global_integer", 42)
645            .number_global_config("global_number", 1.5)
646            .string_global_config("global_string", "value")
647            .text_global_config("global_text", "global")
648            .object_global_config("global_object", custom_object_value.clone());
649
650        let global_configs = def.global_configs.expect("global configs should exist");
651        assert_eq!(global_configs.len(), 7);
652        let config_map: std::collections::HashMap<_, _> = global_configs.into_iter().collect();
653
654        let entry = config_map.get("global_unit").unwrap();
655        assert_eq!(entry.type_.as_deref(), Some("unit"));
656        assert_eq!(entry.value, AgentValue::unit());
657
658        let entry = config_map.get("global_boolean").unwrap();
659        assert_eq!(entry.type_.as_deref(), Some("boolean"));
660        assert_eq!(entry.value, AgentValue::boolean(true));
661
662        let entry = config_map.get("global_integer").unwrap();
663        assert_eq!(entry.type_.as_deref(), Some("integer"));
664        assert_eq!(entry.value, AgentValue::integer(42));
665
666        let entry = config_map.get("global_number").unwrap();
667        assert_eq!(entry.type_.as_deref(), Some("number"));
668        assert_eq!(entry.value, AgentValue::number(1.5));
669
670        let entry = config_map.get("global_string").unwrap();
671        assert_eq!(entry.type_.as_deref(), Some("string"));
672        assert_eq!(entry.value, AgentValue::string("value"));
673
674        let entry = config_map.get("global_text").unwrap();
675        assert_eq!(entry.type_.as_deref(), Some("text"));
676        assert_eq!(entry.value, AgentValue::string("global"));
677
678        let entry = config_map.get("global_object").unwrap();
679        assert_eq!(entry.type_.as_deref(), Some("object"));
680        assert_eq!(entry.value, custom_object_value);
681    }
682
683    #[test]
684    fn test_config_helper_customization() {
685        let def = AgentDefinition::new("test", "custom", None)
686            .integer_config_with("custom_default", 1, |entry| entry.title("Custom"))
687            .text_global_config_with("custom_global", "value", |entry| {
688                entry.description("Global Desc")
689            });
690        // .text_display_config_with("custom_display", |entry| entry.title("Display"));
691
692        let default_entry = def.configs.as_ref().unwrap().get("custom_default").unwrap();
693        assert_eq!(default_entry.title.as_deref(), Some("Custom"));
694
695        let global_entry = def
696            .global_configs
697            .as_ref()
698            .unwrap()
699            .get("custom_global")
700            .unwrap();
701        assert_eq!(global_entry.description.as_deref(), Some("Global Desc"));
702    }
703
704    fn echo_agent_definition() -> AgentDefinition {
705        AgentDefinition::new(
706            "test",
707            "echo",
708            Some(|_app, _id, _spec| Err(AgentError::NotImplemented("Echo agent".into()))),
709        )
710        .title("Echo")
711        .category("Test")
712        .inputs(vec!["in"])
713        .outputs(vec!["out"])
714        .string_config_with("value", "abc", |entry| {
715            entry
716                .title("display_title")
717                .description("display_description")
718                .readonly()
719        })
720        .integer_config_with("hide_title_value", 1, |entry| entry.hide_title().readonly())
721    }
722}