//! Contract API methods. use serde::Serialize; use uuid::Uuid; use super::client::{ApiClient, ApiError}; use super::supervisor::JsonValue; // Request types #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct ReportRequest { pub message: String, #[serde(skip_serializing_if = "Option::is_none")] pub task_id: Option, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct CompletionActionRequest { pub lines_added: i32, pub lines_removed: i32, pub has_code_changes: bool, #[serde(skip_serializing_if = "Option::is_none")] pub task_id: Option, #[serde(skip_serializing_if = "Option::is_none")] pub files_modified: Option>, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct UpdateFileRequest { pub content: String, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct CreateFileRequest { pub name: String, pub content: String, } /// Request to update a contract. #[derive(Serialize, Default)] #[serde(rename_all = "camelCase")] pub struct UpdateContractRequest { #[serde(skip_serializing_if = "Option::is_none")] pub name: Option, #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, } /// Request to create a new contract. #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct CreateContractRequest { pub name: String, #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, #[serde(skip_serializing_if = "Option::is_none")] pub contract_type: Option, #[serde(skip_serializing_if = "Option::is_none")] pub initial_phase: Option, #[serde(skip_serializing_if = "Option::is_none")] pub autonomous_loop: Option, #[serde(skip_serializing_if = "Option::is_none")] pub phase_guard: Option, #[serde(skip_serializing_if = "Option::is_none")] pub local_only: Option, #[serde(skip_serializing_if = "Option::is_none")] pub auto_merge_local: Option, } impl ApiClient { /// List all contracts for the authenticated user. pub async fn list_contracts(&self) -> Result { self.get("/api/v1/contracts").await } /// Create a new contract. pub async fn create_contract(&self, req: CreateContractRequest) -> Result { self.post("/api/v1/contracts", &req).await } /// Get a contract with its tasks, files, and repositories. pub async fn get_contract(&self, contract_id: Uuid) -> Result { self.get(&format!("/api/v1/contracts/{}", contract_id)).await } /// Delete a contract. pub async fn delete_contract(&self, contract_id: Uuid) -> Result<(), ApiError> { self.delete(&format!("/api/v1/contracts/{}", contract_id)) .await } /// Update a contract. pub async fn update_contract( &self, contract_id: Uuid, name: Option, description: Option, ) -> Result { let req = UpdateContractRequest { name, description }; self.put(&format!("/api/v1/contracts/{}", contract_id), &req) .await } /// Get contract status. pub async fn contract_status(&self, contract_id: Uuid) -> Result { self.get(&format!("/api/v1/contracts/{}/daemon/status", contract_id)) .await } /// Get phase checklist. pub async fn contract_checklist(&self, contract_id: Uuid) -> Result { self.get(&format!("/api/v1/contracts/{}/daemon/checklist", contract_id)) .await } /// Get contract goals. pub async fn contract_goals(&self, contract_id: Uuid) -> Result { self.get(&format!("/api/v1/contracts/{}/daemon/goals", contract_id)) .await } /// List contract files. pub async fn contract_files(&self, contract_id: Uuid) -> Result { self.get(&format!("/api/v1/contracts/{}/daemon/files", contract_id)) .await } /// Get a specific file. pub async fn contract_file( &self, contract_id: Uuid, file_id: Uuid, ) -> Result { self.get(&format!( "/api/v1/contracts/{}/daemon/files/{}", contract_id, file_id )) .await } /// Report progress. pub async fn contract_report( &self, contract_id: Uuid, message: &str, task_id: Option, ) -> Result { let req = ReportRequest { message: message.to_string(), task_id, }; self.post(&format!("/api/v1/contracts/{}/daemon/report", contract_id), &req) .await } /// Get suggested action. pub async fn contract_suggest_action(&self, contract_id: Uuid) -> Result { self.post_empty(&format!( "/api/v1/contracts/{}/daemon/suggest-action", contract_id )) .await } /// Get completion action recommendation. pub async fn contract_completion_action( &self, contract_id: Uuid, task_id: Option, files_modified: Option>, lines_added: i32, lines_removed: i32, has_code_changes: bool, ) -> Result { let req = CompletionActionRequest { task_id, files_modified, lines_added, lines_removed, has_code_changes, }; self.post( &format!("/api/v1/contracts/{}/daemon/completion-action", contract_id), &req, ) .await } /// Update a file. pub async fn contract_update_file( &self, contract_id: Uuid, file_id: Uuid, content: &str, ) -> Result { let req = UpdateFileRequest { content: content.to_string(), }; self.put( &format!("/api/v1/contracts/{}/daemon/files/{}", contract_id, file_id), &req, ) .await } /// Create a new file. pub async fn contract_create_file( &self, contract_id: Uuid, name: &str, content: &str, ) -> Result { let req = CreateFileRequest { name: name.to_string(), content: content.to_string(), }; self.post(&format!("/api/v1/contracts/{}/daemon/files", contract_id), &req) .await } /// Get task output history. pub async fn get_task_output(&self, task_id: Uuid) -> Result { self.get(&format!("/api/v1/mesh/tasks/{}/output", task_id)) .await } /// Get repository suggestions for autocomplete. /// Returns recently used repositories sorted by usage frequency and recency. pub async fn get_repository_suggestions( &self, source_type: Option<&str>, limit: Option, ) -> Result { let mut params = Vec::new(); if let Some(st) = source_type { params.push(format!("source_type={}", st)); } if let Some(l) = limit { params.push(format!("limit={}", l)); } let query_string = if params.is_empty() { String::new() } else { format!("?{}", params.join("&")) }; self.get(&format!("/api/v1/settings/repository-history/suggestions{}", query_string)) .await } /// Add a remote repository to a contract. pub async fn add_remote_repository( &self, contract_id: Uuid, name: &str, repository_url: &str, is_primary: bool, ) -> Result { let req = AddRemoteRepositoryRequest { name: name.to_string(), repository_url: repository_url.to_string(), is_primary, }; self.post(&format!("/api/v1/contracts/{}/repositories/remote", contract_id), &req) .await } } #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct AddRemoteRepositoryRequest { name: String, repository_url: String, is_primary: bool, }