diff options
Diffstat (limited to 'makima/src/llm/tools.rs')
| -rw-r--r-- | makima/src/llm/tools.rs | 206 |
1 files changed, 203 insertions, 3 deletions
diff --git a/makima/src/llm/tools.rs b/makima/src/llm/tools.rs index 77fc8c6..649633e 100644 --- a/makima/src/llm/tools.rs +++ b/makima/src/llm/tools.rs @@ -73,6 +73,51 @@ pub static AVAILABLE_TOOLS: once_cell::sync::Lazy<Vec<Tool>> = }), }, Tool { + name: "add_code".to_string(), + description: "Add a code block element to the file body".to_string(), + parameters: json!({ + "type": "object", + "properties": { + "content": { + "type": "string", + "description": "The code content" + }, + "language": { + "type": "string", + "description": "Optional programming language for syntax highlighting (e.g., 'javascript', 'python', 'rust')" + }, + "position": { + "type": "integer", + "description": "Optional position to insert at (0-indexed). If not specified, appends to end." + } + }, + "required": ["content"] + }), + }, + Tool { + name: "add_list".to_string(), + description: "Add a list element (ordered or unordered) to the file body".to_string(), + parameters: json!({ + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { "type": "string" }, + "description": "Array of list item strings" + }, + "ordered": { + "type": "boolean", + "description": "If true, creates a numbered list; if false (default), creates a bullet list" + }, + "position": { + "type": "integer", + "description": "Optional position to insert at (0-indexed). If not specified, appends to end." + } + }, + "required": ["items"] + }), + }, + Tool { name: "add_chart".to_string(), description: "Add a chart visualization to the file body. Supports line, bar, pie, and area charts.".to_string(), parameters: json!({ @@ -122,7 +167,7 @@ pub static AVAILABLE_TOOLS: once_cell::sync::Lazy<Vec<Tool>> = }, Tool { name: "update_element".to_string(), - description: "Update an existing element in the file body. IMPORTANT: You must provide ALL required fields. For heading: type, level (1-6), text. For paragraph: type, text. For chart: type, chartType (line/bar/pie/area), data (array of objects).".to_string(), + description: "Update an existing element in the file body. IMPORTANT: You must provide ALL required fields. For heading: type, level (1-6), text. For paragraph: type, text. For code: type, content, language (optional). For list: type, items (array of strings), ordered (boolean). For chart: type, chartType (line/bar/pie/area), data (array of objects).".to_string(), parameters: json!({ "type": "object", "properties": { @@ -132,7 +177,7 @@ pub static AVAILABLE_TOOLS: once_cell::sync::Lazy<Vec<Tool>> = }, "element_type": { "type": "string", - "enum": ["heading", "paragraph", "chart"], + "enum": ["heading", "paragraph", "code", "list", "chart"], "description": "Type of element" }, "text": { @@ -143,6 +188,23 @@ pub static AVAILABLE_TOOLS: once_cell::sync::Lazy<Vec<Tool>> = "type": "integer", "description": "Heading level 1-6 (required for heading)" }, + "content": { + "type": "string", + "description": "Code content (required for code)" + }, + "language": { + "type": "string", + "description": "Programming language for syntax highlighting (optional for code)" + }, + "items": { + "type": "array", + "items": { "type": "string" }, + "description": "List items (required for list)" + }, + "ordered": { + "type": "boolean", + "description": "If true, numbered list; if false, bullet list (for list)" + }, "chartType": { "type": "string", "enum": ["line", "bar", "pie", "area"], @@ -418,6 +480,8 @@ pub fn execute_tool_call( match call.name.as_str() { "add_heading" => execute_add_heading(call, current_body), "add_paragraph" => execute_add_paragraph(call, current_body), + "add_code" => execute_add_code(call, current_body), + "add_list" => execute_add_list(call, current_body), "add_chart" => execute_add_chart(call, current_body), "remove_element" => execute_remove_element(call, current_body), "update_element" => execute_update_element(call, current_body), @@ -605,6 +669,103 @@ fn execute_add_paragraph(call: &ToolCall, current_body: &[BodyElement]) -> ToolE } } +fn execute_add_code(call: &ToolCall, current_body: &[BodyElement]) -> ToolExecutionResult { + let language = call + .arguments + .get("language") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()); + let content = call + .arguments + .get("content") + .and_then(|v| v.as_str()) + .unwrap_or("") + .to_string(); + let position = call.arguments.get("position").and_then(|v| v.as_u64()); + + let element = BodyElement::Code { + language: language.clone(), + content: content.clone(), + }; + let mut new_body = current_body.to_vec(); + + if let Some(pos) = position { + let pos = pos as usize; + if pos <= new_body.len() { + new_body.insert(pos, element); + } else { + new_body.push(element); + } + } else { + new_body.push(element); + } + + let lang_str = language.as_deref().unwrap_or("plain"); + let preview: String = content.chars().take(50).collect(); + + ToolExecutionResult { + result: ToolResult { + success: true, + message: format!("Added code block ({}): {}", lang_str, preview), + }, + new_body: Some(new_body), + new_summary: None, + parsed_data: None, + version_request: None, + pending_questions: None, + } +} + +fn execute_add_list(call: &ToolCall, current_body: &[BodyElement]) -> ToolExecutionResult { + let ordered = call + .arguments + .get("ordered") + .and_then(|v| v.as_bool()) + .unwrap_or(false); + let items: Vec<String> = call + .arguments + .get("items") + .and_then(|v| v.as_array()) + .map(|arr| { + arr.iter() + .filter_map(|v| v.as_str().map(|s| s.to_string())) + .collect() + }) + .unwrap_or_default(); + let position = call.arguments.get("position").and_then(|v| v.as_u64()); + + let element = BodyElement::List { + ordered, + items: items.clone(), + }; + let mut new_body = current_body.to_vec(); + + if let Some(pos) = position { + let pos = pos as usize; + if pos <= new_body.len() { + new_body.insert(pos, element); + } else { + new_body.push(element); + } + } else { + new_body.push(element); + } + + let list_type = if ordered { "ordered" } else { "unordered" }; + + ToolExecutionResult { + result: ToolResult { + success: true, + message: format!("Added {} list with {} items", list_type, items.len()), + }, + new_body: Some(new_body), + new_summary: None, + parsed_data: None, + version_request: None, + pending_questions: None, + } +} + fn execute_add_chart(call: &ToolCall, current_body: &[BodyElement]) -> ToolExecutionResult { let chart_type_str = call .arguments @@ -778,6 +939,19 @@ fn execute_update_element(call: &ToolCall, current_body: &[BodyElement]) -> Tool let text = call.arguments.get("text").and_then(|v| v.as_str()).unwrap_or("").to_string(); BodyElement::Paragraph { text } } + "code" => { + let language = call.arguments.get("language").and_then(|v| v.as_str()).map(|s| s.to_string()); + let content = call.arguments.get("content").and_then(|v| v.as_str()).unwrap_or("").to_string(); + BodyElement::Code { language, content } + } + "list" => { + let ordered = call.arguments.get("ordered").and_then(|v| v.as_bool()).unwrap_or(false); + let items = call.arguments.get("items") + .and_then(|v| v.as_array()) + .map(|arr| arr.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect()) + .unwrap_or_default(); + BodyElement::List { ordered, items } + } "chart" => { let chart_type_str = call.arguments.get("chartType").and_then(|v| v.as_str()).unwrap_or("bar"); let chart_type = match chart_type_str { @@ -796,7 +970,7 @@ fn execute_update_element(call: &ToolCall, current_body: &[BodyElement]) -> Tool return ToolExecutionResult { result: ToolResult { success: false, - message: format!("Unknown element_type: {}. Must be heading, paragraph, or chart.", element_type), + message: format!("Unknown element_type: {}. Must be heading, paragraph, code, list, or chart.", element_type), }, new_body: None, new_summary: None, @@ -1149,6 +1323,18 @@ fn execute_view_body(current_body: &[BodyElement]) -> ToolExecutionResult { "type": "paragraph", "text": text }), + BodyElement::Code { language, content } => json!({ + "index": i, + "type": "code", + "language": language, + "content": content + }), + BodyElement::List { ordered, items } => json!({ + "index": i, + "type": "list", + "ordered": ordered, + "items": items + }), BodyElement::Chart { chart_type, title, data, config } => json!({ "index": i, "type": "chart", @@ -1226,6 +1412,18 @@ fn execute_read_element(call: &ToolCall, current_body: &[BodyElement]) -> ToolEx "type": "paragraph", "text": text }), + BodyElement::Code { language, content } => json!({ + "index": index, + "type": "code", + "language": language, + "content": content + }), + BodyElement::List { ordered, items } => json!({ + "index": index, + "type": "list", + "ordered": ordered, + "items": items + }), BodyElement::Chart { chart_type, title, data, config } => json!({ "index": index, "type": "chart", @@ -1246,6 +1444,8 @@ fn execute_read_element(call: &ToolCall, current_body: &[BodyElement]) -> ToolEx let type_str = match element { BodyElement::Heading { .. } => "heading", BodyElement::Paragraph { .. } => "paragraph", + BodyElement::Code { .. } => "code", + BodyElement::List { .. } => "list", BodyElement::Chart { .. } => "chart", BodyElement::Image { .. } => "image", }; |
