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