summaryrefslogtreecommitdiff
path: root/makima/src/llm
diff options
context:
space:
mode:
Diffstat (limited to 'makima/src/llm')
-rw-r--r--makima/src/llm/discuss_tools.rs210
-rw-r--r--makima/src/llm/mod.rs4
2 files changed, 214 insertions, 0 deletions
diff --git a/makima/src/llm/discuss_tools.rs b/makima/src/llm/discuss_tools.rs
new file mode 100644
index 0000000..7330db3
--- /dev/null
+++ b/makima/src/llm/discuss_tools.rs
@@ -0,0 +1,210 @@
+//! Tool definitions for contract discussion via LLM.
+//!
+//! These tools allow Makima to help users define and create contracts
+//! through natural conversation.
+
+use serde_json::json;
+
+use super::tools::Tool;
+
+/// Available tools for contract discussion
+pub static DISCUSS_TOOLS: once_cell::sync::Lazy<Vec<Tool>> = once_cell::sync::Lazy::new(|| {
+ vec![
+ Tool {
+ name: "create_contract".to_string(),
+ description: "Create a new contract based on the discussion. Only call this when the user has confirmed they're ready to create the contract.".to_string(),
+ parameters: json!({
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Name for the contract"
+ },
+ "description": {
+ "type": "string",
+ "description": "Detailed description of what the contract is for"
+ },
+ "contract_type": {
+ "type": "string",
+ "enum": ["simple", "specification", "execute"],
+ "description": "Type of contract workflow"
+ },
+ "repository_url": {
+ "type": "string",
+ "description": "Optional repository URL if discussed"
+ },
+ "local_only": {
+ "type": "boolean",
+ "description": "If true, tasks won't auto-push or create PRs"
+ }
+ },
+ "required": ["name", "description", "contract_type"]
+ }),
+ },
+ Tool {
+ name: "ask_clarification".to_string(),
+ description: "Ask the user a clarifying question with multiple choice options.".to_string(),
+ parameters: json!({
+ "type": "object",
+ "properties": {
+ "question": {
+ "type": "string",
+ "description": "The question to ask"
+ },
+ "options": {
+ "type": "array",
+ "items": { "type": "string" },
+ "description": "Multiple choice options"
+ },
+ "allow_custom": {
+ "type": "boolean",
+ "description": "Allow user to provide a custom answer"
+ }
+ },
+ "required": ["question", "options"]
+ }),
+ },
+ ]
+});
+
+/// Request for discussion tool operations that require async database access
+#[derive(Debug, Clone)]
+pub enum DiscussToolRequest {
+ /// Create a new contract
+ CreateContract {
+ name: String,
+ description: String,
+ contract_type: String,
+ repository_url: Option<String>,
+ local_only: bool,
+ },
+}
+
+/// Result from executing a discussion tool
+#[derive(Debug)]
+pub struct DiscussToolExecutionResult {
+ pub success: bool,
+ pub message: String,
+ pub data: Option<serde_json::Value>,
+ /// Request for async operations (handled by discuss handler)
+ pub request: Option<DiscussToolRequest>,
+ /// Questions to ask the user (pauses conversation)
+ pub pending_questions: Option<Vec<super::tools::UserQuestion>>,
+}
+
+/// Parse and validate a discussion tool call, returning a DiscussToolRequest for async handling
+pub fn parse_discuss_tool_call(call: &super::tools::ToolCall) -> DiscussToolExecutionResult {
+ match call.name.as_str() {
+ "create_contract" => parse_create_contract(call),
+ "ask_clarification" => parse_ask_clarification(call),
+ _ => DiscussToolExecutionResult {
+ success: false,
+ message: format!("Unknown discussion tool: {}", call.name),
+ data: None,
+ request: None,
+ pending_questions: None,
+ },
+ }
+}
+
+fn parse_create_contract(call: &super::tools::ToolCall) -> DiscussToolExecutionResult {
+ let name = call.arguments.get("name").and_then(|v| v.as_str());
+ let description = call.arguments.get("description").and_then(|v| v.as_str());
+ let contract_type = call.arguments.get("contract_type").and_then(|v| v.as_str());
+
+ let Some(name) = name else {
+ return error_result("Missing required parameter: name");
+ };
+ let Some(description) = description else {
+ return error_result("Missing required parameter: description");
+ };
+ let Some(contract_type) = contract_type else {
+ return error_result("Missing required parameter: contract_type");
+ };
+
+ let valid_types = ["simple", "specification", "execute"];
+ if !valid_types.contains(&contract_type) {
+ return error_result("Invalid contract_type. Must be one of: simple, specification, execute");
+ }
+
+ let repository_url = call
+ .arguments
+ .get("repository_url")
+ .and_then(|v| v.as_str())
+ .map(|s| s.to_string());
+
+ let local_only = call
+ .arguments
+ .get("local_only")
+ .and_then(|v| v.as_bool())
+ .unwrap_or(false);
+
+ DiscussToolExecutionResult {
+ success: true,
+ message: format!("Creating contract '{}'...", name),
+ data: None,
+ request: Some(DiscussToolRequest::CreateContract {
+ name: name.to_string(),
+ description: description.to_string(),
+ contract_type: contract_type.to_string(),
+ repository_url,
+ local_only,
+ }),
+ pending_questions: None,
+ }
+}
+
+fn parse_ask_clarification(call: &super::tools::ToolCall) -> DiscussToolExecutionResult {
+ let question = call.arguments.get("question").and_then(|v| v.as_str());
+ let options = call.arguments.get("options").and_then(|v| v.as_array());
+
+ let Some(question) = question else {
+ return error_result("Missing required parameter: question");
+ };
+ let Some(options) = options else {
+ return error_result("Missing required parameter: options");
+ };
+
+ let options: Vec<String> = options
+ .iter()
+ .filter_map(|o| o.as_str())
+ .map(|s| s.to_string())
+ .collect();
+
+ if options.is_empty() {
+ return error_result("Options array cannot be empty");
+ }
+
+ let allow_custom = call
+ .arguments
+ .get("allow_custom")
+ .and_then(|v| v.as_bool())
+ .unwrap_or(true);
+
+ // Create a UserQuestion for the ask_clarification tool
+ let user_question = super::tools::UserQuestion {
+ id: "clarification".to_string(),
+ question: question.to_string(),
+ options,
+ allow_multiple: false,
+ allow_custom,
+ };
+
+ DiscussToolExecutionResult {
+ success: true,
+ message: format!("Asking clarification: {}", question),
+ data: None,
+ request: None,
+ pending_questions: Some(vec![user_question]),
+ }
+}
+
+fn error_result(message: &str) -> DiscussToolExecutionResult {
+ DiscussToolExecutionResult {
+ success: false,
+ message: message.to_string(),
+ data: None,
+ request: None,
+ pending_questions: None,
+ }
+}
diff --git a/makima/src/llm/mod.rs b/makima/src/llm/mod.rs
index a3a3daf..4c84ced 100644
--- a/makima/src/llm/mod.rs
+++ b/makima/src/llm/mod.rs
@@ -2,6 +2,7 @@
pub mod claude;
pub mod contract_tools;
+pub mod discuss_tools;
pub mod groq;
pub mod markdown;
pub mod mesh_tools;
@@ -16,6 +17,9 @@ pub use contract_tools::{
parse_contract_tool_call, ChainedTaskDef, ContractToolExecutionResult, ContractToolRequest,
CONTRACT_TOOLS,
};
+pub use discuss_tools::{
+ parse_discuss_tool_call, DiscussToolExecutionResult, DiscussToolRequest, DISCUSS_TOOLS,
+};
pub use groq::GroqClient;
pub use mesh_tools::{parse_mesh_tool_call, MeshToolExecutionResult, MeshToolRequest, MESH_TOOLS};
pub use phase_guidance::{