1use std::collections::HashMap;
17use std::sync::Arc;
18
19use async_trait::async_trait;
20
21#[cfg(feature = "native")]
22pub use tokio_util::sync::CancellationToken;
23
24#[cfg(not(feature = "native"))]
25mod cancellation {
26 use std::sync::Arc;
27 use std::sync::atomic::{AtomicBool, Ordering};
28
29 #[derive(Clone)]
35 pub struct CancellationToken {
36 cancelled: Arc<AtomicBool>,
37 }
38
39 impl CancellationToken {
40 pub fn new() -> Self {
42 Self {
43 cancelled: Arc::new(AtomicBool::new(false)),
44 }
45 }
46
47 pub fn cancel(&self) {
49 self.cancelled.store(true, Ordering::SeqCst);
50 }
51
52 pub fn is_cancelled(&self) -> bool {
54 self.cancelled.load(Ordering::SeqCst)
55 }
56 }
57
58 impl Default for CancellationToken {
59 fn default() -> Self {
60 Self::new()
61 }
62 }
63}
64
65#[cfg(not(feature = "native"))]
66pub use cancellation::CancellationToken;
67
68use crate::error::PluginError;
69use crate::message::MessagePayload;
70
71#[async_trait]
82pub trait Tool: Send + Sync {
83 fn name(&self) -> &str;
85
86 fn description(&self) -> &str;
88
89 fn parameters_schema(&self) -> serde_json::Value;
94
95 async fn execute(
100 &self,
101 params: serde_json::Value,
102 ctx: &dyn ToolContext,
103 ) -> Result<serde_json::Value, PluginError>;
104}
105
106#[async_trait]
116pub trait ChannelAdapter: Send + Sync {
117 fn name(&self) -> &str;
119
120 fn display_name(&self) -> &str;
122
123 fn supports_threads(&self) -> bool;
125
126 fn supports_media(&self) -> bool;
128
129 async fn start(
131 &self,
132 host: Arc<dyn ChannelAdapterHost>,
133 cancel: CancellationToken,
134 ) -> Result<(), PluginError>;
135
136 async fn send(
140 &self,
141 target: &str,
142 payload: &MessagePayload,
143 ) -> Result<String, PluginError>;
144}
145
146#[async_trait]
148pub trait ChannelAdapterHost: Send + Sync {
149 async fn deliver_inbound(
151 &self,
152 channel: &str,
153 sender_id: &str,
154 chat_id: &str,
155 payload: MessagePayload,
156 metadata: HashMap<String, serde_json::Value>,
157 ) -> Result<(), PluginError>;
158}
159
160#[non_exhaustive]
166#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
167#[serde(rename_all = "snake_case")]
168pub enum PipelineStageType {
169 PreProcess,
171 Process,
173 PostProcess,
175 Observer,
177}
178
179#[async_trait]
184pub trait PipelineStage: Send + Sync {
185 fn name(&self) -> &str;
187
188 fn stage_type(&self) -> PipelineStageType;
190
191 async fn process(
196 &self,
197 input: serde_json::Value,
198 ) -> Result<serde_json::Value, PluginError>;
199}
200
201#[async_trait]
213pub trait Skill: Send + Sync {
214 fn name(&self) -> &str;
216
217 fn description(&self) -> &str;
219
220 fn version(&self) -> &str;
222
223 fn variables(&self) -> HashMap<String, String>;
225
226 fn allowed_tools(&self) -> Vec<String>;
228
229 fn instructions(&self) -> &str;
231
232 fn is_user_invocable(&self) -> bool;
234
235 async fn execute_tool(
241 &self,
242 tool_name: &str,
243 params: serde_json::Value,
244 ctx: &dyn ToolContext,
245 ) -> Result<serde_json::Value, PluginError>;
246}
247
248#[async_trait]
258pub trait MemoryBackend: Send + Sync {
259 async fn store(
261 &self,
262 key: &str,
263 value: &str,
264 namespace: Option<&str>,
265 ttl_seconds: Option<u64>,
266 tags: Option<Vec<String>>,
267 ) -> Result<(), PluginError>;
268
269 async fn retrieve(
271 &self,
272 key: &str,
273 namespace: Option<&str>,
274 ) -> Result<Option<String>, PluginError>;
275
276 async fn search(
280 &self,
281 query: &str,
282 namespace: Option<&str>,
283 limit: Option<usize>,
284 ) -> Result<Vec<(String, String, f64)>, PluginError>;
285
286 async fn delete(
288 &self,
289 key: &str,
290 namespace: Option<&str>,
291 ) -> Result<bool, PluginError>;
292}
293
294#[async_trait]
305pub trait VoiceHandler: Send + Sync {
306 async fn process_audio(
311 &self,
312 audio_data: &[u8],
313 mime_type: &str,
314 ) -> Result<String, PluginError>;
315
316 async fn synthesize(
320 &self,
321 text: &str,
322 ) -> Result<(Vec<u8>, String), PluginError>;
323}
324
325#[async_trait]
335pub trait KeyValueStore: Send + Sync {
336 async fn get(&self, key: &str) -> Result<Option<String>, PluginError>;
338
339 async fn set(&self, key: &str, value: &str) -> Result<(), PluginError>;
341
342 async fn delete(&self, key: &str) -> Result<bool, PluginError>;
344
345 async fn list_keys(&self, prefix: Option<&str>) -> Result<Vec<String>, PluginError>;
347}
348
349pub trait ToolContext: Send + Sync {
358 fn key_value_store(&self) -> &dyn KeyValueStore;
360
361 fn plugin_id(&self) -> &str;
363
364 fn agent_id(&self) -> &str;
366}
367
368#[cfg(test)]
373mod tests {
374 use super::*;
375
376 fn assert_send_sync<T: Send + Sync + ?Sized>() {}
378
379 #[test]
380 fn test_traits_are_send_sync() {
381 assert_send_sync::<dyn Tool>();
383 assert_send_sync::<dyn ChannelAdapter>();
384 assert_send_sync::<dyn PipelineStage>();
385 assert_send_sync::<dyn Skill>();
386 assert_send_sync::<dyn MemoryBackend>();
387 assert_send_sync::<dyn VoiceHandler>();
388
389 assert_send_sync::<dyn KeyValueStore>();
391 assert_send_sync::<dyn ToolContext>();
392 assert_send_sync::<dyn ChannelAdapterHost>();
393 }
394
395 #[test]
396 fn test_pipeline_stage_type_serde_roundtrip() {
397 let types = vec![
398 PipelineStageType::PreProcess,
399 PipelineStageType::Process,
400 PipelineStageType::PostProcess,
401 PipelineStageType::Observer,
402 ];
403 for t in &types {
404 let json = serde_json::to_string(t).unwrap();
405 let restored: PipelineStageType = serde_json::from_str(&json).unwrap();
406 assert_eq!(&restored, t);
407 }
408 }
409
410 #[test]
411 fn test_pipeline_stage_type_json_values() {
412 assert_eq!(
413 serde_json::to_string(&PipelineStageType::PreProcess).unwrap(),
414 "\"pre_process\""
415 );
416 assert_eq!(
417 serde_json::to_string(&PipelineStageType::Process).unwrap(),
418 "\"process\""
419 );
420 assert_eq!(
421 serde_json::to_string(&PipelineStageType::PostProcess).unwrap(),
422 "\"post_process\""
423 );
424 assert_eq!(
425 serde_json::to_string(&PipelineStageType::Observer).unwrap(),
426 "\"observer\""
427 );
428 }
429
430 struct MockKvStore;
435
436 #[async_trait]
437 impl KeyValueStore for MockKvStore {
438 async fn get(&self, _key: &str) -> Result<Option<String>, PluginError> {
439 Ok(None)
440 }
441 async fn set(&self, _key: &str, _value: &str) -> Result<(), PluginError> {
442 Ok(())
443 }
444 async fn delete(&self, _key: &str) -> Result<bool, PluginError> {
445 Ok(false)
446 }
447 async fn list_keys(&self, _prefix: Option<&str>) -> Result<Vec<String>, PluginError> {
448 Ok(vec![])
449 }
450 }
451
452 struct MockToolContext;
453
454 impl ToolContext for MockToolContext {
455 fn key_value_store(&self) -> &dyn KeyValueStore {
456 &MockKvStore
457 }
458 fn plugin_id(&self) -> &str {
459 "mock-plugin"
460 }
461 fn agent_id(&self) -> &str {
462 "mock-agent"
463 }
464 }
465
466 struct MockTool;
467
468 #[async_trait]
469 impl Tool for MockTool {
470 fn name(&self) -> &str {
471 "mock_tool"
472 }
473 fn description(&self) -> &str {
474 "A mock tool for testing"
475 }
476 fn parameters_schema(&self) -> serde_json::Value {
477 serde_json::json!({
478 "type": "object",
479 "properties": {
480 "input": { "type": "string" }
481 }
482 })
483 }
484 async fn execute(
485 &self,
486 params: serde_json::Value,
487 _ctx: &dyn ToolContext,
488 ) -> Result<serde_json::Value, PluginError> {
489 Ok(serde_json::json!({
490 "result": format!("processed: {}", params)
491 }))
492 }
493 }
494
495 struct MockChannelAdapter;
496
497 #[async_trait]
498 impl ChannelAdapter for MockChannelAdapter {
499 fn name(&self) -> &str {
500 "mock"
501 }
502 fn display_name(&self) -> &str {
503 "Mock Channel"
504 }
505 fn supports_threads(&self) -> bool {
506 false
507 }
508 fn supports_media(&self) -> bool {
509 true
510 }
511 async fn start(
512 &self,
513 _host: Arc<dyn ChannelAdapterHost>,
514 cancel: CancellationToken,
515 ) -> Result<(), PluginError> {
516 cancel.cancelled().await;
517 Ok(())
518 }
519 async fn send(
520 &self,
521 _target: &str,
522 _payload: &MessagePayload,
523 ) -> Result<String, PluginError> {
524 Ok("msg-001".into())
525 }
526 }
527
528 struct MockPipelineStage;
529
530 #[async_trait]
531 impl PipelineStage for MockPipelineStage {
532 fn name(&self) -> &str {
533 "mock_stage"
534 }
535 fn stage_type(&self) -> PipelineStageType {
536 PipelineStageType::PreProcess
537 }
538 async fn process(
539 &self,
540 input: serde_json::Value,
541 ) -> Result<serde_json::Value, PluginError> {
542 Ok(input)
543 }
544 }
545
546 struct MockSkill;
547
548 #[async_trait]
549 impl Skill for MockSkill {
550 fn name(&self) -> &str {
551 "mock-skill"
552 }
553 fn description(&self) -> &str {
554 "A mock skill"
555 }
556 fn version(&self) -> &str {
557 "1.0.0"
558 }
559 fn variables(&self) -> HashMap<String, String> {
560 HashMap::new()
561 }
562 fn allowed_tools(&self) -> Vec<String> {
563 vec!["mock_tool".into()]
564 }
565 fn instructions(&self) -> &str {
566 "Do mock things."
567 }
568 fn is_user_invocable(&self) -> bool {
569 true
570 }
571 async fn execute_tool(
572 &self,
573 tool_name: &str,
574 _params: serde_json::Value,
575 _ctx: &dyn ToolContext,
576 ) -> Result<serde_json::Value, PluginError> {
577 Ok(serde_json::json!({ "tool": tool_name, "status": "ok" }))
578 }
579 }
580
581 struct MockMemoryBackend;
582
583 #[async_trait]
584 impl MemoryBackend for MockMemoryBackend {
585 async fn store(
586 &self,
587 _key: &str,
588 _value: &str,
589 _namespace: Option<&str>,
590 _ttl_seconds: Option<u64>,
591 _tags: Option<Vec<String>>,
592 ) -> Result<(), PluginError> {
593 Ok(())
594 }
595 async fn retrieve(
596 &self,
597 _key: &str,
598 _namespace: Option<&str>,
599 ) -> Result<Option<String>, PluginError> {
600 Ok(Some("stored-value".into()))
601 }
602 async fn search(
603 &self,
604 _query: &str,
605 _namespace: Option<&str>,
606 _limit: Option<usize>,
607 ) -> Result<Vec<(String, String, f64)>, PluginError> {
608 Ok(vec![("key".into(), "value".into(), 0.95)])
609 }
610 async fn delete(
611 &self,
612 _key: &str,
613 _namespace: Option<&str>,
614 ) -> Result<bool, PluginError> {
615 Ok(true)
616 }
617 }
618
619 struct MockVoiceHandler;
620
621 #[async_trait]
622 impl VoiceHandler for MockVoiceHandler {
623 async fn process_audio(
624 &self,
625 _audio_data: &[u8],
626 _mime_type: &str,
627 ) -> Result<String, PluginError> {
628 Ok("transcribed text".into())
629 }
630 async fn synthesize(
631 &self,
632 _text: &str,
633 ) -> Result<(Vec<u8>, String), PluginError> {
634 Ok((vec![0u8; 100], "audio/wav".into()))
635 }
636 }
637
638 #[tokio::test]
639 async fn test_tool_trait_implementation() {
640 let tool = MockTool;
641 let ctx = MockToolContext;
642 assert_eq!(tool.name(), "mock_tool");
643 assert_eq!(tool.description(), "A mock tool for testing");
644 assert!(tool.parameters_schema().is_object());
645 let result = tool
646 .execute(serde_json::json!({"input": "test"}), &ctx)
647 .await
648 .unwrap();
649 assert!(result["result"].as_str().unwrap().contains("test"));
650 }
651
652 #[tokio::test]
653 async fn test_channel_adapter_trait_implementation() {
654 let adapter = MockChannelAdapter;
655 assert_eq!(adapter.name(), "mock");
656 assert_eq!(adapter.display_name(), "Mock Channel");
657 assert!(!adapter.supports_threads());
658 assert!(adapter.supports_media());
659 let payload = MessagePayload::text("hello");
660 let msg_id = adapter.send("target", &payload).await.unwrap();
661 assert_eq!(msg_id, "msg-001");
662 }
663
664 #[tokio::test]
665 async fn test_pipeline_stage_trait_implementation() {
666 let stage = MockPipelineStage;
667 assert_eq!(stage.name(), "mock_stage");
668 assert_eq!(stage.stage_type(), PipelineStageType::PreProcess);
669 let input = serde_json::json!({"data": "test"});
670 let output = stage.process(input.clone()).await.unwrap();
671 assert_eq!(output, input);
672 }
673
674 #[tokio::test]
675 async fn test_skill_trait_implementation() {
676 let skill = MockSkill;
677 let ctx = MockToolContext;
678 assert_eq!(skill.name(), "mock-skill");
679 assert_eq!(skill.description(), "A mock skill");
680 assert_eq!(skill.version(), "1.0.0");
681 assert!(skill.variables().is_empty());
682 assert_eq!(skill.allowed_tools(), vec!["mock_tool"]);
683 assert_eq!(skill.instructions(), "Do mock things.");
684 assert!(skill.is_user_invocable());
685 let result = skill
686 .execute_tool("mock_tool", serde_json::json!({}), &ctx)
687 .await
688 .unwrap();
689 assert_eq!(result["tool"], "mock_tool");
690 assert_eq!(result["status"], "ok");
691 }
692
693 #[tokio::test]
694 async fn test_memory_backend_trait_implementation() {
695 let backend = MockMemoryBackend;
696 backend
697 .store("key", "value", None, None, None)
698 .await
699 .unwrap();
700 let val = backend.retrieve("key", None).await.unwrap();
701 assert_eq!(val, Some("stored-value".into()));
702 let results = backend.search("query", None, Some(10)).await.unwrap();
703 assert_eq!(results.len(), 1);
704 assert_eq!(results[0].0, "key");
705 let deleted = backend.delete("key", None).await.unwrap();
706 assert!(deleted);
707 }
708
709 #[tokio::test]
710 async fn test_voice_handler_trait_implementation() {
711 let handler = MockVoiceHandler;
712 let text = handler
713 .process_audio(&[0u8; 100], "audio/wav")
714 .await
715 .unwrap();
716 assert_eq!(text, "transcribed text");
717 let (audio, mime) = handler.synthesize("hello").await.unwrap();
718 assert!(!audio.is_empty());
719 assert_eq!(mime, "audio/wav");
720 }
721
722 #[tokio::test]
723 async fn test_key_value_store_trait_implementation() {
724 let store = MockKvStore;
725 let val = store.get("missing").await.unwrap();
726 assert!(val.is_none());
727 store.set("key", "value").await.unwrap();
728 let deleted = store.delete("key").await.unwrap();
729 assert!(!deleted); let keys = store.list_keys(None).await.unwrap();
731 assert!(keys.is_empty());
732 }
733
734 #[test]
735 fn test_tool_context_trait_implementation() {
736 let ctx = MockToolContext;
737 assert_eq!(ctx.plugin_id(), "mock-plugin");
738 assert_eq!(ctx.agent_id(), "mock-agent");
739 let _kv = ctx.key_value_store();
741 }
742
743 #[test]
744 fn test_trait_objects_can_be_boxed() {
745 let _tool: Box<dyn Tool> = Box::new(MockTool);
747 let _channel: Box<dyn ChannelAdapter> = Box::new(MockChannelAdapter);
748 let _stage: Box<dyn PipelineStage> = Box::new(MockPipelineStage);
749 let _skill: Box<dyn Skill> = Box::new(MockSkill);
750 let _memory: Box<dyn MemoryBackend> = Box::new(MockMemoryBackend);
751 let _voice: Box<dyn VoiceHandler> = Box::new(MockVoiceHandler);
752 let _kv: Box<dyn KeyValueStore> = Box::new(MockKvStore);
753 let _ctx: Box<dyn ToolContext> = Box::new(MockToolContext);
754 }
755
756 #[test]
757 fn test_trait_objects_can_be_arced() {
758 let _tool: Arc<dyn Tool> = Arc::new(MockTool);
760 let _channel: Arc<dyn ChannelAdapter> = Arc::new(MockChannelAdapter);
761 let _stage: Arc<dyn PipelineStage> = Arc::new(MockPipelineStage);
762 let _skill: Arc<dyn Skill> = Arc::new(MockSkill);
763 let _memory: Arc<dyn MemoryBackend> = Arc::new(MockMemoryBackend);
764 let _voice: Arc<dyn VoiceHandler> = Arc::new(MockVoiceHandler);
765 let _kv: Arc<dyn KeyValueStore> = Arc::new(MockKvStore);
766 }
767}