oxi_agent/tools/
yield_tool.rs1use super::{AgentTool, AgentToolResult, ToolContext, ToolError, ToolExecutionMode, ToolTier};
8use async_trait::async_trait;
9use serde_json::{Value, json};
10use std::sync::LazyLock;
11use std::sync::Mutex;
12use tokio::sync::oneshot;
13
14#[derive(Clone, Debug)]
16#[allow(dead_code)]
17struct YieldedResult {
18 result_type: String,
20 data: Option<Value>,
22 error: Option<String>,
24 summary: Option<String>,
26 timestamp: u64,
28}
29
30static YIELD_STORE: LazyLock<Mutex<Vec<YieldedResult>>> = LazyLock::new(|| Mutex::new(Vec::new()));
32
33pub struct YieldTool;
35
36#[async_trait]
37impl AgentTool for YieldTool {
38 fn name(&self) -> &str {
39 "yield"
40 }
41
42 fn label(&self) -> &str {
43 "Yield"
44 }
45
46 fn description(&self) -> &str {
47 concat!(
48 "Submit subagent output with type labels and optional structured data. ",
49 "Use 'result' for final output, 'findings' for incremental code review ",
50 "findings, or 'progress' for interim updates. ",
51 "Provide 'data' for structured results or 'error' for failures."
52 )
53 }
54
55 fn essential(&self) -> bool {
56 false
57 }
58
59 fn parameters_schema(&self) -> Value {
60 json!({
61 "type": "object",
62 "properties": {
63 "type": {
64 "type": "string",
65 "enum": ["result", "findings", "progress"],
66 "description": "Submission type: 'result' (final), 'findings' (incremental), 'progress' (interim)."
67 },
68 "data": {
69 "type": "object",
70 "description": "Structured output data (arbitrary JSON)."
71 },
72 "error": {
73 "type": "string",
74 "description": "Error message if the subagent failed."
75 },
76 "summary": {
77 "type": "string",
78 "description": "One-line summary of the result."
79 }
80 }
81 })
82 }
83
84 fn intent(&self) -> Option<&str> {
85 Some("Submit subagent output")
86 }
87
88 fn execution_mode(&self) -> ToolExecutionMode {
89 ToolExecutionMode::SequentialOnly
90 }
91
92 fn tool_tier(&self) -> ToolTier {
93 ToolTier::Read
94 }
95
96 async fn execute(
97 &self,
98 _tool_call_id: &str,
99 params: Value,
100 _signal: Option<oneshot::Receiver<()>>,
101 _ctx: &ToolContext,
102 ) -> Result<AgentToolResult, ToolError> {
103 let result_type = params
104 .get("type")
105 .and_then(|v| v.as_str())
106 .unwrap_or("result")
107 .to_string();
108
109 let data = params.get("data").cloned();
110 let error = params
111 .get("error")
112 .and_then(|v| v.as_str())
113 .map(String::from);
114 let summary = params
115 .get("summary")
116 .and_then(|v| v.as_str())
117 .map(String::from);
118
119 let yielded = YieldedResult {
120 result_type: result_type.clone(),
121 data,
122 error: error.clone(),
123 summary: summary.clone(),
124 timestamp: std::time::SystemTime::now()
125 .duration_since(std::time::UNIX_EPOCH)
126 .map(|d| d.as_secs())
127 .unwrap_or(0),
128 };
129
130 let mut store = YIELD_STORE
131 .lock()
132 .map_err(|e| format!("Yield store lock error: {}", e))?;
133 store.push(yielded);
134
135 let mut lines = vec![format!("Yielded result (type: {})", result_type)];
136 if let Some(ref s) = summary {
137 lines.push(format!("Summary: {}", s));
138 }
139 if let Some(ref e) = error {
140 lines.push(format!("Error: {}", e));
141 }
142
143 Ok(AgentToolResult::success(format!(
144 "{}\nResult stored {} entry.",
145 lines.join("\n"),
146 store.len()
147 )))
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[tokio::test]
156 async fn test_yield_result() {
157 *YIELD_STORE.lock().unwrap() = Vec::new();
159
160 let tool = YieldTool;
161 let params = json!({
162 "type": "result",
163 "data": {"output": "completed analysis"},
164 "summary": "Analysis complete"
165 });
166 let result = tool
167 .execute("id", params, None, &ToolContext::default())
168 .await
169 .unwrap();
170 assert!(result.success);
171 assert!(result.output.contains("Yielded result"));
172 }
173
174 #[tokio::test]
175 async fn test_yield_findings() {
176 let tool = YieldTool;
177 let params = json!({
178 "type": "findings",
179 "data": {
180 "findings": [
181 {"title": "Security issue", "priority": "P0", "file": "src/auth.rs"}
182 ]
183 }
184 });
185 let result = tool
186 .execute("id", params, None, &ToolContext::default())
187 .await
188 .unwrap();
189 assert!(result.success);
190 }
191
192 #[tokio::test]
193 async fn test_yield_error() {
194 let tool = YieldTool;
195 let params = json!({
196 "type": "result",
197 "error": "Analysis failed: timeout",
198 "summary": "Failed to complete"
199 });
200 let result = tool
201 .execute("id", params, None, &ToolContext::default())
202 .await
203 .unwrap();
204 assert!(result.success);
205 assert!(result.output.contains("Error:"));
206 }
207}