syncable_cli/agent/tools/platform/
open_provider_settings.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};
11
12#[derive(Debug, Deserialize)]
14pub struct OpenProviderSettingsArgs {
15 pub project_id: String,
17}
18
19#[derive(Debug, thiserror::Error)]
21#[error("Open provider settings error: {0}")]
22pub struct OpenProviderSettingsError(String);
23
24#[derive(Debug, Clone, Serialize, Deserialize, Default)]
33pub struct OpenProviderSettingsTool;
34
35impl OpenProviderSettingsTool {
36 pub fn new() -> Self {
38 Self
39 }
40}
41
42impl Tool for OpenProviderSettingsTool {
43 const NAME: &'static str = "open_provider_settings";
44
45 type Error = OpenProviderSettingsError;
46 type Args = OpenProviderSettingsArgs;
47 type Output = String;
48
49 async fn definition(&self, _prompt: String) -> ToolDefinition {
50 ToolDefinition {
51 name: Self::NAME.to_string(),
52 description: r#"Open the cloud providers settings page in the user's browser.
53
54This opens the Syncable platform's settings page where users can connect their
55cloud provider accounts (GCP, AWS, Azure, Hetzner).
56
57**Important:**
58- The actual credential connection happens in the browser, NOT through the CLI
59- After calling this tool, ask the user to confirm when they've completed the setup
60- Use check_provider_connection to verify the connection was successful
61
62**Workflow:**
631. Call open_provider_settings with the project_id
642. Ask user: "Please connect your [provider] account in the browser. Let me know when done."
653. Call check_provider_connection to verify the connection
66
67**Prerequisites:**
68- User must be authenticated via `sync-ctl auth login`
69- User must have a valid project_id (from select_project or list_projects)"#
70 .to_string(),
71 parameters: json!({
72 "type": "object",
73 "properties": {
74 "project_id": {
75 "type": "string",
76 "description": "The UUID of the project to configure cloud providers for"
77 }
78 },
79 "required": ["project_id"]
80 }),
81 }
82 }
83
84 async fn call(&self, args: Self::Args) -> Result<Self::Output, Self::Error> {
85 if args.project_id.trim().is_empty() {
87 return Ok(format_error_for_llm(
88 "open_provider_settings",
89 ErrorCategory::ValidationFailed,
90 "project_id cannot be empty",
91 Some(vec![
92 "Use list_projects to find valid project IDs",
93 "Use select_project to set the current project context",
94 ]),
95 ));
96 }
97
98 let url = format!(
100 "https://syncable.dev/projects/{}/settings?tab=cloud-providers",
101 args.project_id
102 );
103
104 match open::that(&url) {
106 Ok(()) => {
107 let result = json!({
108 "success": true,
109 "message": "Opened cloud providers settings in your browser",
110 "url": url,
111 "next_steps": [
112 "Connect your cloud provider account in the browser",
113 "Once done, tell me which provider you connected",
114 "I'll verify the connection with check_provider_connection"
115 ]
116 });
117
118 serde_json::to_string_pretty(&result)
119 .map_err(|e| OpenProviderSettingsError(format!("Failed to serialize: {}", e)))
120 }
121 Err(e) => Ok(format_error_for_llm(
122 "open_provider_settings",
123 ErrorCategory::ExternalCommandFailed,
124 &format!("Failed to open browser: {}", e),
125 Some(vec![
126 &format!("You can manually open: {}", url),
127 "Check if a default browser is configured",
128 ]),
129 )),
130 }
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn test_tool_name() {
140 assert_eq!(OpenProviderSettingsTool::NAME, "open_provider_settings");
141 }
142
143 #[test]
144 fn test_tool_creation() {
145 let tool = OpenProviderSettingsTool::new();
146 assert!(format!("{:?}", tool).contains("OpenProviderSettingsTool"));
147 }
148
149 #[test]
150 fn test_settings_url_format() {
151 let project_id = "proj-12345-uuid";
152 let expected_url = format!(
153 "https://syncable.dev/projects/{}/settings?tab=cloud-providers",
154 project_id
155 );
156 assert!(expected_url.contains(project_id));
157 assert!(expected_url.contains("cloud-providers"));
158 }
159}