syncable_cli/agent/tools/platform/
list_deployment_configs.rs1use rig::completion::ToolDefinition;
6use rig::tool::Tool;
7use serde::{Deserialize, Serialize};
8use serde_json::json;
9
10use crate::agent::tools::error::{ErrorCategory, format_error_for_llm};
11use crate::platform::api::{PlatformApiClient, PlatformApiError};
12
13#[derive(Debug, Deserialize)]
15pub struct ListDeploymentConfigsArgs {
16 pub project_id: String,
18}
19
20#[derive(Debug, thiserror::Error)]
22#[error("List deployment configs error: {0}")]
23pub struct ListDeploymentConfigsError(String);
24
25#[derive(Debug, Clone, Serialize, Deserialize, Default)]
30pub struct ListDeploymentConfigsTool;
31
32impl ListDeploymentConfigsTool {
33 pub fn new() -> Self {
35 Self
36 }
37}
38
39impl Tool for ListDeploymentConfigsTool {
40 const NAME: &'static str = "list_deployment_configs";
41
42 type Error = ListDeploymentConfigsError;
43 type Args = ListDeploymentConfigsArgs;
44 type Output = String;
45
46 async fn definition(&self, _prompt: String) -> ToolDefinition {
47 ToolDefinition {
48 name: Self::NAME.to_string(),
49 description: r#"List deployment configurations for a project.
50
51Returns all deployment configs associated with the project, including:
52- Service name and branch
53- Target type (kubernetes or cloud_runner)
54- Auto-deploy status
55- Port configuration
56
57**Prerequisites:**
58- User must be authenticated via `sync-ctl auth login`
59- A project must be selected (use select_project first)
60
61**Use Cases:**
62- View available deployment configurations before triggering a deployment
63- Check auto-deploy settings for services
64- Find the config_id needed to trigger a deployment"#
65 .to_string(),
66 parameters: json!({
67 "type": "object",
68 "properties": {
69 "project_id": {
70 "type": "string",
71 "description": "The UUID of the project to list deployment configs for"
72 }
73 },
74 "required": ["project_id"]
75 }),
76 }
77 }
78
79 async fn call(&self, args: Self::Args) -> Result<Self::Output, Self::Error> {
80 if args.project_id.trim().is_empty() {
82 return Ok(format_error_for_llm(
83 "list_deployment_configs",
84 ErrorCategory::ValidationFailed,
85 "project_id cannot be empty",
86 Some(vec![
87 "Use list_projects to find valid project IDs",
88 "Use select_project to set the current project context",
89 ]),
90 ));
91 }
92
93 let client = match PlatformApiClient::new() {
95 Ok(c) => c,
96 Err(e) => {
97 return Ok(format_api_error("list_deployment_configs", e));
98 }
99 };
100
101 match client.list_deployment_configs(&args.project_id).await {
103 Ok(configs) => {
104 if configs.is_empty() {
105 return Ok(json!({
106 "success": true,
107 "configs": [],
108 "count": 0,
109 "message": "No deployment configs found for this project. You may need to create a deployment configuration first."
110 })
111 .to_string());
112 }
113
114 let config_list: Vec<serde_json::Value> = configs
115 .iter()
116 .map(|config| {
117 json!({
118 "id": config.id,
119 "service_name": config.service_name,
120 "repository": config.repository_full_name,
121 "branch": config.branch,
122 "target_type": config.target_type,
123 "port": config.port,
124 "auto_deploy_enabled": config.auto_deploy_enabled,
125 "deployment_strategy": config.deployment_strategy,
126 "environment_id": config.environment_id,
127 "created_at": config.created_at.to_rfc3339()
128 })
129 })
130 .collect();
131
132 let result = json!({
133 "success": true,
134 "configs": config_list,
135 "count": configs.len(),
136 "message": format!("Found {} deployment configuration(s)", configs.len())
137 });
138
139 serde_json::to_string_pretty(&result)
140 .map_err(|e| ListDeploymentConfigsError(format!("Failed to serialize: {}", e)))
141 }
142 Err(e) => Ok(format_api_error("list_deployment_configs", e)),
143 }
144 }
145}
146
147fn format_api_error(tool_name: &str, error: PlatformApiError) -> String {
149 match error {
150 PlatformApiError::Unauthorized => format_error_for_llm(
151 tool_name,
152 ErrorCategory::PermissionDenied,
153 "Not authenticated - please run `sync-ctl auth login` first",
154 Some(vec![
155 "The user needs to authenticate with the Syncable platform",
156 "Run: sync-ctl auth login",
157 ]),
158 ),
159 PlatformApiError::NotFound(msg) => format_error_for_llm(
160 tool_name,
161 ErrorCategory::ResourceUnavailable,
162 &format!("Resource not found: {}", msg),
163 Some(vec![
164 "The project ID may be incorrect",
165 "Use list_projects to find valid project IDs",
166 ]),
167 ),
168 PlatformApiError::PermissionDenied(msg) => format_error_for_llm(
169 tool_name,
170 ErrorCategory::PermissionDenied,
171 &format!("Permission denied: {}", msg),
172 Some(vec![
173 "The user does not have access to this project",
174 "Contact the project admin for access",
175 ]),
176 ),
177 PlatformApiError::RateLimited => format_error_for_llm(
178 tool_name,
179 ErrorCategory::ResourceUnavailable,
180 "Rate limit exceeded - please try again later",
181 Some(vec!["Wait a moment before retrying"]),
182 ),
183 PlatformApiError::HttpError(e) => format_error_for_llm(
184 tool_name,
185 ErrorCategory::NetworkError,
186 &format!("Network error: {}", e),
187 Some(vec![
188 "Check network connectivity",
189 "The Syncable API may be temporarily unavailable",
190 ]),
191 ),
192 PlatformApiError::ParseError(msg) => format_error_for_llm(
193 tool_name,
194 ErrorCategory::InternalError,
195 &format!("Failed to parse API response: {}", msg),
196 Some(vec!["This may be a temporary API issue"]),
197 ),
198 PlatformApiError::ApiError { status, message } => format_error_for_llm(
199 tool_name,
200 ErrorCategory::ExternalCommandFailed,
201 &format!("API error ({}): {}", status, message),
202 Some(vec!["Check the error message for details"]),
203 ),
204 PlatformApiError::ServerError { status, message } => format_error_for_llm(
205 tool_name,
206 ErrorCategory::ExternalCommandFailed,
207 &format!("Server error ({}): {}", status, message),
208 Some(vec![
209 "The Syncable API is experiencing issues",
210 "Try again later",
211 ]),
212 ),
213 PlatformApiError::ConnectionFailed => format_error_for_llm(
214 tool_name,
215 ErrorCategory::NetworkError,
216 "Could not connect to Syncable API",
217 Some(vec![
218 "Check your internet connection",
219 "The Syncable API may be temporarily unavailable",
220 ]),
221 ),
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228
229 #[test]
230 fn test_tool_name() {
231 assert_eq!(ListDeploymentConfigsTool::NAME, "list_deployment_configs");
232 }
233
234 #[test]
235 fn test_tool_creation() {
236 let tool = ListDeploymentConfigsTool::new();
237 assert!(format!("{:?}", tool).contains("ListDeploymentConfigsTool"));
238 }
239}