zoey_core/
runtime_ref.rs

1//! Thread-safe runtime reference wrapper for components
2//!
3//! Provides a type-erased, Send+Sync wrapper around AgentRuntime
4//! that can be safely passed to actions, providers, and evaluators
5
6use std::sync::{Arc, RwLock, Weak};
7use uuid::Uuid;
8
9/// Thread-safe runtime reference that can be passed to components
10///
11/// This wrapper provides:
12/// - Thread safety (Send + Sync)
13/// - Type erasure (implements Any)
14/// - Weak reference to avoid circular dependencies
15/// - Safe downcasting when components need runtime access
16pub struct RuntimeRef {
17    /// Weak reference to avoid circular ownership
18    runtime_weak: Weak<RwLock<crate::AgentRuntime>>,
19
20    /// Cached agent ID for quick access
21    agent_id: Uuid,
22
23    /// Cached agent name for logging
24    agent_name: String,
25}
26
27impl RuntimeRef {
28    /// Create a new runtime reference
29    pub fn new(runtime: &Arc<RwLock<crate::AgentRuntime>>) -> Self {
30        let rt = runtime.read().unwrap();
31        Self {
32            runtime_weak: Arc::downgrade(runtime),
33            agent_id: rt.agent_id,
34            agent_name: rt.character.name.clone(),
35        }
36    }
37
38    /// Get agent ID without accessing full runtime
39    pub fn agent_id(&self) -> Uuid {
40        self.agent_id
41    }
42
43    /// Get agent name without accessing full runtime
44    pub fn agent_name(&self) -> &str {
45        &self.agent_name
46    }
47
48    /// Try to upgrade to full runtime access
49    /// Returns None if runtime has been dropped
50    pub fn try_upgrade(&self) -> Option<Arc<RwLock<crate::AgentRuntime>>> {
51        self.runtime_weak.upgrade()
52    }
53
54    /// Get a setting from runtime if available
55    pub fn get_setting(&self, key: &str) -> Option<serde_json::Value> {
56        self.try_upgrade().and_then(|rt| {
57            let runtime = rt.read().unwrap();
58            runtime.get_setting(key)
59        })
60    }
61}
62
63// Implement Send + Sync manually since we control the internals
64unsafe impl Send for RuntimeRef {}
65unsafe impl Sync for RuntimeRef {}
66
67impl std::fmt::Debug for RuntimeRef {
68    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69        f.debug_struct("RuntimeRef")
70            .field("agent_id", &self.agent_id)
71            .field("agent_name", &self.agent_name)
72            .field("runtime_available", &self.runtime_weak.strong_count())
73            .finish()
74    }
75}
76
77/// Convert RuntimeRef to type-erased Arc for component interfaces
78impl RuntimeRef {
79    /// Convert to type-erased Arc<dyn Any + Send + Sync>
80    pub fn as_any_arc(self: &Arc<Self>) -> Arc<dyn std::any::Any + Send + Sync> {
81        Arc::clone(self) as Arc<dyn std::any::Any + Send + Sync>
82    }
83}
84
85/// Helper to downcast from Any back to RuntimeRef
86pub fn downcast_runtime_ref(any: &Arc<dyn std::any::Any + Send + Sync>) -> Option<Arc<RuntimeRef>> {
87    any.clone().downcast::<RuntimeRef>().ok()
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93    use crate::{Character, RuntimeOpts};
94
95    #[tokio::test]
96    async fn test_runtime_ref() {
97        let runtime = crate::AgentRuntime::new(RuntimeOpts {
98            character: Some(Character {
99                name: "TestBot".to_string(),
100                ..Default::default()
101            }),
102            ..Default::default()
103        })
104        .await
105        .unwrap();
106
107        let runtime_ref = Arc::new(RuntimeRef::new(&runtime));
108
109        assert_eq!(runtime_ref.agent_name(), "TestBot");
110        assert!(runtime_ref.try_upgrade().is_some());
111    }
112
113    #[tokio::test]
114    async fn test_runtime_ref_weak() {
115        let runtime = crate::AgentRuntime::new(RuntimeOpts {
116            character: Some(Character {
117                name: "TestBot".to_string(),
118                ..Default::default()
119            }),
120            ..Default::default()
121        })
122        .await
123        .unwrap();
124
125        let runtime_ref = Arc::new(RuntimeRef::new(&runtime));
126
127        // Runtime still exists
128        assert!(runtime_ref.try_upgrade().is_some());
129
130        // Drop runtime
131        drop(runtime);
132
133        // Weak reference should fail to upgrade
134        assert!(runtime_ref.try_upgrade().is_none());
135    }
136
137    #[tokio::test]
138    async fn test_downcast() {
139        let runtime = crate::AgentRuntime::new(RuntimeOpts {
140            character: Some(Character {
141                name: "TestBot".to_string(),
142                ..Default::default()
143            }),
144            ..Default::default()
145        })
146        .await
147        .unwrap();
148
149        let runtime_ref = Arc::new(RuntimeRef::new(&runtime));
150        let any_arc = runtime_ref.as_any_arc();
151
152        let downcasted = downcast_runtime_ref(&any_arc);
153        assert!(downcasted.is_some());
154        assert_eq!(downcasted.unwrap().agent_name(), "TestBot");
155    }
156}