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}