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