Skip to main content

git_iris/agents/tools/
content_update.rs

1//! Content update tools for Iris Studio chat
2//!
3//! These tools allow Iris to update commit messages, PR descriptions, and reviews
4//! through proper tool calls rather than JSON parsing.
5
6use anyhow::Result;
7use rig::completion::ToolDefinition;
8use rig::tool::Tool;
9use serde::{Deserialize, Serialize};
10use serde_json::json;
11use std::sync::Arc;
12use tokio::sync::mpsc;
13
14use super::common::parameters_schema;
15
16// Use standard tool error macro for consistency
17crate::define_tool_error!(ContentUpdateError);
18
19/// The types of content updates that can be sent to the Studio
20#[derive(Debug, Clone)]
21pub enum ContentUpdate {
22    /// Update the commit message
23    Commit {
24        emoji: Option<String>,
25        title: String,
26        message: String,
27    },
28    /// Update the PR description
29    PR { content: String },
30    /// Update the code review
31    Review { content: String },
32}
33
34/// Channel capacity for content updates
35pub const CONTENT_UPDATE_CHANNEL_CAPACITY: usize = 100;
36
37/// Sender for content updates - passed to tools and listened to by Studio
38pub type ContentUpdateSender = mpsc::Sender<ContentUpdate>;
39pub type ContentUpdateReceiver = mpsc::Receiver<ContentUpdate>;
40
41/// Create a new bounded content update channel
42#[must_use]
43pub fn create_content_update_channel() -> (ContentUpdateSender, ContentUpdateReceiver) {
44    mpsc::channel(CONTENT_UPDATE_CHANNEL_CAPACITY)
45}
46
47// ═══════════════════════════════════════════════════════════════════════════════
48// Update Commit Tool
49// ═══════════════════════════════════════════════════════════════════════════════
50
51/// Tool for updating commit messages
52#[derive(Clone)]
53pub struct UpdateCommitTool {
54    sender: Arc<ContentUpdateSender>,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
58pub struct UpdateCommitArgs {
59    /// The emoji/gitmoji for the commit (e.g., "✨", "🐛", "♻️")
60    #[serde(default)]
61    pub emoji: Option<String>,
62    /// The commit title (first line, should be concise)
63    pub title: String,
64    /// The commit body/message (detailed description)
65    #[serde(default)]
66    pub message: Option<String>,
67}
68
69impl UpdateCommitTool {
70    #[must_use]
71    pub fn new(sender: ContentUpdateSender) -> Self {
72        Self {
73            sender: Arc::new(sender),
74        }
75    }
76}
77
78impl Tool for UpdateCommitTool {
79    const NAME: &'static str = "update_commit";
80    type Error = ContentUpdateError;
81    type Args = UpdateCommitArgs;
82    type Output = String;
83
84    async fn definition(&self, _: String) -> ToolDefinition {
85        ToolDefinition {
86            name: "update_commit".to_string(),
87            description: "Update the current commit message. Use this when the user asks you to modify, change, or rewrite the commit message. The update will be applied immediately.".to_string(),
88            parameters: parameters_schema::<UpdateCommitArgs>(),
89        }
90    }
91
92    async fn call(&self, args: Self::Args) -> Result<Self::Output, Self::Error> {
93        tracing::info!(
94            "update_commit tool called! emoji={:?}, title={}, message_len={}",
95            args.emoji,
96            args.title,
97            args.message.as_ref().map_or(0, std::string::String::len)
98        );
99
100        let update = ContentUpdate::Commit {
101            emoji: args.emoji.clone(),
102            title: args.title.clone(),
103            message: args.message.clone().unwrap_or_default(),
104        };
105
106        self.sender.try_send(update).map_err(|e| {
107            tracing::error!("Failed to send content update: {}", e);
108            ContentUpdateError(format!("Failed to send update: {}", e))
109        })?;
110
111        tracing::info!("Content update sent successfully via channel");
112
113        let result = json!({
114            "success": true,
115            "message": "Commit message updated successfully",
116            "new_title": args.title,
117            "emoji": args.emoji
118        });
119
120        serde_json::to_string_pretty(&result).map_err(|e| ContentUpdateError(e.to_string()))
121    }
122}
123
124// ═══════════════════════════════════════════════════════════════════════════════
125// Update PR Tool
126// ═══════════════════════════════════════════════════════════════════════════════
127
128/// Tool for updating PR descriptions
129#[derive(Clone)]
130pub struct UpdatePRTool {
131    sender: Arc<ContentUpdateSender>,
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
135pub struct UpdatePRArgs {
136    /// The complete PR description content (markdown)
137    pub content: String,
138}
139
140impl UpdatePRTool {
141    #[must_use]
142    pub fn new(sender: ContentUpdateSender) -> Self {
143        Self {
144            sender: Arc::new(sender),
145        }
146    }
147}
148
149impl Tool for UpdatePRTool {
150    const NAME: &'static str = "update_pr";
151    type Error = ContentUpdateError;
152    type Args = UpdatePRArgs;
153    type Output = String;
154
155    async fn definition(&self, _: String) -> ToolDefinition {
156        ToolDefinition {
157            name: "update_pr".to_string(),
158            description: "Update the current PR description. Use this when the user asks you to modify, change, or rewrite the PR content.".to_string(),
159            parameters: parameters_schema::<UpdatePRArgs>(),
160        }
161    }
162
163    async fn call(&self, args: Self::Args) -> Result<Self::Output, Self::Error> {
164        let content_len = args.content.len();
165        let update = ContentUpdate::PR {
166            content: args.content,
167        };
168
169        self.sender
170            .try_send(update)
171            .map_err(|e| ContentUpdateError(format!("Failed to send update: {}", e)))?;
172
173        let result = json!({
174            "success": true,
175            "message": "PR description updated successfully",
176            "content_length": content_len
177        });
178
179        serde_json::to_string_pretty(&result).map_err(|e| ContentUpdateError(e.to_string()))
180    }
181}
182
183// ═══════════════════════════════════════════════════════════════════════════════
184// Update Review Tool
185// ═══════════════════════════════════════════════════════════════════════════════
186
187/// Tool for updating code reviews
188#[derive(Clone)]
189pub struct UpdateReviewTool {
190    sender: Arc<ContentUpdateSender>,
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
194pub struct UpdateReviewArgs {
195    /// The complete review content (markdown)
196    pub content: String,
197}
198
199impl UpdateReviewTool {
200    #[must_use]
201    pub fn new(sender: ContentUpdateSender) -> Self {
202        Self {
203            sender: Arc::new(sender),
204        }
205    }
206}
207
208impl Tool for UpdateReviewTool {
209    const NAME: &'static str = "update_review";
210    type Error = ContentUpdateError;
211    type Args = UpdateReviewArgs;
212    type Output = String;
213
214    async fn definition(&self, _: String) -> ToolDefinition {
215        ToolDefinition {
216            name: "update_review".to_string(),
217            description: "Update the current code review. Use this when the user asks you to modify, change, or rewrite the review content.".to_string(),
218            parameters: parameters_schema::<UpdateReviewArgs>(),
219        }
220    }
221
222    async fn call(&self, args: Self::Args) -> Result<Self::Output, Self::Error> {
223        let content_len = args.content.len();
224        let update = ContentUpdate::Review {
225            content: args.content,
226        };
227
228        self.sender
229            .try_send(update)
230            .map_err(|e| ContentUpdateError(format!("Failed to send update: {}", e)))?;
231
232        let result = json!({
233            "success": true,
234            "message": "Review updated successfully",
235            "content_length": content_len
236        });
237
238        serde_json::to_string_pretty(&result).map_err(|e| ContentUpdateError(e.to_string()))
239    }
240}