Skip to main content

anda_engine/
context.rs

1//! Execution context system for Anda agents and tools.
2//!
3//! Contexts carry the runtime capabilities exposed to [`anda_core`] agents and
4//! tools: caller identity, request metadata, cancellation, scoped cache, object
5//! storage, HTTP and canister calls, Web3 signing, remote engine access, and
6//! per-session state. Contexts are hierarchical so each agent or tool receives a
7//! scoped namespace while sharing the same underlying runtime resources.
8
9mod agent;
10mod base;
11mod cache;
12mod engine;
13mod tool;
14mod web3;
15
16pub use agent::*;
17pub use base::*;
18pub use engine::*;
19pub use tool::*;
20pub use web3::*;
21
22/// Mock implementations for tests.
23///
24/// This module provides mock implementations of core interfaces that allow
25/// for controlled testing environments without requiring actual canister calls.
26pub mod mock {
27    use anda_core::{BoxError, CanisterCaller};
28    use candid::{CandidType, Decode, Principal, encode_args, utils::ArgumentEncoder};
29
30    /// A mock implementation of CanisterCaller for testing purposes.
31    ///
32    /// This struct allows you to simulate canister calls by providing a transformation function
33    /// that takes the canister ID, method name, and arguments, and returns a response.
34    ///
35    /// # Example
36    /// ```rust,ignore
37    /// use anda_engine::context::mock::MockCanisterCaller;
38    /// use anda_core::CanisterCaller;
39    /// use candid::{encode_args, CandidType, Deserialize, Principal};
40    ///
41    /// #[derive(CandidType, Deserialize, Debug, PartialEq)]
42    /// struct TestResponse {
43    ///     canister: Principal,
44    ///     method: String,
45    ///     args: Vec<u8>,
46    /// }
47    ///
48    /// #[tokio::test]
49    /// async fn test_mock_canister_caller() {
50    ///     let canister_id = Principal::anonymous();
51    ///     let empty_args = encode_args(()).unwrap();
52    ///
53    ///     let caller = MockCanisterCaller::new(|canister, method, args| {
54    ///         let response = TestResponse {
55    ///             canister: canister.clone(),
56    ///             method: method.to_string(),
57    ///             args,
58    ///         };
59    ///         candid::encode_args((response,)).unwrap()
60    ///     });
61    ///
62    ///     let res: TestResponse = caller
63    ///         .canister_query(&canister_id, "canister_query", ())
64    ///         .await
65    ///         .unwrap();
66    ///     assert_eq!(res.canister, canister_id);
67    ///     assert_eq!(res.method, "canister_query");
68    ///     assert_eq!(res.args, empty_args);
69    ///
70    ///     let res: TestResponse = caller
71    ///         .canister_update(&canister_id, "canister_update", ())
72    ///         .await
73    ///         .unwrap();
74    ///     assert_eq!(res.canister, canister_id);
75    ///     assert_eq!(res.method, "canister_update");
76    ///     assert_eq!(res.args, empty_args);
77    /// }
78    /// ```
79    pub struct MockCanisterCaller<F: Fn(&Principal, &str, Vec<u8>) -> Vec<u8> + Send + Sync> {
80        transform: F,
81    }
82
83    impl<F> MockCanisterCaller<F>
84    where
85        F: Fn(&Principal, &str, Vec<u8>) -> Vec<u8> + Send + Sync,
86    {
87        /// Creates a new MockCanisterCaller with the provided transformation function.
88        ///
89        /// # Arguments
90        /// * `transform` - A function that takes (canister_id, method_name, args) and returns
91        ///   a serialized response
92        pub fn new(transform: F) -> Self {
93            Self { transform }
94        }
95    }
96
97    impl<F> CanisterCaller for MockCanisterCaller<F>
98    where
99        F: Fn(&Principal, &str, Vec<u8>) -> Vec<u8> + Send + Sync,
100    {
101        async fn canister_query<
102            In: ArgumentEncoder + Send,
103            Out: CandidType + for<'a> candid::Deserialize<'a>,
104        >(
105            &self,
106            canister: &Principal,
107            method: &str,
108            args: In,
109        ) -> Result<Out, BoxError> {
110            let args = encode_args(args)?;
111            let res = (self.transform)(canister, method, args);
112            let output = Decode!(res.as_slice(), Out)?;
113            Ok(output)
114        }
115
116        async fn canister_update<
117            In: ArgumentEncoder + Send,
118            Out: CandidType + for<'a> candid::Deserialize<'a>,
119        >(
120            &self,
121            canister: &Principal,
122            method: &str,
123            args: In,
124        ) -> Result<Out, BoxError> {
125            let args = encode_args(args)?;
126            let res = (self.transform)(canister, method, args);
127            let output = Decode!(res.as_slice(), Out)?;
128            Ok(output)
129        }
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136    use anda_core::CanisterCaller;
137    use candid::{CandidType, Deserialize, Principal, encode_args};
138
139    #[derive(CandidType, Deserialize, Debug, PartialEq)]
140    struct TestResponse {
141        canister: Principal,
142        method: String,
143        args: Vec<u8>,
144    }
145
146    #[tokio::test(flavor = "current_thread")]
147    async fn test_mock_canister_caller() {
148        let canister_id = Principal::anonymous();
149        let empty_args = encode_args(()).unwrap();
150
151        let caller = mock::MockCanisterCaller::new(|canister, method, args| {
152            let response = TestResponse {
153                canister: *canister,
154                method: method.to_string(),
155                args,
156            };
157            candid::encode_args((response,)).unwrap()
158        });
159
160        let res: TestResponse = caller
161            .canister_query(&canister_id, "canister_query", ())
162            .await
163            .unwrap();
164        assert_eq!(res.canister, canister_id);
165        assert_eq!(res.method, "canister_query");
166        assert_eq!(res.args, empty_args);
167
168        let res: TestResponse = caller
169            .canister_update(&canister_id, "canister_update", ())
170            .await
171            .unwrap();
172        assert_eq!(res.canister, canister_id);
173        assert_eq!(res.method, "canister_update");
174        assert_eq!(res.args, empty_args);
175    }
176}