1use async_openai::config::OpenAIConfig;
2use tokio::sync::{broadcast, mpsc, oneshot};
3
4#[cfg(feature = "tool")]
5use crate::event_bus::tool_bus::ToolBus;
6#[cfg(feature = "skill")]
7use crate::re_act::skills::Skill;
8#[cfg(feature = "tool")]
9use crate::re_act::tool::Tool;
10#[cfg(feature = "tool")]
11use crate::re_act::tool_executor::ToolExecutor;
12#[cfg(feature = "security")]
13use crate::security::audit::{AuditBus, AuditEvent};
14#[cfg(feature = "security")]
15use crate::security::policy::ToolPolicy;
16#[cfg(feature = "sandbox")]
17use crate::security::sandbox::SandboxPolicy;
18
19use crate::env::{FuneraEnv, FuneraEnvWatcher};
20use crate::event_bus::env_state_bus::EnvStateEvent;
21
22pub struct EnvToolConfig {
32 #[cfg(feature = "tool")]
33 pub tool_bus: ToolBus,
34 #[cfg(feature = "tool")]
35 pub exec_rx: mpsc::Receiver<crate::event_bus::tool_bus::ToolExecCommand>,
36}
37
38pub struct EnvSecurityConfig {
40 #[cfg(feature = "sandbox")]
41 pub sandbox_policy: SandboxPolicy,
42 #[cfg(feature = "security")]
43 pub tool_policy: ToolPolicy,
44 #[cfg(feature = "security")]
45 pub audit_bus: AuditBus,
46}
47
48pub struct ReActConfig {
53 pub env_watcher: FuneraEnvWatcher,
54 #[cfg(feature = "tool")]
55 pub tool_bus: ToolBus,
56 pub max_iterations: usize,
57 pub channel_buffer: usize,
58}
59
60pub enum EnvCmd {
65 SetModel(String),
67 SetClient(async_openai::Client<OpenAIConfig>),
68 #[cfg(feature = "tool")]
69 AddTool(Box<dyn Tool>),
70 #[cfg(feature = "tool")]
71 RemoveTool(String),
72 #[cfg(feature = "tool")]
73 SetToolAvailability {
74 name: String,
75 available: bool,
76 },
77 #[cfg(feature = "skill")]
78 AddSkill(Skill),
79 #[cfg(feature = "skill")]
80 RemoveSkill(String),
81 #[cfg(feature = "skill")]
82 ActivateSkill {
83 name: String,
84 respond: oneshot::Sender<bool>,
85 },
86 #[cfg(feature = "skill")]
87 DeactivateSkill {
88 name: String,
89 respond: oneshot::Sender<bool>,
90 },
91 #[cfg(feature = "skill")]
92 SetSkillPrompt(String),
93
94 #[cfg(feature = "skill")]
96 GetSkillPrompt {
97 respond: oneshot::Sender<String>,
98 },
99 SubscribeEnvState {
100 respond: oneshot::Sender<broadcast::Receiver<EnvStateEvent>>,
101 },
102 GetReActConfig {
103 respond: oneshot::Sender<ReActConfig>,
104 },
105 GetModel {
106 respond: oneshot::Sender<String>,
107 },
108 #[cfg(feature = "tool")]
109 GetToolNames {
110 respond: oneshot::Sender<Vec<String>>,
111 },
112 #[cfg(feature = "sandbox")]
113 GetSandboxPolicy {
114 respond: oneshot::Sender<SandboxPolicy>,
115 },
116 #[cfg(all(feature = "tool", feature = "security"))]
117 ApproveToolCall {
118 call_id: String,
119 approved: bool,
120 respond: oneshot::Sender<Result<(), String>>,
121 },
122 #[cfg(feature = "security")]
123 SubscribeAudit {
124 respond: oneshot::Sender<broadcast::Receiver<AuditEvent>>,
125 },
126}
127
128pub fn spawn_env_actor(
140 env: FuneraEnv,
141 env_watcher: FuneraEnvWatcher,
142 max_iterations: usize,
143 channel_buffer: usize,
144 tool: Option<EnvToolConfig>,
145 security: Option<EnvSecurityConfig>,
146) -> mpsc::UnboundedSender<EnvCmd> {
147 let (cmd_tx, mut cmd_rx) = mpsc::unbounded_channel::<EnvCmd>();
148 let (state_tx, _) = broadcast::channel::<EnvStateEvent>(32);
149
150 let mut env = env;
151
152 #[cfg(feature = "tool")]
154 let tool_bus_for_react = {
155 if let Some(tc) = tool {
156 let reg = env.tool_registry.clone();
157 let tb = tc.tool_bus.clone();
158 tokio::spawn(async move {
159 ToolExecutor::new(reg, tc.exec_rx).run().await;
160 });
161 Some(tb)
162 } else {
163 None
164 }
165 };
166
167 #[cfg(feature = "sandbox")]
169 let sandbox_policy = {
170 let _s = &security;
171 _s.as_ref()
172 .map(|s| s.sandbox_policy.clone())
173 .unwrap_or_default()
174 };
175 #[cfg(feature = "security")]
176 let audit_bus = {
177 let _s = &security;
178 _s.as_ref().map(|s| s.audit_bus.clone()).unwrap_or_default()
179 };
180 #[cfg(not(any(feature = "sandbox", feature = "security")))]
181 let _ = &security;
182
183 tokio::spawn(async move {
184 #[cfg(feature = "tool")]
186 if let Ok(guard) = env.tool_registry.try_read() {
187 for name in guard.get_all_tools().keys() {
188 let _ = state_tx.send(EnvStateEvent::ToolAdded(name.clone()));
189 }
190 }
191 #[cfg(feature = "skill")]
192 if let Ok(guard) = env.skill_registry.try_read() {
193 for name in guard.all_skills().keys() {
194 let _ = state_tx.send(EnvStateEvent::SkillAdded(name.clone()));
195 }
196 }
197
198 while let Some(cmd) = cmd_rx.recv().await {
200 match cmd {
201 EnvCmd::SetModel(model) => {
203 env.set_model(&model);
204 let _ = state_tx.send(EnvStateEvent::LlmChanged(model));
205 }
206 EnvCmd::SetClient(client) => {
207 env.set_client(client);
208 }
209 #[cfg(feature = "tool")]
210 EnvCmd::AddTool(tool) => {
211 let name = tool.name().to_string();
212 env.add_tool(tool).await;
213 let _ = state_tx.send(EnvStateEvent::ToolAdded(name));
214 }
215 #[cfg(feature = "tool")]
216 EnvCmd::RemoveTool(name) => {
217 env.remove_tool(&name).await;
218 let _ = state_tx.send(EnvStateEvent::ToolRemoved(name));
219 }
220 #[cfg(feature = "tool")]
221 EnvCmd::SetToolAvailability { name, available } => {
222 env.set_tool_availability(&name, available).await;
223 let _ = state_tx.send(EnvStateEvent::ToolAvailability(name, available));
224 }
225 #[cfg(feature = "skill")]
226 EnvCmd::AddSkill(skill) => {
227 let name = skill.name.clone();
228 env.add_skill(skill).await;
229 let _ = state_tx.send(EnvStateEvent::SkillAdded(name));
230 }
231 #[cfg(feature = "skill")]
232 EnvCmd::RemoveSkill(name) => {
233 env.remove_skill(&name).await;
234 let _ = state_tx.send(EnvStateEvent::SkillRemoved(name.clone()));
235 }
236 #[cfg(feature = "skill")]
237 EnvCmd::ActivateSkill { name, respond } => {
238 let ok = env.activate_skill(&name).await;
239 let _ = respond.send(ok);
240 if ok {
241 let _ = state_tx.send(EnvStateEvent::SkillActivated(name));
242 }
243 }
244 #[cfg(feature = "skill")]
245 EnvCmd::DeactivateSkill { name, respond } => {
246 let ok = env.deactivate_skill(&name).await;
247 let _ = respond.send(ok);
248 if ok {
249 let _ = state_tx.send(EnvStateEvent::SkillDeactivated(name));
250 }
251 }
252 #[cfg(feature = "skill")]
253 EnvCmd::SetSkillPrompt(prompt) => {
254 env.set_skill_prompt(prompt);
255 }
256
257 #[cfg(feature = "skill")]
259 EnvCmd::GetSkillPrompt { respond } => {
260 let _ = respond.send(env.skill_prompt_now());
261 }
262 EnvCmd::SubscribeEnvState { respond } => {
263 let _ = respond.send(state_tx.subscribe());
264 }
265 EnvCmd::GetReActConfig { respond } => {
266 let _ = respond.send(ReActConfig {
267 env_watcher: env_watcher.clone(),
268 #[cfg(feature = "tool")]
269 tool_bus: tool_bus_for_react
270 .clone()
271 .expect("tool_bus must be Some when tool feature is enabled"),
272 max_iterations,
273 channel_buffer,
274 });
275 }
276 EnvCmd::GetModel { respond } => {
277 let _ = respond.send(env.model().to_string());
278 }
279 #[cfg(feature = "tool")]
280 EnvCmd::GetToolNames { respond } => {
281 let names = if let Ok(guard) = env.tool_registry.try_read() {
282 guard.get_all_tools().keys().cloned().collect()
283 } else {
284 Vec::new()
285 };
286 let _ = respond.send(names);
287 }
288 #[cfg(feature = "sandbox")]
289 EnvCmd::GetSandboxPolicy { respond } => {
290 let _ = respond.send(sandbox_policy.clone());
291 }
292 #[cfg(all(feature = "tool", feature = "security"))]
293 EnvCmd::ApproveToolCall {
294 call_id,
295 approved,
296 respond,
297 } => {
298 let result = env
299 .tool_registry
300 .read()
301 .await
302 .approve_tool_call(&call_id, approved);
303 let _ = respond.send(result);
304 }
305 #[cfg(feature = "security")]
306 EnvCmd::SubscribeAudit { respond } => {
307 let _ = respond.send(audit_bus.subscribe());
308 }
309 }
310 }
311 });
312
313 cmd_tx
314}