summaryrefslogblamecommitdiff
path: root/makima/src/daemon/api/contract.rs
blob: 7c76b406f3e0d5763fddaf753ea5687c885b8aa8 (plain) (tree)










































                                                     









                                                     














                                                     


                                                     

                                                     


                                                     

 
                




                                                                       









                                                                                                    

















                                                                                    



















































































































                                                                                                   





                                                                                       






















                                                                                             
























                                                                                          
 
//! 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<Uuid>,
}

#[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<Uuid>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub files_modified: Option<Vec<String>>,
}

#[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<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
}

/// 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<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub contract_type: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub initial_phase: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub autonomous_loop: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub phase_guard: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub local_only: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub auto_merge_local: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub red_team_enabled: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub red_team_prompt: Option<String>,
}

impl ApiClient {
    /// List all contracts for the authenticated user.
    pub async fn list_contracts(&self) -> Result<JsonValue, ApiError> {
        self.get("/api/v1/contracts").await
    }

    /// Create a new contract.
    pub async fn create_contract(&self, req: CreateContractRequest) -> Result<JsonValue, ApiError> {
        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<JsonValue, ApiError> {
        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<String>,
        description: Option<String>,
    ) -> Result<JsonValue, ApiError> {
        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<JsonValue, ApiError> {
        self.get(&format!("/api/v1/contracts/{}/daemon/status", contract_id))
            .await
    }

    /// Get phase checklist.
    pub async fn contract_checklist(&self, contract_id: Uuid) -> Result<JsonValue, ApiError> {
        self.get(&format!("/api/v1/contracts/{}/daemon/checklist", contract_id))
            .await
    }

    /// Get contract goals.
    pub async fn contract_goals(&self, contract_id: Uuid) -> Result<JsonValue, ApiError> {
        self.get(&format!("/api/v1/contracts/{}/daemon/goals", contract_id))
            .await
    }

    /// List contract files.
    pub async fn contract_files(&self, contract_id: Uuid) -> Result<JsonValue, ApiError> {
        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<JsonValue, ApiError> {
        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<Uuid>,
    ) -> Result<JsonValue, ApiError> {
        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<JsonValue, ApiError> {
        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<Uuid>,
        files_modified: Option<Vec<String>>,
        lines_added: i32,
        lines_removed: i32,
        has_code_changes: bool,
    ) -> Result<JsonValue, ApiError> {
        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<JsonValue, ApiError> {
        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<JsonValue, ApiError> {
        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<JsonValue, ApiError> {
        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<i32>,
    ) -> Result<JsonValue, ApiError> {
        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<JsonValue, ApiError> {
        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,
}