summaryrefslogtreecommitdiff
path: root/makima/src/llm/tools.rs
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-01-11 05:52:14 +0000
committersoryu <soryu@soryu.co>2026-01-15 00:21:16 +0000
commit87044a747b47bd83249d61a45842c7f7b2eae56d (patch)
treeef2000ce79ffcc2723ef841acef5aa1deb1d5378 /makima/src/llm/tools.rs
parent077820c4167c168072d217a1b01df840463a12a8 (diff)
downloadsoryu-87044a747b47bd83249d61a45842c7f7b2eae56d.tar.gz
soryu-87044a747b47bd83249d61a45842c7f7b2eae56d.zip
Contract system
Diffstat (limited to 'makima/src/llm/tools.rs')
-rw-r--r--makima/src/llm/tools.rs170
1 files changed, 170 insertions, 0 deletions
diff --git a/makima/src/llm/tools.rs b/makima/src/llm/tools.rs
index 649633e..ae1dc5a 100644
--- a/makima/src/llm/tools.rs
+++ b/makima/src/llm/tools.rs
@@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
use serde_json::json;
use crate::db::models::{BodyElement, ChartType, TranscriptEntry};
+use crate::llm::templates;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Tool {
@@ -411,6 +412,36 @@ pub static AVAILABLE_TOOLS: once_cell::sync::Lazy<Vec<Tool>> =
"required": ["target_version"]
}),
},
+ // Template tools
+ Tool {
+ name: "suggest_templates".to_string(),
+ description: "Get suggested file templates based on a contract phase. Returns templates with predefined structures appropriate for research, specify, plan, execute, or review phases. Use this to help users start documents with proper structure.".to_string(),
+ parameters: json!({
+ "type": "object",
+ "properties": {
+ "phase": {
+ "type": "string",
+ "enum": ["research", "specify", "plan", "execute", "review"],
+ "description": "The contract phase to get templates for. If not provided, returns all templates."
+ }
+ },
+ "required": []
+ }),
+ },
+ Tool {
+ name: "apply_template".to_string(),
+ description: "Apply a template to the current file, replacing the body with the template structure. The template provides a starting structure that should be customized for the user's needs.".to_string(),
+ parameters: json!({
+ "type": "object",
+ "properties": {
+ "template_id": {
+ "type": "string",
+ "description": "The template ID to apply (e.g., 'research-notes', 'requirements', 'architecture')"
+ }
+ },
+ "required": ["template_id"]
+ }),
+ },
]
});
@@ -500,6 +531,9 @@ pub fn execute_tool_call(
"list_versions" => execute_list_versions(),
"read_version" => execute_read_version(call),
"restore_version" => execute_restore_version(call),
+ // Template tools
+ "suggest_templates" => execute_suggest_templates(call),
+ "apply_template" => execute_apply_template(call),
_ => ToolExecutionResult {
result: ToolResult {
success: false,
@@ -1350,6 +1384,11 @@ fn execute_view_body(current_body: &[BodyElement]) -> ToolExecutionResult {
"alt": alt,
"caption": caption
}),
+ BodyElement::Markdown { content } => json!({
+ "index": i,
+ "type": "markdown",
+ "content": content
+ }),
}
})
.collect();
@@ -1439,6 +1478,11 @@ fn execute_read_element(call: &ToolCall, current_body: &[BodyElement]) -> ToolEx
"alt": alt,
"caption": caption
}),
+ BodyElement::Markdown { content } => json!({
+ "index": index,
+ "type": "markdown",
+ "content": content
+ }),
};
let type_str = match element {
@@ -1448,6 +1492,7 @@ fn execute_read_element(call: &ToolCall, current_body: &[BodyElement]) -> ToolEx
BodyElement::List { .. } => "list",
BodyElement::Chart { .. } => "chart",
BodyElement::Image { .. } => "image",
+ BodyElement::Markdown { .. } => "markdown",
};
ToolExecutionResult {
@@ -1603,6 +1648,131 @@ fn execute_restore_version(call: &ToolCall) -> ToolExecutionResult {
}
}
+// =============================================================================
+// Template Tool Execution Functions
+// =============================================================================
+
+fn execute_suggest_templates(call: &ToolCall) -> ToolExecutionResult {
+ let phase = call.arguments.get("phase").and_then(|v| v.as_str());
+
+ let template_list = match phase {
+ Some(p) => templates::templates_for_phase(p),
+ None => templates::all_templates(),
+ };
+
+ if template_list.is_empty() {
+ return ToolExecutionResult {
+ result: ToolResult {
+ success: true,
+ message: format!(
+ "No templates available for phase: {}",
+ phase.unwrap_or("(none)")
+ ),
+ },
+ new_body: None,
+ new_summary: None,
+ parsed_data: Some(json!([])),
+ version_request: None,
+ pending_questions: None,
+ };
+ }
+
+ // Convert templates to JSON (without the full body for display)
+ let templates_json: Vec<serde_json::Value> = template_list
+ .iter()
+ .map(|t| {
+ json!({
+ "id": t.id,
+ "name": t.name,
+ "phase": t.phase,
+ "description": t.description,
+ "elementCount": t.suggested_body.len()
+ })
+ })
+ .collect();
+
+ let phase_msg = phase
+ .map(|p| format!(" for '{}' phase", p))
+ .unwrap_or_default();
+
+ ToolExecutionResult {
+ result: ToolResult {
+ success: true,
+ message: format!(
+ "Found {} template(s){}. Use apply_template with a template_id to apply one.",
+ templates_json.len(),
+ phase_msg
+ ),
+ },
+ new_body: None,
+ new_summary: None,
+ parsed_data: Some(json!(templates_json)),
+ version_request: None,
+ pending_questions: None,
+ }
+}
+
+fn execute_apply_template(call: &ToolCall) -> ToolExecutionResult {
+ let template_id = call
+ .arguments
+ .get("template_id")
+ .and_then(|v| v.as_str());
+
+ let Some(template_id) = template_id else {
+ return ToolExecutionResult {
+ result: ToolResult {
+ success: false,
+ message: "Missing template_id parameter".to_string(),
+ },
+ new_body: None,
+ new_summary: None,
+ parsed_data: None,
+ version_request: None,
+ pending_questions: None,
+ };
+ };
+
+ // Find the template
+ let all = templates::all_templates();
+ let template = all.iter().find(|t| t.id == template_id);
+
+ let Some(template) = template else {
+ let available: Vec<String> = all.iter().map(|t| t.id.clone()).collect();
+ return ToolExecutionResult {
+ result: ToolResult {
+ success: false,
+ message: format!(
+ "Template '{}' not found. Available: {}",
+ template_id,
+ available.join(", ")
+ ),
+ },
+ new_body: None,
+ new_summary: None,
+ parsed_data: None,
+ version_request: None,
+ pending_questions: None,
+ };
+ };
+
+ ToolExecutionResult {
+ result: ToolResult {
+ success: true,
+ message: format!(
+ "Applied template '{}' ({}) with {} elements. You can now customize the content.",
+ template.name,
+ template.phase,
+ template.suggested_body.len()
+ ),
+ },
+ new_body: Some(template.suggested_body.clone()),
+ new_summary: None,
+ parsed_data: None,
+ version_request: None,
+ pending_questions: None,
+ }
+}
+
/// Convert serde_json::Value to jaq_interpret::Val
fn json_to_jaq(value: &serde_json::Value) -> jaq_interpret::Val {
match value {