defect_agent/tool/
goal_done.rs1use std::pin::Pin;
21
22use agent_client_protocol_schema::{
23 Content, ContentBlock, TextContent, ToolCallContent, ToolCallUpdateFields, ToolKind,
24};
25use futures::future::BoxFuture;
26use serde::Deserialize;
27use serde_json::json;
28
29use crate::tool::{
30 SafetyClass, Tool, ToolCallDescription, ToolContext, ToolEvent, ToolSchema, ToolStream,
31};
32
33pub const GOAL_DONE_TOOL_NAME: &str = "goal_done";
35
36pub struct GoalDoneTool {
40 schema: ToolSchema,
41}
42
43impl Default for GoalDoneTool {
44 fn default() -> Self {
45 Self::new()
46 }
47}
48
49impl GoalDoneTool {
50 #[must_use]
51 pub fn new() -> Self {
52 Self {
53 schema: ToolSchema {
54 name: GOAL_DONE_TOOL_NAME.to_string(),
55 description: "Signal that the assigned goal has been fully achieved. Call this \
56 only when you are confident the goal is genuinely complete and there \
57 is nothing left to do. After calling it, stop taking further actions \
58 so the run can finish."
59 .to_string(),
60 input_schema: json!({
61 "type": "object",
62 "properties": {
63 "summary": {
64 "type": "string",
65 "description": "Brief summary of how the goal was achieved (what was done, key results)."
66 }
67 },
68 "required": []
69 }),
70 },
71 }
72 }
73}
74
75#[derive(Debug, Default, Deserialize)]
76struct GoalDoneArgs {
77 #[serde(default)]
78 summary: Option<String>,
79}
80
81impl Tool for GoalDoneTool {
82 fn schema(&self) -> &ToolSchema {
83 &self.schema
84 }
85
86 fn safety_hint(&self, _args: &serde_json::Value) -> SafetyClass {
87 SafetyClass::ReadOnly
88 }
89
90 fn describe<'a>(
91 &'a self,
92 _args: &'a serde_json::Value,
93 _ctx: ToolContext<'a>,
94 ) -> BoxFuture<'a, ToolCallDescription> {
95 Box::pin(async move {
96 let mut fields = ToolCallUpdateFields::default();
97 fields.title = Some("Mark goal as complete".to_string());
98 fields.kind = Some(ToolKind::Think);
99 ToolCallDescription { fields }
100 })
101 }
102
103 fn execute(&self, args: serde_json::Value, ctx: ToolContext<'_>) -> ToolStream {
104 let goal = ctx.goal.clone();
108 let parsed: GoalDoneArgs = serde_json::from_value(args).unwrap_or_default();
109 let fut = async move {
110 if let Some(goal) = goal {
111 goal.mark_reached();
112 }
113 let text = match parsed.summary {
114 Some(s) if !s.is_empty() => {
115 format!(
116 "Goal marked as complete. The run will finish once you stop. Summary: {s}"
117 )
118 }
119 _ => "Goal marked as complete. The run will finish once you stop.".to_string(),
120 };
121 let mut fields = ToolCallUpdateFields::default();
122 fields.content = Some(vec![ToolCallContent::Content(Content::new(
123 ContentBlock::Text(TextContent::new(text.clone())),
124 ))]);
125 fields.raw_output = Some(serde_json::Value::String(text));
126 ToolEvent::Completed(fields)
127 };
128 let s: Pin<Box<dyn futures::Stream<Item = ToolEvent> + Send>> =
129 Box::pin(futures::stream::once(fut));
130 s
131 }
132}