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 #[serde(default, skip_serializing_if = "<&bool>::not")]
71 pub hidden: bool,
72
73 #[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 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 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 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}