1use async_trait::async_trait;
2use serde_json::{json, Value};
3use tokio::sync::Mutex;
4
5use crate::engine::AgentRuntime;
6use crate::types::{AgentError, AgentEvent, AgentResult, SessionId};
7use super::{Tool, ToolContext, ToolControlFlow, ToolOutput};
8
9#[derive(Clone, Debug)]
11pub enum SubAgentSessionPolicy {
12 Ephemeral,
14 Persistent,
16}
17
18pub struct SubAgentTool {
19 name: &'static str,
20 description: &'static str,
21 sub_runtime: Mutex<AgentRuntime>,
22 sub_session_id: Mutex<Option<SessionId>>,
23 session_policy: SubAgentSessionPolicy,
24}
25
26impl SubAgentTool {
27 pub fn new(
28 name: &'static str,
29 description: &'static str,
30 sub_runtime: AgentRuntime,
31 ) -> Self {
32 Self {
33 name,
34 description,
35 sub_runtime: Mutex::new(sub_runtime),
36 sub_session_id: Mutex::new(None),
37 session_policy: SubAgentSessionPolicy::Ephemeral,
38 }
39 }
40
41 pub fn with_persistent(
42 name: &'static str,
43 description: &'static str,
44 sub_runtime: AgentRuntime,
45 ) -> Self {
46 Self {
47 name,
48 description,
49 sub_runtime: Mutex::new(sub_runtime),
50 sub_session_id: Mutex::new(None),
51 session_policy: SubAgentSessionPolicy::Persistent,
52 }
53 }
54}
55
56#[async_trait]
57impl Tool for SubAgentTool {
58 fn name(&self) -> &'static str {
59 self.name
60 }
61
62 fn definition(&self) -> Value {
63 json!({
64 "type": "function",
65 "function": {
66 "name": self.name,
67 "description": self.description,
68 "parameters": {
69 "type": "object",
70 "properties": {
71 "task": {
72 "type": "string",
73 "description": "Task description to delegate to the sub-agent"
74 }
75 },
76 "required": ["task"]
77 }
78 }
79 })
80 }
81
82 async fn call(&self, args: &Value, ctx: &ToolContext) -> AgentResult<ToolOutput> {
83 let task = args
84 .get("task")
85 .and_then(Value::as_str)
86 .ok_or_else(|| AgentError::ToolArgsInvalid {
87 name: self.name.to_string(),
88 raw: args.to_string(),
89 })?;
90
91 if task.is_empty() {
92 return Ok(ToolOutput {
93 summary: "Task description is empty, cannot execute".to_string(),
94 raw: None,
95 control_flow: ToolControlFlow::Break,
96 truncation: None,
97 });
98 }
99
100 let parent_event_bus = ctx.event_bus.clone();
101 let parent_session_id = ctx.session_id.clone();
102
103 let sub_session_id = match self.session_policy {
104 SubAgentSessionPolicy::Ephemeral => {
105 let runtime = self.sub_runtime.lock().await;
106 let new_id = runtime.create_session().await;
107 let mut sid_guard = self.sub_session_id.lock().await;
108 *sid_guard = Some(new_id.clone());
109 new_id
110 }
111 SubAgentSessionPolicy::Persistent => {
112 let mut sid_guard = self.sub_session_id.lock().await;
113 if let Some(id) = sid_guard.clone() {
114 id
115 } else {
116 let runtime = self.sub_runtime.lock().await;
117 let new_id = runtime.create_session().await;
118 *sid_guard = Some(new_id.clone());
119 new_id
120 }
121 }
122 };
123
124 {
126 let runtime = self.sub_runtime.lock().await;
127 runtime.add_user_message(&sub_session_id, task).await.map_err(|e| AgentError::ToolExecution {
128 name: self.name.to_string(),
129 source: Box::new(e),
130 })?;
131 }
132
133 let mut events = Vec::new();
134 let _outcome = {
135 let runtime = self.sub_runtime.lock().await;
136 runtime
137 .run(sub_session_id, |event| {
138 events.push(event.clone());
139 Ok(())
140 })
141 .await
142 .map_err(|e| AgentError::ToolExecution {
143 name: self.name.to_string(),
144 source: Box::new(e),
145 })?
146 };
147
148 let mut final_text = String::new();
149 for event in &events {
150 match event {
151 AgentEvent::TextDelta { text, .. } => {
152 final_text.push_str(&text);
153 }
154 _ => {}
155 }
156 let _ = parent_event_bus.send(AgentEvent::Custom {
157 session_id: parent_session_id.clone(),
158 payload: json!({
159 "type": "subagent_event",
160 "subagent": self.name,
161 "event": event_to_value(&event),
162 }),
163 });
164 }
165
166 let summary = if final_text.is_empty() {
167 format!("Sub-agent [{}] finished", self.name)
168 } else {
169 final_text
170 };
171
172 Ok(ToolOutput {
173 summary,
174 raw: None,
175 control_flow: ToolControlFlow::Continue,
176 truncation: None,
177 })
178 }
179}
180
181fn event_to_value(event: &AgentEvent) -> Value {
182 match event {
183 AgentEvent::TextDelta { text, .. } => json!({"type": "TextDelta", "text": text}),
184 AgentEvent::ThoughtDelta { text, .. } => json!({"type": "ThoughtDelta", "text": text}),
185 AgentEvent::ToolCallStarted { tool_name, args_json, .. } => {
186 json!({"type": "ToolCallStarted", "tool_name": tool_name, "args_json": args_json})
187 }
188 AgentEvent::ToolCallFinished { tool_name, summary, .. } => {
189 json!({"type": "ToolCallFinished", "tool_name": tool_name, "summary": summary})
190 }
191 AgentEvent::AwaitingApproval { request, .. } => {
192 json!({"type": "AwaitingApproval", "title": request.title})
193 }
194 AgentEvent::Checkpoint { .. } => json!({"type": "Checkpoint"}),
195 AgentEvent::RunFinished { .. } => json!({"type": "RunFinished"}),
196 AgentEvent::Custom { payload, .. } => json!({"type": "Custom", "payload": payload}),
197 AgentEvent::PlanGenerated { plan, .. } => json!({"type": "PlanGenerated", "plan_id": plan.id}),
198 AgentEvent::PlanStepStarted { step_id, step_description, .. } => {
199 json!({"type": "PlanStepStarted", "step_id": step_id, "step_description": step_description})
200 }
201 AgentEvent::PlanStepCompleted { step_id, success, result, .. } => {
202 json!({"type": "PlanStepCompleted", "step_id": step_id, "success": success, "result": result})
203 }
204 AgentEvent::PlanCompleted { plan_id, success, .. } => {
205 json!({"type": "PlanCompleted", "plan_id": plan_id, "success": success})
206 }
207 AgentEvent::PlanGenerating { plan_id, .. } => {
208 json!({"type": "PlanGenerating", "plan_id": plan_id})
209 }
210 AgentEvent::PlanStepParsed { plan_id, step_index, step_id, step_description, .. } => {
211 json!({"type": "PlanStepParsed", "plan_id": plan_id, "step_index": step_index, "step_id": step_id, "step_description": step_description})
212 }
213 AgentEvent::PlanFailed { plan_id, error, .. } => {
214 json!({"type": "PlanFailed", "plan_id": plan_id, "error": error})
215 }
216 }
217}