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