Auto deployments & columns

This commit is contained in:
Alex Emmet 2026-06-14 14:27:42 +02:00
commit fab5746c42

View file

@ -86,10 +86,7 @@ impl ToolDefinition {
} }
pub fn to_short_doc(&self) -> String { pub fn to_short_doc(&self) -> String {
format!( format!("- {}: {}\n", self.name, self.description)
"- {}: {}\n",
self.name, self.description
)
} }
/// Strip the project_id parameter from the tool's JSON schema. /// Strip the project_id parameter from the tool's JSON schema.
@ -141,12 +138,17 @@ impl ToolDefinition {
} }
/// Filter tools by execution context /// Filter tools by execution context
pub fn filter_by_context(tools: &[ToolDefinition], context: ExecutionContext) -> Vec<ToolDefinition> { pub fn filter_by_context(
tools: &[ToolDefinition],
context: ExecutionContext,
) -> Vec<ToolDefinition> {
match context { match context {
ExecutionContext::Both => tools.to_vec(), ExecutionContext::Both => tools.to_vec(),
context => tools context => tools
.iter() .iter()
.filter(|t| t.execution_context == context || t.execution_context == ExecutionContext::Both) .filter(|t| {
t.execution_context == context || t.execution_context == ExecutionContext::Both
})
.cloned() .cloned()
.collect(), .collect(),
} }
@ -154,11 +156,7 @@ impl ToolDefinition {
/// Filter tools to only include non-deprecated ones /// Filter tools to only include non-deprecated ones
pub fn filter_active(tools: &[ToolDefinition]) -> Vec<ToolDefinition> { pub fn filter_active(tools: &[ToolDefinition]) -> Vec<ToolDefinition> {
tools tools.iter().filter(|t| !t.deprecated).cloned().collect()
.iter()
.filter(|t| !t.deprecated)
.cloned()
.collect()
} }
/// Check compatibility with another tool definition /// Check compatibility with another tool definition
@ -1099,15 +1097,16 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
), ),
tool!( tool!(
"kanban_create_todo", "kanban_create_todo",
"Create a new todo item in a Kanban column", "Create a new todo item in a Kanban board",
"kanban", "kanban",
false, false, true, false, false, true,
serde_json::json!({ serde_json::json!({
"type": "object", "type": "object",
"properties": { "properties": {
"project_id": { "type": "string", "description": "Project ID" }, "project_id": { "type": "string", "description": "Project ID" },
"board_id": { "type": "string", "description": "Board ID (defaults to project's primary board)" }, "board_id": { "type": "string", "description": "Board ID (defaults to project's primary board)" },
"column_id": { "type": "string", "description": "Column ID to place the todo" }, "status": { "type": "string", "enum": ["Ready","InProgress","Done","Blocked","Delegated","Failed","PendingApproval"], "description": "Initial status of the todo" },
"title": { "type": "string", "description": "Title of the todo" }, "title": { "type": "string", "description": "Title of the todo" },
"description": { "type": "string", "description": "Optional description" }, "description": { "type": "string", "description": "Optional description" },
"priority": { "type": "number", "description": "Priority (1-1000)" }, "priority": { "type": "number", "description": "Priority (1-1000)" },
@ -1115,7 +1114,7 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
"agent_prompt": { "type": "string", "description": "Optional agent prompt if deploy_agent is true" }, "agent_prompt": { "type": "string", "description": "Optional agent prompt if deploy_agent is true" },
"agent_task_details": { "type": "string", "description": "Optional agent task details" } "agent_task_details": { "type": "string", "description": "Optional agent task details" }
}, },
"required": ["project_id", "column_id", "title"] "required": ["project_id", "title"]
}) })
), ),
tool!( tool!(
@ -1152,19 +1151,19 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
), ),
tool!( tool!(
"kanban_move_todo", "kanban_move_todo",
"Move a todo to a different column or reorder it", "Move a todo item to a different status",
"kanban", "kanban",
false, false, true, false, false, true,
serde_json::json!({ serde_json::json!({
"type": "object", "type": "object",
"properties": { "properties": {
"project_id": { "type": "string", "description": "Project ID" }, "project_id": { "type": "string", "description": "Project ID" },
"todo_id": { "type": "string", "description": "ID of the todo to move" }, "todo_id": { "type": "string", "description": "ID of the todo to move" },
"column_id": { "type": "string", "description": "Target column ID" }, "status": { "type": "string", "enum": ["Ready","InProgress","Done","Blocked","Delegated","Failed","PendingApproval"], "description": "Target status" },
"task_order": { "type": "number", "description": "New order position" } "task_order": { "type": "number", "description": "New order position" }
}, },
"required": ["project_id", "todo_id", "column_id"] "required": ["project_id", "todo_id"]
}) })
), ),
tool!( tool!(
"kanban_create_task", "kanban_create_task",
@ -2014,10 +2013,7 @@ Done with tools.
#[test] #[test]
fn test_execution_context_default() { fn test_execution_context_default() {
assert_eq!( assert_eq!(ExecutionContext::default(), ExecutionContext::Both);
ExecutionContext::default(),
ExecutionContext::Both
);
} }
#[test] #[test]
@ -2043,7 +2039,10 @@ Done with tools.
"```\nSome trailing text.", "```\nSome trailing text.",
]; ];
let results = parse_tool_call_stream(chunks.into_iter()); let results = parse_tool_call_stream(chunks.into_iter());
assert!(results.is_empty(), "tool-result: at stream start should be rejected"); assert!(
results.is_empty(),
"tool-result: at stream start should be rejected"
);
} }
#[test] #[test]
@ -2053,7 +2052,10 @@ Done with tools.
"```tool-result:write_file\n{\"call_id\": \"c2\"}\n```\n", "```tool-result:write_file\n{\"call_id\": \"c2\"}\n```\n",
]; ];
let results = parse_tool_call_stream(chunks.into_iter()); let results = parse_tool_call_stream(chunks.into_iter());
assert!(results.is_empty(), "multiple tool-result: blocks should all be rejected"); assert!(
results.is_empty(),
"multiple tool-result: blocks should all be rejected"
);
} }
#[test] #[test]
@ -2064,15 +2066,24 @@ Done with tools.
"```tool:search_files\n{\"call_id\": \"c3\"}\n```\n", "```tool:search_files\n{\"call_id\": \"c3\"}\n```\n",
]; ];
let results = parse_tool_call_stream(chunks.into_iter()); let results = parse_tool_call_stream(chunks.into_iter());
assert_eq!(results.len(), 2, "should only parse the tool: blocks, not tool-result:"); assert_eq!(
results.len(),
2,
"should only parse the tool: blocks, not tool-result:"
);
assert_eq!(results[0].call_id, "c1"); assert_eq!(results[0].call_id, "c1");
assert_eq!(results[1].call_id, "c3"); assert_eq!(results[1].call_id, "c3");
} }
#[test] #[test]
fn test_streaming_tool_result_only_chunk() { fn test_streaming_tool_result_only_chunk() {
let results = parse_tool_call_stream(vec!["```tool-result:read_file\n{\"call_id\": \"c1\"}\n```\n"].into_iter()); let results = parse_tool_call_stream(
assert!(results.is_empty(), "tool-result: as only chunk content should be rejected"); vec!["```tool-result:read_file\n{\"call_id\": \"c1\"}\n```\n"].into_iter(),
);
assert!(
results.is_empty(),
"tool-result: as only chunk content should be rejected"
);
} }
#[test] #[test]
@ -2083,12 +2094,20 @@ Done with tools.
"Some text after.", "Some text after.",
]; ];
let results = parse_tool_call_stream(chunks.into_iter()); let results = parse_tool_call_stream(chunks.into_iter());
assert!(results.is_empty(), "tool-result: in middle of text should be rejected"); assert!(
results.is_empty(),
"tool-result: in middle of text should be rejected"
);
} }
#[test] #[test]
fn test_streaming_finish_rejects_tool_result_no_closing_fence() { fn test_streaming_finish_rejects_tool_result_no_closing_fence() {
let results = parse_tool_call_stream(vec!["```tool-result:read_file\n{\"call_id\": \"c1\"}\n"].into_iter()); let results = parse_tool_call_stream(
assert!(results.is_empty(), "tool-result: without closing fence should be rejected by finish()"); vec!["```tool-result:read_file\n{\"call_id\": \"c1\"}\n"].into_iter(),
);
assert!(
results.is_empty(),
"tool-result: without closing fence should be rejected by finish()"
);
} }
} }