syncable_cli/agent/tools/platform/
current_context.rs

1//! Current context tool for the agent
2//!
3//! Allows the agent to query the currently selected project context.
4
5use 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::PlatformSession;
12
13/// Arguments for the current context tool (none required)
14#[derive(Debug, Deserialize)]
15pub struct CurrentContextArgs {}
16
17/// Error type for current context operations
18#[derive(Debug, thiserror::Error)]
19#[error("Current context error: {0}")]
20pub struct CurrentContextError(String);
21
22/// Tool to get the currently selected project context
23///
24/// This tool reads the platform session from `~/.syncable/platform-session.json`
25/// and returns information about the selected project and organization.
26#[derive(Debug, Clone, Serialize, Deserialize, Default)]
27pub struct CurrentContextTool;
28
29impl CurrentContextTool {
30    /// Create a new CurrentContextTool
31    pub fn new() -> Self {
32        Self
33    }
34}
35
36impl Tool for CurrentContextTool {
37    const NAME: &'static str = "current_context";
38
39    type Error = CurrentContextError;
40    type Args = CurrentContextArgs;
41    type Output = String;
42
43    async fn definition(&self, _prompt: String) -> ToolDefinition {
44        ToolDefinition {
45            name: Self::NAME.to_string(),
46            description: r#"Get the currently selected project context.
47
48Returns information about the currently selected project and organization,
49or indicates if no project is selected.
50
51**Use Cases:**
52- Checking which project is currently active before operations
53- Verifying context after selection
54- Determining if context setup is needed
55
56**No Prerequisites:**
57- This tool can be called at any time
58- Returns helpful message if no project is selected"#
59                .to_string(),
60            parameters: json!({
61                "type": "object",
62                "properties": {},
63                "required": []
64            }),
65        }
66    }
67
68    async fn call(&self, _args: Self::Args) -> Result<Self::Output, Self::Error> {
69        // Load the platform session
70        let session = match PlatformSession::load() {
71            Ok(s) => s,
72            Err(e) => {
73                return Ok(format_error_for_llm(
74                    "current_context",
75                    ErrorCategory::InternalError,
76                    &format!("Failed to load platform session: {}", e),
77                    Some(vec![
78                        "The session file may be corrupted",
79                        "Try selecting a project with select_project",
80                    ]),
81                ));
82            }
83        };
84
85        // Check if a project is selected
86        if !session.is_project_selected() {
87            let result = json!({
88                "success": true,
89                "has_context": false,
90                "message": "No project currently selected",
91                "suggestion": "Use list_organizations and list_projects to find a project, then select_project to set context"
92            });
93
94            return serde_json::to_string_pretty(&result)
95                .map_err(|e| CurrentContextError(format!("Failed to serialize: {}", e)));
96        }
97
98        // Return the current context
99        let result = json!({
100            "success": true,
101            "has_context": true,
102            "context": {
103                "project_id": session.project_id,
104                "project_name": session.project_name,
105                "organization_id": session.org_id,
106                "organization_name": session.org_name,
107                "display": session.display_context(),
108                "last_updated": session.last_updated.map(|dt| dt.to_rfc3339())
109            }
110        });
111
112        serde_json::to_string_pretty(&result)
113            .map_err(|e| CurrentContextError(format!("Failed to serialize: {}", e)))
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    #[test]
122    fn test_tool_name() {
123        assert_eq!(CurrentContextTool::NAME, "current_context");
124    }
125
126    #[test]
127    fn test_tool_creation() {
128        let tool = CurrentContextTool::new();
129        assert!(format!("{:?}", tool).contains("CurrentContextTool"));
130    }
131}