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
42pub fn create_content_update_channel() -> (ContentUpdateSender, ContentUpdateReceiver) {
43    mpsc::channel(CONTENT_UPDATE_CHANNEL_CAPACITY)
44}
45
46// ═══════════════════════════════════════════════════════════════════════════════
47// Update Commit Tool
48// ═══════════════════════════════════════════════════════════════════════════════
49
50/// Tool for updating commit messages
51#[derive(Clone)]
52pub struct UpdateCommitTool {
53    sender: Arc<ContentUpdateSender>,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
57pub struct UpdateCommitArgs {
58    /// The emoji/gitmoji for the commit (e.g., "✨", "🐛", "♻️")
59    #[serde(default)]
60    pub emoji: Option<String>,
61    /// The commit title (first line, should be concise)
62    pub title: String,
63    /// The commit body/message (detailed description)
64    #[serde(default)]
65    pub message: Option<String>,
66}
67
68impl UpdateCommitTool {
69    pub fn new(sender: ContentUpdateSender) -> Self {
70        Self {
71            sender: Arc::new(sender),
72        }
73    }
74}
75
76impl Tool for UpdateCommitTool {
77    const NAME: &'static str = "update_commit";
78    type Error = ContentUpdateError;
79    type Args = UpdateCommitArgs;
80    type Output = String;
81
82    async fn definition(&self, _: String) -> ToolDefinition {
83        ToolDefinition {
84            name: "update_commit".to_string(),
85            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(),
86            parameters: parameters_schema::<UpdateCommitArgs>(),
87        }
88    }
89
90    async fn call(&self, args: Self::Args) -> Result<Self::Output, Self::Error> {
91        tracing::info!(
92            "update_commit tool called! emoji={:?}, title={}, message_len={}",
93            args.emoji,
94            args.title,
95            args.message.as_ref().map_or(0, std::string::String::len)
96        );
97
98        let update = ContentUpdate::Commit {
99            emoji: args.emoji.clone(),
100            title: args.title.clone(),
101            message: args.message.clone().unwrap_or_default(),
102        };
103
104        self.sender.try_send(update).map_err(|e| {
105            tracing::error!("Failed to send content update: {}", e);
106            ContentUpdateError(format!("Failed to send update: {}", e))
107        })?;
108
109        tracing::info!("Content update sent successfully via channel");
110
111        let result = json!({
112            "success": true,
113            "message": "Commit message updated successfully",
114            "new_title": args.title,
115            "emoji": args.emoji
116        });
117
118        serde_json::to_string_pretty(&result).map_err(|e| ContentUpdateError(e.to_string()))
119    }
120}
121
122// ═══════════════════════════════════════════════════════════════════════════════
123// Update PR Tool
124// ═══════════════════════════════════════════════════════════════════════════════
125
126/// Tool for updating PR descriptions
127#[derive(Clone)]
128pub struct UpdatePRTool {
129    sender: Arc<ContentUpdateSender>,
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
133pub struct UpdatePRArgs {
134    /// The complete PR description content (markdown)
135    pub content: String,
136}
137
138impl UpdatePRTool {
139    pub fn new(sender: ContentUpdateSender) -> Self {
140        Self {
141            sender: Arc::new(sender),
142        }
143    }
144}
145
146impl Tool for UpdatePRTool {
147    const NAME: &'static str = "update_pr";
148    type Error = ContentUpdateError;
149    type Args = UpdatePRArgs;
150    type Output = String;
151
152    async fn definition(&self, _: String) -> ToolDefinition {
153        ToolDefinition {
154            name: "update_pr".to_string(),
155            description: "Update the current PR description. Use this when the user asks you to modify, change, or rewrite the PR content.".to_string(),
156            parameters: parameters_schema::<UpdatePRArgs>(),
157        }
158    }
159
160    async fn call(&self, args: Self::Args) -> Result<Self::Output, Self::Error> {
161        let content_len = args.content.len();
162        let update = ContentUpdate::PR {
163            content: args.content,
164        };
165
166        self.sender
167            .try_send(update)
168            .map_err(|e| ContentUpdateError(format!("Failed to send update: {}", e)))?;
169
170        let result = json!({
171            "success": true,
172            "message": "PR description updated successfully",
173            "content_length": content_len
174        });
175
176        serde_json::to_string_pretty(&result).map_err(|e| ContentUpdateError(e.to_string()))
177    }
178}
179
180// ═══════════════════════════════════════════════════════════════════════════════
181// Update Review Tool
182// ═══════════════════════════════════════════════════════════════════════════════
183
184/// Tool for updating code reviews
185#[derive(Clone)]
186pub struct UpdateReviewTool {
187    sender: Arc<ContentUpdateSender>,
188}
189
190#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
191pub struct UpdateReviewArgs {
192    /// The complete review content (markdown)
193    pub content: String,
194}
195
196impl UpdateReviewTool {
197    pub fn new(sender: ContentUpdateSender) -> Self {
198        Self {
199            sender: Arc::new(sender),
200        }
201    }
202}
203
204impl Tool for UpdateReviewTool {
205    const NAME: &'static str = "update_review";
206    type Error = ContentUpdateError;
207    type Args = UpdateReviewArgs;
208    type Output = String;
209
210    async fn definition(&self, _: String) -> ToolDefinition {
211        ToolDefinition {
212            name: "update_review".to_string(),
213            description: "Update the current code review. Use this when the user asks you to modify, change, or rewrite the review content.".to_string(),
214            parameters: parameters_schema::<UpdateReviewArgs>(),
215        }
216    }
217
218    async fn call(&self, args: Self::Args) -> Result<Self::Output, Self::Error> {
219        let content_len = args.content.len();
220        let update = ContentUpdate::Review {
221            content: args.content,
222        };
223
224        self.sender
225            .try_send(update)
226            .map_err(|e| ContentUpdateError(format!("Failed to send update: {}", e)))?;
227
228        let result = json!({
229            "success": true,
230            "message": "Review updated successfully",
231            "content_length": content_len
232        });
233
234        serde_json::to_string_pretty(&result).map_err(|e| ContentUpdateError(e.to_string()))
235    }
236}