1use std::collections::HashMap;
2use std::ops::Not;
3
4use serde::{Deserialize, Serialize};
5
6use super::agent::Agent;
7use super::askit::ASKit;
8use super::config::AgentConfigs;
9use super::error::AgentError;
10use super::value::AgentValue;
11
12pub type AgentDefinitions = HashMap<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 default_configs: Option<AgentDefaultConfigs>,
37
38 #[serde(skip_serializing_if = "Option::is_none")]
39 pub global_configs: Option<AgentGlobalConfigs>,
40
41 #[serde(skip_serializing_if = "Option::is_none")]
42 pub display_configs: Option<AgentDisplayConfigs>,
43
44 #[serde(default, skip_serializing_if = "<&bool>::not")]
45 pub native_thread: bool,
46
47 #[serde(skip)]
48 pub new_boxed: Option<AgentNewBoxedFn>,
49}
50
51pub type AgentDefaultConfigs = Vec<(String, AgentConfigEntry)>;
52pub type AgentGlobalConfigs = Vec<(String, AgentConfigEntry)>;
53
54#[derive(Debug, Default, Serialize, Deserialize, Clone)]
55pub struct AgentConfigEntry {
56 pub value: AgentValue,
57
58 #[serde(rename = "type")]
59 pub type_: Option<String>,
60
61 #[serde(skip_serializing_if = "Option::is_none")]
62 pub title: Option<String>,
63
64 #[serde(skip_serializing_if = "Option::is_none")]
65 pub description: Option<String>,
66
67 #[serde(default, skip_serializing_if = "<&bool>::not")]
70 pub hidden: bool,
71}
72
73pub type AgentDisplayConfigs = Vec<(String, AgentDisplayConfigEntry)>;
74
75#[derive(Debug, Default, Serialize, Deserialize, Clone)]
76pub struct AgentDisplayConfigEntry {
77 #[serde(rename = "type")]
78 pub type_: Option<String>,
79
80 #[serde(skip_serializing_if = "Option::is_none")]
81 pub title: Option<String>,
82
83 #[serde(skip_serializing_if = "Option::is_none")]
84 pub description: Option<String>,
85
86 #[serde(default, skip_serializing_if = "<&bool>::not")]
87 pub hide_title: bool,
88}
89
90pub type AgentNewBoxedFn = fn(
99 askit: ASKit,
100 id: String,
101 def_name: String,
102 configs: Option<AgentConfigs>,
103) -> Result<Box<dyn Agent>, AgentError>;
104
105impl AgentDefinition {
106 pub fn new(
107 kind: impl Into<String>,
108 name: impl Into<String>,
109 new_boxed: Option<AgentNewBoxedFn>,
110 ) -> Self {
111 Self {
112 kind: kind.into(),
113 name: name.into(),
114 new_boxed,
115 ..Default::default()
116 }
117 }
118
119 pub fn title(mut self, title: &str) -> Self {
120 self.title = Some(title.into());
121 self
122 }
123
124 pub fn description(mut self, description: &str) -> Self {
125 self.description = Some(description.into());
126 self
127 }
128
129 pub fn category(mut self, category: &str) -> Self {
130 self.category = Some(category.into());
131 self
132 }
133
134 pub fn inputs(mut self, inputs: Vec<&str>) -> Self {
135 self.inputs = Some(inputs.into_iter().map(|x| x.into()).collect());
136 self
137 }
138
139 pub fn outputs(mut self, outputs: Vec<&str>) -> Self {
140 self.outputs = Some(outputs.into_iter().map(|x| x.into()).collect());
141 self
142 }
143
144 pub fn default_configs(mut self, configs: Vec<(&str, AgentConfigEntry)>) -> Self {
147 self.default_configs = Some(configs.into_iter().map(|(k, v)| (k.into(), v)).collect());
148 self
149 }
150
151 pub fn unit_config(self, key: &str) -> Self {
152 self.unit_config_with(key, |entry| entry)
153 }
154
155 pub fn unit_config_with<F>(self, key: &str, f: F) -> Self
156 where
157 F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
158 {
159 self.config_type_with(key, (), "unit", f)
160 }
161
162 pub fn boolean_config(self, key: &str, default: bool) -> Self {
163 self.boolean_config_with(key, default, |entry| entry)
164 }
165
166 pub fn boolean_config_with<F>(self, key: &str, default: bool, f: F) -> Self
167 where
168 F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
169 {
170 self.config_type_with(key, default, "boolean", f)
171 }
172
173 pub fn boolean_config_default(self, key: &str) -> Self {
174 self.boolean_config(key, false)
175 }
176
177 pub fn integer_config(self, key: &str, default: i64) -> Self {
178 self.integer_config_with(key, default, |entry| entry)
179 }
180
181 pub fn integer_config_with<F>(self, key: &str, default: i64, f: F) -> Self
182 where
183 F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
184 {
185 self.config_type_with(key, default, "integer", f)
186 }
187
188 pub fn integer_config_default(self, key: &str) -> Self {
189 self.integer_config(key, 0)
190 }
191
192 pub fn number_config(self, key: &str, default: f64) -> Self {
193 self.number_config_with(key, default, |entry| entry)
194 }
195
196 pub fn number_config_with<F>(self, key: &str, default: f64, f: F) -> Self
197 where
198 F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
199 {
200 self.config_type_with(key, default, "number", f)
201 }
202
203 pub fn number_config_default(self, key: &str) -> Self {
204 self.number_config(key, 0.0)
205 }
206
207 pub fn string_config(self, key: &str, default: impl Into<String>) -> Self {
208 self.string_config_with(key, default, |entry| entry)
209 }
210
211 pub fn string_config_with<F>(self, key: &str, default: impl Into<String>, f: F) -> Self
212 where
213 F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
214 {
215 let default = default.into();
216 self.config_type_with(key, AgentValue::string(default), "string", f)
217 }
218
219 pub fn string_config_default(self, key: &str) -> Self {
220 self.string_config(key, "")
221 }
222
223 pub fn text_config(self, key: &str, default: impl Into<String>) -> Self {
224 self.text_config_with(key, default, |entry| entry)
225 }
226
227 pub fn text_config_with<F>(self, key: &str, default: impl Into<String>, f: F) -> Self
228 where
229 F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
230 {
231 let default = default.into();
232 self.config_type_with(key, AgentValue::string(default), "text", f)
233 }
234
235 pub fn text_config_default(self, key: &str) -> Self {
236 self.text_config(key, "")
237 }
238
239 pub fn object_config<V: Into<AgentValue>>(self, key: &str, default: V) -> Self {
240 self.object_config_with(key, default, |entry| entry)
241 }
242
243 pub fn object_config_with<V: Into<AgentValue>, F>(self, key: &str, default: V, f: F) -> Self
244 where
245 F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
246 {
247 self.config_type_with(key, default, "object", f)
248 }
249
250 pub fn object_config_default(self, key: &str) -> Self {
251 self.object_config(key, AgentValue::object_default())
252 }
253
254 pub fn custom_config_with<V: Into<AgentValue>, F>(
255 self,
256 key: &str,
257 default: V,
258 type_: &str,
259 f: F,
260 ) -> Self
261 where
262 F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
263 {
264 self.config_type_with(key, default, type_, f)
265 }
266
267 fn config_type_with<V: Into<AgentValue>, F>(
268 mut self,
269 key: &str,
270 default: V,
271 type_: &str,
272 f: F,
273 ) -> Self
274 where
275 F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
276 {
277 let entry = AgentConfigEntry::new(default, type_);
278 self.push_default_config_entry(key.into(), f(entry));
279 self
280 }
281
282 fn push_default_config_entry(&mut self, key: String, entry: AgentConfigEntry) {
283 if let Some(configs) = self.default_configs.as_mut() {
284 configs.push((key, entry));
285 } else {
286 self.default_configs = Some(vec![(key, entry)]);
287 }
288 }
289
290 pub fn global_configs(mut self, configs: Vec<(&str, AgentConfigEntry)>) -> Self {
293 self.global_configs = Some(configs.into_iter().map(|(k, v)| (k.into(), v)).collect());
294 self
295 }
296
297 pub fn unit_global_config(self, key: &str) -> Self {
298 self.unit_global_config_with(key, |entry| entry)
299 }
300
301 pub fn unit_global_config_with<F>(self, key: &str, f: F) -> Self
302 where
303 F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
304 {
305 self.global_config_type_with(key, (), "unit", f)
306 }
307
308 pub fn boolean_global_config(self, key: &str, default: bool) -> Self {
309 self.boolean_global_config_with(key, default, |entry| entry)
310 }
311
312 pub fn boolean_global_config_with<F>(self, key: &str, default: bool, f: F) -> Self
313 where
314 F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
315 {
316 self.global_config_type_with(key, default, "boolean", f)
317 }
318
319 pub fn integer_global_config(self, key: &str, default: i64) -> Self {
320 self.integer_global_config_with(key, default, |entry| entry)
321 }
322
323 pub fn integer_global_config_with<F>(self, key: &str, default: i64, f: F) -> Self
324 where
325 F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
326 {
327 self.global_config_type_with(key, default, "integer", f)
328 }
329
330 pub fn number_global_config(self, key: &str, default: f64) -> Self {
331 self.number_global_config_with(key, default, |entry| entry)
332 }
333
334 pub fn number_global_config_with<F>(self, key: &str, default: f64, f: F) -> Self
335 where
336 F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
337 {
338 self.global_config_type_with(key, default, "number", f)
339 }
340
341 pub fn string_global_config(self, key: &str, default: impl Into<String>) -> Self {
342 self.string_global_config_with(key, default, |entry| entry)
343 }
344
345 pub fn string_global_config_with<F>(self, key: &str, default: impl Into<String>, f: F) -> Self
346 where
347 F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
348 {
349 let default = default.into();
350 self.global_config_type_with(key, AgentValue::string(default), "string", f)
351 }
352
353 pub fn text_global_config(self, key: &str, default: impl Into<String>) -> Self {
354 self.text_global_config_with(key, default, |entry| entry)
355 }
356
357 pub fn text_global_config_with<F>(self, key: &str, default: impl Into<String>, f: F) -> Self
358 where
359 F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
360 {
361 let default = default.into();
362 self.global_config_type_with(key, AgentValue::string(default), "text", f)
363 }
364
365 pub fn object_global_config<V: Into<AgentValue>>(self, key: &str, default: V) -> Self {
366 self.object_global_config_with(key, default, |entry| entry)
367 }
368
369 pub fn object_global_config_with<V: Into<AgentValue>, F>(
370 self,
371 key: &str,
372 default: V,
373 f: F,
374 ) -> Self
375 where
376 F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
377 {
378 self.global_config_type_with(key, default, "object", f)
379 }
380
381 pub fn custom_global_config_with<V: Into<AgentValue>, F>(
382 self,
383 key: &str,
384 default: V,
385 type_: &str,
386 f: F,
387 ) -> Self
388 where
389 F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
390 {
391 self.global_config_type_with(key, default, type_, f)
392 }
393
394 fn global_config_type_with<V: Into<AgentValue>, F>(
395 mut self,
396 key: &str,
397 default: V,
398 type_: &str,
399 f: F,
400 ) -> Self
401 where
402 F: FnOnce(AgentConfigEntry) -> AgentConfigEntry,
403 {
404 let entry = AgentConfigEntry::new(default, type_);
405 self.push_global_config_entry(key.into(), f(entry));
406 self
407 }
408
409 fn push_global_config_entry(&mut self, key: String, entry: AgentConfigEntry) {
410 if let Some(configs) = self.global_configs.as_mut() {
411 configs.push((key, entry));
412 } else {
413 self.global_configs = Some(vec![(key, entry)]);
414 }
415 }
416
417 pub fn display_configs(mut self, configs: Vec<(&str, AgentDisplayConfigEntry)>) -> Self {
420 self.display_configs = Some(configs.into_iter().map(|(k, v)| (k.into(), v)).collect());
421 self
422 }
423
424 pub fn unit_display_config(self, key: &str) -> Self {
425 self.unit_display_config_with(key, |entry| entry)
426 }
427
428 pub fn unit_display_config_with<F>(self, key: &str, f: F) -> Self
429 where
430 F: FnOnce(AgentDisplayConfigEntry) -> AgentDisplayConfigEntry,
431 {
432 self.display_config_type_with(key, "unit", f)
433 }
434
435 pub fn boolean_display_config(self, key: &str) -> Self {
436 self.boolean_display_config_with(key, |entry| entry)
437 }
438
439 pub fn boolean_display_config_with<F>(self, key: &str, f: F) -> Self
440 where
441 F: FnOnce(AgentDisplayConfigEntry) -> AgentDisplayConfigEntry,
442 {
443 self.display_config_type_with(key, "boolean", f)
444 }
445
446 pub fn integer_display_config(self, key: &str) -> Self {
447 self.integer_display_config_with(key, |entry| entry)
448 }
449
450 pub fn integer_display_config_with<F>(self, key: &str, f: F) -> Self
451 where
452 F: FnOnce(AgentDisplayConfigEntry) -> AgentDisplayConfigEntry,
453 {
454 self.display_config_type_with(key, "integer", f)
455 }
456
457 pub fn number_display_config(self, key: &str) -> Self {
458 self.number_display_config_with(key, |entry| entry)
459 }
460
461 pub fn number_display_config_with<F>(self, key: &str, f: F) -> Self
462 where
463 F: FnOnce(AgentDisplayConfigEntry) -> AgentDisplayConfigEntry,
464 {
465 self.display_config_type_with(key, "number", f)
466 }
467
468 pub fn string_display_config(self, key: &str) -> Self {
469 self.string_display_config_with(key, |entry| entry)
470 }
471
472 pub fn string_display_config_with<F>(self, key: &str, f: F) -> Self
473 where
474 F: FnOnce(AgentDisplayConfigEntry) -> AgentDisplayConfigEntry,
475 {
476 self.display_config_type_with(key, "string", f)
477 }
478
479 pub fn text_display_config(self, key: &str) -> Self {
480 self.text_display_config_with(key, |entry| entry)
481 }
482
483 pub fn text_display_config_with<F>(self, key: &str, f: F) -> Self
484 where
485 F: FnOnce(AgentDisplayConfigEntry) -> AgentDisplayConfigEntry,
486 {
487 self.display_config_type_with(key, "text", f)
488 }
489
490 pub fn object_display_config(self, key: &str) -> Self {
491 self.object_display_config_with(key, |entry| entry)
492 }
493
494 pub fn object_display_config_with<F>(self, key: &str, f: F) -> Self
495 where
496 F: FnOnce(AgentDisplayConfigEntry) -> AgentDisplayConfigEntry,
497 {
498 self.display_config_type_with(key, "object", f)
499 }
500
501 pub fn custom_display_config_with<F>(self, key: &str, type_: &str, f: F) -> Self
502 where
503 F: FnOnce(AgentDisplayConfigEntry) -> AgentDisplayConfigEntry,
504 {
505 self.display_config_type_with(key, type_, f)
506 }
507
508 fn display_config_type_with<F>(mut self, key: &str, type_: &str, f: F) -> Self
509 where
510 F: FnOnce(AgentDisplayConfigEntry) -> AgentDisplayConfigEntry,
511 {
512 let entry = AgentDisplayConfigEntry::new(type_);
513 self.push_display_config_entry(key.into(), f(entry));
514 self
515 }
516
517 fn push_display_config_entry(&mut self, key: String, entry: AgentDisplayConfigEntry) {
518 if let Some(configs) = self.display_configs.as_mut() {
519 configs.push((key, entry));
520 } else {
521 self.display_configs = Some(vec![(key, entry)]);
522 }
523 }
524
525 pub fn use_native_thread(mut self) -> Self {
526 self.native_thread = true;
527 self
528 }
529}
530
531impl AgentConfigEntry {
532 pub fn new<V: Into<AgentValue>>(value: V, type_: &str) -> Self {
533 Self {
534 value: value.into(),
535 type_: Some(type_.into()),
536 ..Default::default()
537 }
538 }
539
540 pub fn title(mut self, title: &str) -> Self {
541 self.title = Some(title.into());
542 self
543 }
544
545 pub fn description(mut self, description: &str) -> Self {
546 self.description = Some(description.into());
547 self
548 }
549
550 pub fn hidden(mut self) -> Self {
551 self.hidden = true;
552 self
553 }
554}
555
556impl AgentDisplayConfigEntry {
557 pub fn new(type_: &str) -> Self {
558 Self {
559 type_: Some(type_.into()),
560 ..Default::default()
561 }
562 }
563
564 pub fn hide_title(mut self) -> Self {
565 self.hide_title = true;
566 self
567 }
568
569 #[allow(unused)]
570 pub fn title(mut self, title: &str) -> Self {
571 self.title = Some(title.into());
572 self
573 }
574
575 #[allow(unused)]
576 pub fn description(mut self, description: &str) -> Self {
577 self.description = Some(description.into());
578 self
579 }
580}
581
582#[cfg(test)]
583mod tests {
584 use super::*;
585
586 #[test]
587 fn test_agent_definition() {
588 let def = AgentDefinition::default();
589 assert_eq!(def.name, "");
590 }
591
592 #[test]
593 fn test_agent_definition_new_default() {
594 let def = AgentDefinition::new(
595 "test",
596 "echo",
597 Some(|_app, _id, _def_name, _configs| {
598 Err(AgentError::NotImplemented("Echo agent".into()))
599 }),
600 );
601
602 assert_eq!(def.kind, "test");
603 assert_eq!(def.name, "echo");
604 assert!(def.title.is_none());
605 assert!(def.category.is_none());
606 assert!(def.inputs.is_none());
607 assert!(def.outputs.is_none());
608 assert!(def.display_configs.is_none());
609 }
610
611 #[test]
612 fn test_agent_definition_new() {
613 let def = echo_agent_definition();
614
615 assert_eq!(def.kind, "test");
616 assert_eq!(def.name, "echo");
617 assert_eq!(def.title.unwrap(), "Echo");
618 assert_eq!(def.category.unwrap(), "Test");
619 assert_eq!(def.inputs.unwrap(), vec!["in"]);
620 assert_eq!(def.outputs.unwrap(), vec!["out"]);
621 let display_configs = def.display_configs.unwrap();
622 assert_eq!(display_configs.len(), 2);
623 let entry = &display_configs[0];
624 assert_eq!(entry.0, "value");
625 assert_eq!(entry.1.type_.as_ref().unwrap(), "string");
626 assert_eq!(entry.1.title.as_ref().unwrap(), "display_title");
627 assert_eq!(entry.1.description.as_ref().unwrap(), "display_description");
628 assert_eq!(entry.1.hide_title, false);
629 let entry = &display_configs[1];
630 assert_eq!(entry.0, "hide_title_value");
631 assert_eq!(entry.1.type_.as_ref().unwrap(), "integer");
632 assert_eq!(entry.1.title, None);
633 assert_eq!(entry.1.description, None);
634 assert_eq!(entry.1.hide_title, true);
635 }
636
637 #[test]
638 fn test_serialize_agent_definition() {
639 let def = AgentDefinition::new(
640 "test",
641 "echo",
642 Some(|_app, _id, _def_name, _configs| {
643 Err(AgentError::NotImplemented("Echo agent".into()))
644 }),
645 );
646 let json = serde_json::to_string(&def).unwrap();
647 assert_eq!(json, r#"{"kind":"test","name":"echo"}"#);
648 }
649
650 #[test]
651 fn test_serialize_echo_agent_definition() {
652 let def = echo_agent_definition();
653 let json = serde_json::to_string(&def).unwrap();
654 print!("{}", json);
655 assert_eq!(
656 json,
657 r#"{"kind":"test","name":"echo","title":"Echo","category":"Test","inputs":["in"],"outputs":["out"],"display_configs":[["value",{"type":"string","title":"display_title","description":"display_description"}],["hide_title_value",{"type":"integer","hide_title":true}]]}"#
658 );
659 }
660
661 #[test]
662 fn test_deserialize_echo_agent_definition() {
663 let json = r#"{"kind":"test","name":"echo","title":"Echo","category":"Test","inputs":["in"],"outputs":["out"],"display_configs":[["value",{"type":"string","title":"display_title","description":"display_description"}],["hide_title_value",{"type":"integer","hide_title":true}]]}"#;
664 let def: AgentDefinition = serde_json::from_str(json).unwrap();
665 assert_eq!(def.kind, "test");
666 assert_eq!(def.name, "echo");
667 assert_eq!(def.title.unwrap(), "Echo");
668 assert_eq!(def.category.unwrap(), "Test");
669 assert_eq!(def.inputs.unwrap(), vec!["in"]);
670 assert_eq!(def.outputs.unwrap(), vec!["out"]);
671 let display_configs = def.display_configs.unwrap();
672 assert_eq!(display_configs.len(), 2);
673 let entry = &display_configs[0];
674 assert_eq!(entry.0, "value");
675 assert_eq!(entry.1.type_.as_ref().unwrap(), "string");
676 assert_eq!(entry.1.title.as_ref().unwrap(), "display_title");
677 assert_eq!(entry.1.description.as_ref().unwrap(), "display_description");
678 assert_eq!(entry.1.hide_title, false);
679 let entry = &display_configs[1];
680 assert_eq!(entry.0, "hide_title_value");
681 assert_eq!(entry.1.type_.as_ref().unwrap(), "integer");
682 assert_eq!(entry.1.title, None);
683 assert_eq!(entry.1.description, None);
684 assert_eq!(entry.1.hide_title, true);
685 }
686
687 #[test]
688 fn test_default_config_helpers() {
689 let custom_object_value =
690 AgentValue::object([("key".to_string(), AgentValue::string("value"))].into());
691
692 let def = AgentDefinition::new("test", "helpers", None)
693 .unit_config("unit_value")
694 .boolean_config_default("boolean_value")
695 .boolean_config("boolean_custom", true)
696 .integer_config_default("integer_value")
697 .integer_config("integer_custom", 42)
698 .number_config_default("number_value")
699 .number_config("number_custom", 1.5)
700 .string_config_default("string_default")
701 .string_config("string_value", "value")
702 .text_config_default("text_value")
703 .text_config("text_custom", "custom")
704 .object_config_default("object_value")
705 .object_config("object_custom", custom_object_value.clone());
706
707 let configs = def
708 .default_configs
709 .clone()
710 .expect("default configs should exist");
711 assert_eq!(configs.len(), 13);
712 let config_map: std::collections::HashMap<_, _> = configs.into_iter().collect();
713
714 let unit_entry = config_map.get("unit_value").unwrap();
715 assert_eq!(unit_entry.type_.as_deref(), Some("unit"));
716 assert_eq!(unit_entry.value, AgentValue::unit());
717
718 let boolean_entry = config_map.get("boolean_value").unwrap();
719 assert_eq!(boolean_entry.type_.as_deref(), Some("boolean"));
720 assert_eq!(boolean_entry.value, AgentValue::boolean(false));
721
722 let boolean_custom_entry = config_map.get("boolean_custom").unwrap();
723 assert_eq!(boolean_custom_entry.type_.as_deref(), Some("boolean"));
724 assert_eq!(boolean_custom_entry.value, AgentValue::boolean(true));
725
726 let integer_entry = config_map.get("integer_value").unwrap();
727 assert_eq!(integer_entry.type_.as_deref(), Some("integer"));
728 assert_eq!(integer_entry.value, AgentValue::integer(0));
729
730 let integer_custom_entry = config_map.get("integer_custom").unwrap();
731 assert_eq!(integer_custom_entry.type_.as_deref(), Some("integer"));
732 assert_eq!(integer_custom_entry.value, AgentValue::integer(42));
733
734 let number_entry = config_map.get("number_value").unwrap();
735 assert_eq!(number_entry.type_.as_deref(), Some("number"));
736 assert_eq!(number_entry.value, AgentValue::number(0.0));
737
738 let number_custom_entry = config_map.get("number_custom").unwrap();
739 assert_eq!(number_custom_entry.type_.as_deref(), Some("number"));
740 assert_eq!(number_custom_entry.value, AgentValue::number(1.5));
741
742 let string_default_entry = config_map.get("string_default").unwrap();
743 assert_eq!(string_default_entry.type_.as_deref(), Some("string"));
744 assert_eq!(string_default_entry.value, AgentValue::string(""));
745
746 let string_entry = config_map.get("string_value").unwrap();
747 assert_eq!(string_entry.type_.as_deref(), Some("string"));
748 assert_eq!(string_entry.value, AgentValue::string("value"));
749
750 let text_entry = config_map.get("text_value").unwrap();
751 assert_eq!(text_entry.type_.as_deref(), Some("text"));
752 assert_eq!(text_entry.value, AgentValue::string(""));
753
754 let text_custom_entry = config_map.get("text_custom").unwrap();
755 assert_eq!(text_custom_entry.type_.as_deref(), Some("text"));
756 assert_eq!(text_custom_entry.value, AgentValue::string("custom"));
757
758 let object_entry = config_map.get("object_value").unwrap();
759 assert_eq!(object_entry.type_.as_deref(), Some("object"));
760 assert_eq!(object_entry.value, AgentValue::object_default());
761
762 let object_custom_entry = config_map.get("object_custom").unwrap();
763 assert_eq!(object_custom_entry.type_.as_deref(), Some("object"));
764 assert_eq!(object_custom_entry.value, custom_object_value);
765 }
766
767 #[test]
768 fn test_global_config_helpers() {
769 let custom_object_value =
770 AgentValue::object([("key".to_string(), AgentValue::string("value"))].into());
771
772 let def = AgentDefinition::new("test", "helpers", None)
773 .unit_global_config("global_unit")
774 .boolean_global_config("global_boolean", true)
775 .integer_global_config("global_integer", 42)
776 .number_global_config("global_number", 1.5)
777 .string_global_config("global_string", "value")
778 .text_global_config("global_text", "global")
779 .object_global_config("global_object", custom_object_value.clone());
780
781 let global_configs = def.global_configs.expect("global configs should exist");
782 assert_eq!(global_configs.len(), 7);
783 let config_map: std::collections::HashMap<_, _> = global_configs.into_iter().collect();
784
785 let entry = config_map.get("global_unit").unwrap();
786 assert_eq!(entry.type_.as_deref(), Some("unit"));
787 assert_eq!(entry.value, AgentValue::unit());
788
789 let entry = config_map.get("global_boolean").unwrap();
790 assert_eq!(entry.type_.as_deref(), Some("boolean"));
791 assert_eq!(entry.value, AgentValue::boolean(true));
792
793 let entry = config_map.get("global_integer").unwrap();
794 assert_eq!(entry.type_.as_deref(), Some("integer"));
795 assert_eq!(entry.value, AgentValue::integer(42));
796
797 let entry = config_map.get("global_number").unwrap();
798 assert_eq!(entry.type_.as_deref(), Some("number"));
799 assert_eq!(entry.value, AgentValue::number(1.5));
800
801 let entry = config_map.get("global_string").unwrap();
802 assert_eq!(entry.type_.as_deref(), Some("string"));
803 assert_eq!(entry.value, AgentValue::string("value"));
804
805 let entry = config_map.get("global_text").unwrap();
806 assert_eq!(entry.type_.as_deref(), Some("text"));
807 assert_eq!(entry.value, AgentValue::string("global"));
808
809 let entry = config_map.get("global_object").unwrap();
810 assert_eq!(entry.type_.as_deref(), Some("object"));
811 assert_eq!(entry.value, custom_object_value);
812 }
813
814 #[test]
815 fn test_display_config_helpers() {
816 let def = AgentDefinition::new("test", "helpers", None)
817 .unit_display_config("display_unit")
818 .boolean_display_config("display_boolean")
819 .integer_display_config("display_integer")
820 .number_display_config("display_number")
821 .string_display_config("display_string")
822 .text_display_config("display_text")
823 .object_display_config("display_object");
824
825 let display_configs = def.display_configs.expect("display configs should exist");
826 assert_eq!(display_configs.len(), 7);
827 let config_map: std::collections::HashMap<_, _> = display_configs.into_iter().collect();
828
829 assert_eq!(
830 config_map.get("display_unit").unwrap().type_.as_deref(),
831 Some("unit")
832 );
833 assert_eq!(
834 config_map.get("display_boolean").unwrap().type_.as_deref(),
835 Some("boolean")
836 );
837 assert_eq!(
838 config_map.get("display_integer").unwrap().type_.as_deref(),
839 Some("integer")
840 );
841 assert_eq!(
842 config_map.get("display_number").unwrap().type_.as_deref(),
843 Some("number")
844 );
845 assert_eq!(
846 config_map.get("display_string").unwrap().type_.as_deref(),
847 Some("string")
848 );
849 assert_eq!(
850 config_map.get("display_text").unwrap().type_.as_deref(),
851 Some("text")
852 );
853 assert_eq!(
854 config_map.get("display_object").unwrap().type_.as_deref(),
855 Some("object")
856 );
857
858 for entry in config_map.values() {
859 assert!(!entry.hide_title);
860 }
861 }
862
863 #[test]
864 fn test_config_helper_customization() {
865 let def = AgentDefinition::new("test", "custom", None)
866 .integer_config_with("custom_default", 1, |entry| entry.title("Custom"))
867 .text_global_config_with("custom_global", "value", |entry| {
868 entry.description("Global Desc")
869 })
870 .text_display_config_with("custom_display", |entry| entry.title("Display"));
871
872 let default_entry = def
873 .default_configs
874 .as_ref()
875 .unwrap()
876 .iter()
877 .find(|(k, _)| k == "custom_default")
878 .map(|(_, v)| v)
879 .unwrap();
880 assert_eq!(default_entry.title.as_deref(), Some("Custom"));
881
882 let global_entry = def
883 .global_configs
884 .as_ref()
885 .unwrap()
886 .iter()
887 .find(|(k, _)| k == "custom_global")
888 .map(|(_, v)| v)
889 .unwrap();
890 assert_eq!(global_entry.description.as_deref(), Some("Global Desc"));
891
892 let display_entry = def
893 .display_configs
894 .as_ref()
895 .unwrap()
896 .iter()
897 .find(|(k, _)| k == "custom_display")
898 .map(|(_, v)| v)
899 .unwrap();
900 assert_eq!(display_entry.title.as_deref(), Some("Display"));
901 }
902
903 fn echo_agent_definition() -> AgentDefinition {
904 AgentDefinition::new(
905 "test",
906 "echo",
907 Some(|_app, _id, _def_name, _configs| {
908 Err(AgentError::NotImplemented("Echo agent".into()))
909 }),
910 )
911 .title("Echo")
912 .category("Test")
913 .inputs(vec!["in"])
914 .outputs(vec!["out"])
915 .string_display_config_with("value", |entry| {
916 entry
917 .title("display_title")
918 .description("display_description")
919 })
920 .integer_display_config_with("hide_title_value", |entry| entry.hide_title())
921 }
922}