1use crate::config::Config as PilotConfig;
6use crate::debug;
7use crate::features::commit::{service::CommitService, types::format_pull_request};
8use crate::git::GitRepo;
9use crate::server::tools::utils::{
10 PilotTool, apply_custom_instructions, create_text_result, resolve_git_repo,
11 validate_repository_parameter,
12};
13
14use rmcp::handler::server::tool::cached_schema_for_type;
15use rmcp::model::{CallToolResult, Tool};
16use rmcp::schemars;
17
18use serde::{Deserialize, Serialize};
19use std::borrow::Cow;
20use std::sync::Arc;
21
22#[derive(Debug, Deserialize, Serialize, schemars::JsonSchema)]
24pub struct PrTool {
25 pub from: String,
27
28 #[serde(default)]
30 pub to: String,
31
32 #[serde(default)]
34 pub preset: String,
35
36 #[serde(default)]
38 pub custom_instructions: String,
39
40 pub repository: String,
42}
43
44impl PrTool {
45 pub fn get_tool_definition() -> Tool {
47 Tool {
48 name: Cow::Borrowed("gitai_pr"),
49 description: Some(Cow::Borrowed(
50 "Generate comprehensive pull request descriptions for changesets spanning multiple commits. Analyzes commits and changes as an atomic unit.",
51 )),
52 input_schema: cached_schema_for_type::<Self>(),
53 annotations: None,
54 icons: None,
55 output_schema: None,
56 title: None,
57 }
58 }
59}
60
61#[async_trait::async_trait]
62impl PilotTool for PrTool {
63 async fn execute(
65 &self,
66 git_repo: Arc<GitRepo>,
67 config: PilotConfig,
68 ) -> Result<CallToolResult, anyhow::Error> {
69 debug!("Generating PR description with: {:?}", self);
70
71 validate_repository_parameter(&self.repository)?;
73 let git_repo = resolve_git_repo(Some(self.repository.as_str()), git_repo)?;
74 debug!("Using repository: {}", git_repo.repo_path().display());
75
76 if self.from.trim().is_empty() {
78 return Err(anyhow::anyhow!(
79 "The 'from' parameter is required for PR description generation"
80 ));
81 }
82
83 let to = if self.to.trim().is_empty() {
85 "HEAD".to_string()
86 } else {
87 self.to.clone()
88 };
89
90 if git_repo.is_remote() {
92 debug!("Operating on remote repository in read-only mode");
93 }
94
95 let repo_path = git_repo.repo_path().clone();
97 let provider_name = &config.default_provider;
98
99 let service = CommitService::new(
100 config.clone(),
101 &repo_path,
102 provider_name,
103 false, false, GitRepo::new(&repo_path)?,
106 )?;
107
108 let mut config_clone = config.clone();
110 apply_custom_instructions(&mut config_clone, &self.custom_instructions);
111
112 let preset = if self.preset.trim().is_empty() {
114 "default"
115 } else {
116 &self.preset
117 };
118
119 let pr_description = service
121 .generate_pr_for_commit_range(preset, &self.custom_instructions, &self.from, &to)
122 .await?;
123
124 let formatted_pr = format_pull_request(&pr_description);
126 Ok(create_text_result(formatted_pr))
127 }
128}