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