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