syncable_cli/agent/tools/
generate.rs1use rig::completion::ToolDefinition;
6use rig::tool::Tool;
7use serde::{Deserialize, Serialize};
8use serde_json::json;
9use std::path::PathBuf;
10
11use crate::analyzer::analyze_monorepo;
12use crate::generator;
13
14#[derive(Debug, Deserialize)]
16pub struct GenerateIaCArgs {
17 pub generate_type: String,
19 pub path: Option<String>,
21}
22
23#[derive(Debug, thiserror::Error)]
25#[error("Generation error: {0}")]
26pub struct GenerateIaCError(String);
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct GenerateIaCTool {
31 project_path: PathBuf,
32}
33
34impl GenerateIaCTool {
35 pub fn new(project_path: PathBuf) -> Self {
36 Self { project_path }
37 }
38}
39
40impl Tool for GenerateIaCTool {
41 const NAME: &'static str = "generate_iac";
42
43 type Error = GenerateIaCError;
44 type Args = GenerateIaCArgs;
45 type Output = String;
46
47 async fn definition(&self, _prompt: String) -> ToolDefinition {
48 ToolDefinition {
49 name: Self::NAME.to_string(),
50 description: "Generate Infrastructure as Code files based on project analysis. Can generate Dockerfiles, Docker Compose configurations, or Terraform files. Returns the generated content as a preview without writing to disk.".to_string(),
51 parameters: json!({
52 "type": "object",
53 "properties": {
54 "generate_type": {
55 "type": "string",
56 "enum": ["dockerfile", "compose", "terraform", "all"],
57 "description": "Type of IaC to generate: 'dockerfile' for container config, 'compose' for Docker Compose, 'terraform' for infrastructure, 'all' for everything"
58 },
59 "path": {
60 "type": "string",
61 "description": "Optional subdirectory to analyze for generation (relative to project root)"
62 }
63 },
64 "required": ["generate_type"]
65 }),
66 }
67 }
68
69 async fn call(&self, args: Self::Args) -> Result<Self::Output, Self::Error> {
70 let path = if let Some(subpath) = args.path {
71 self.project_path.join(subpath)
72 } else {
73 self.project_path.clone()
74 };
75
76 let monorepo_analysis = analyze_monorepo(&path)
78 .map_err(|e| GenerateIaCError(format!("Analysis failed: {}", e)))?;
79
80 let main_project = &monorepo_analysis.projects[0];
82 let analysis = &main_project.analysis;
83
84 let generate_type = args.generate_type.to_lowercase();
85 let generate_all = generate_type == "all";
86
87 let mut results = Vec::new();
88
89 if generate_all || generate_type == "dockerfile" {
91 match generator::generate_dockerfile(analysis) {
92 Ok(content) => {
93 results.push(json!({
94 "type": "Dockerfile",
95 "content": content,
96 "filename": "Dockerfile"
97 }));
98 }
99 Err(e) => {
100 results.push(json!({
101 "type": "Dockerfile",
102 "error": e.to_string()
103 }));
104 }
105 }
106 }
107
108 if generate_all || generate_type == "compose" {
110 match generator::generate_compose(analysis) {
111 Ok(content) => {
112 results.push(json!({
113 "type": "Docker Compose",
114 "content": content,
115 "filename": "docker-compose.yml"
116 }));
117 }
118 Err(e) => {
119 results.push(json!({
120 "type": "Docker Compose",
121 "error": e.to_string()
122 }));
123 }
124 }
125 }
126
127 if generate_all || generate_type == "terraform" {
129 match generator::generate_terraform(analysis) {
130 Ok(content) => {
131 results.push(json!({
132 "type": "Terraform",
133 "content": content,
134 "filename": "main.tf"
135 }));
136 }
137 Err(e) => {
138 results.push(json!({
139 "type": "Terraform",
140 "error": e.to_string()
141 }));
142 }
143 }
144 }
145
146 let project_info = json!({
148 "project_name": main_project.name,
149 "languages": monorepo_analysis.technology_summary.languages,
150 "frameworks": monorepo_analysis.technology_summary.frameworks,
151 "is_monorepo": monorepo_analysis.is_monorepo,
152 "project_count": monorepo_analysis.projects.len()
153 });
154
155 let result = json!({
156 "generated": results,
157 "project_info": project_info,
158 "note": "This is a preview. The content has not been written to disk. Share with the user and ask if they want to save these files."
159 });
160
161 serde_json::to_string_pretty(&result)
162 .map_err(|e| GenerateIaCError(format!("Serialization error: {}", e)))
163 }
164}