Skip to main content

matrixcode_core/tools/workflow/
create.rs

1//! Workflow Create/Edit Tool
2//!
3//! 帮助用户创建、编辑、验证 workflow YAML 文件的工具
4//! 
5//! 核心功能:
6//! - 创建:从零开始创建 workflow
7//! - 编辑:修改现有 workflow 的节点、边、参数
8//! - 模板:提供预定义模板
9//! - 验证:检查 workflow 结构合法性
10
11use async_trait::async_trait;
12use serde_json::{Value, json};
13use anyhow::{Result, bail};
14
15use crate::tools::{Tool, ToolDefinition};
16use crate::workflow::{NodeDef, EdgeDef, InputDef, OutputDef, NodeType, parser::{parse_workflow, parse_workflow_from_file, to_yaml}};
17use std::fs;
18use std::path::PathBuf;
19
20/// 获取 workflow 保存目录
21fn get_workflow_dir(location: &str) -> Result<PathBuf> {
22    match location {
23        "project" => {
24            let cwd = std::env::current_dir()
25                .map_err(|e| anyhow::anyhow!("Failed to get current directory: {}", e))?;
26            Ok(cwd.join(".matrix").join("workflows"))
27        }
28        "user" => {
29            dirs::home_dir()
30                .ok_or_else(|| anyhow::anyhow!("Failed to get home directory"))
31                .map(|p| p.join(".matrix").join("workflows"))
32        }
33        _ => bail!("Invalid location: {}. Use 'project' or 'user'", location),
34    }
35}
36
37/// Workflow 创建工具
38pub struct WorkflowCreateTool;
39
40#[async_trait]
41impl Tool for WorkflowCreateTool {
42    fn definition(&self) -> ToolDefinition {
43        ToolDefinition {
44            name: "workflow_create".to_string(),
45            description: "创建和编辑 workflow YAML 文件,支持迭代修改。
46
47【核心功能】
48此工具是 workflow 制作的核心工具,支持完整的创建-编辑-验证循环。
49
50【适用场景】
51- 创建新的 workflow
52- 修改现有 workflow(增删改节点、调整连接、修改参数)
53- 获取预定义模板作为起点
54- 验证 workflow 结构合法性
55- **多次迭代调整**:用户可能反复修改直到满意
56
57【功能模式】
58- **create**: 创建新 workflow(首次创建)
59- **edit**: 编辑现有 workflow(精确修改节点、边、参数)
60- **template**: 获取模板(快速开始)
61- **validate**: 验证结构(检查错误)
62- **info**: 查看 workflow 详情(查看节点、边、参数)
63
64【迭代修改流程】
65典型使用流程:
661. template → 获取模板作为起点
672. create → 创建初始版本
683. edit → 多次调整(修改节点名称、添加节点、调整连接)
694. validate → 验证最终版本
70
71【edit 模式操作类型】
72- add_node: 添加节点
73- remove_node: 删除节点
74- update_node: 更新节点属性(name, task, params, on_failure)
75- add_edge: 添加连接
76- remove_edge: 删除连接
77- add_input: 添加输入参数
78- remove_input: 删除输入参数
79- update_input: 更新输入参数
80- add_output: 添加输出参数
81- remove_output: 删除输出参数
82- update_output: 更新输出参数
83- update_metadata: 更新元数据(name, description, version)
84
85【参数说明】
86- mode: 操作模式 (create/edit/template/validate/info)
87- workflow_id: 目标 workflow ID (edit/info 模式必需)
88- workflow: workflow 定义 (create 模式必需)
89- yaml_content: YAML 内容字符串 (可选,用于直接提供 YAML)
90- edit_operation: 编辑操作类型 (edit 模式必需)
91- edit_target: 编辑目标 (节点ID、边ID等)
92- edit_value: 新值 (更新操作必需)
93- location: 保存位置 (project/user,默认 project)
94- template_type: 模板类型 (template 模式使用)
95
96【使用示例】
97创建 workflow:
98{
99  \"mode\": \"create\",
100  \"workflow\": {\"id\": \"my-workflow\", \"name\": \"My Workflow\", ...}
101}
102
103添加节点:
104{
105  \"mode\": \"edit\",
106  \"workflow_id\": \"my-workflow\",
107  \"edit_operation\": \"add_node\",
108  \"edit_value\": {\"id\": \"new_task\", \"type\": \"task\", \"name\": \"New Task\", \"task\": \"process\"}
109}
110
111修改节点名称:
112{
113  \"mode\": \"edit\",
114  \"workflow_id\": \"my-workflow\",
115  \"edit_operation\": \"update_node\",
116  \"edit_target\": \"task1\",
117  \"edit_value\": {\"name\": \"Updated Task Name\"}
118}
119
120添加连接:
121{
122  \"mode\": \"edit\",
123  \"workflow_id\": \"my-workflow\",
124  \"edit_operation\": \"add_edge\",
125  \"edit_value\": {\"from\": \"task1\", \"to\": \"new_task\"}
126}
127
128查看详情:
129{
130  \"mode\": \"info\",
131  \"workflow_id\": \"my-workflow\"
132}".to_string(),
133            parameters: json!({
134                "type": "object",
135                "properties": {
136                    "mode": {
137                        "type": "string",
138                        "enum": ["create", "edit", "template", "validate", "info"],
139                        "description": "操作模式",
140                        "default": "create"
141                    },
142                    "workflow_id": {
143                        "type": "string",
144                        "description": "目标 workflow ID (edit/info 模式必需)"
145                    },
146                    "workflow": {
147                        "type": "object",
148                        "description": "完整 workflow 定义 (create 模式)"
149                    },
150                    "yaml_content": {
151                        "type": "string",
152                        "description": "YAML 内容字符串 (可选)"
153                    },
154                    "edit_operation": {
155                        "type": "string",
156                        "enum": [
157                            "add_node", "remove_node", "update_node",
158                            "add_edge", "remove_edge",
159                            "add_input", "remove_input", "update_input",
160                            "add_output", "remove_output", "update_output",
161                            "update_metadata"
162                        ],
163                        "description": "编辑操作类型 (edit 模式)"
164                    },
165                    "edit_target": {
166                        "type": "string",
167                        "description": "编辑目标:节点ID、边ID、输入参数名等"
168                    },
169                    "edit_value": {
170                        "type": "object",
171                        "description": "新值:节点定义、边定义、参数定义等"
172                    },
173                    "location": {
174                        "type": "string",
175                        "enum": ["project", "user"],
176                        "description": "保存位置",
177                        "default": "project"
178                    },
179                    "template_type": {
180                        "type": "string",
181                        "enum": ["simple", "parallel", "condition", "research", "batch"],
182                        "description": "模板类型 (template 模式)"
183                    },
184                    "overwrite": {
185                        "type": "boolean",
186                        "description": "是否覆盖已存在文件",
187                        "default": false
188                    }
189                },
190                "required": ["mode"]
191            }),
192            is_priority: false,
193        }
194    }
195
196    async fn execute(&self, params: Value) -> Result<String> {
197        let mode = params["mode"].as_str().unwrap_or("create");
198
199        match mode {
200            "create" => self.handle_create(params).await,
201            "edit" => self.handle_edit(params).await,
202            "template" => self.handle_template(params).await,
203            "validate" => self.handle_validate(params).await,
204            "info" => self.handle_info(params).await,
205            _ => bail!("Invalid mode: {}. Supported modes: create, edit, template, validate, info", mode),
206        }
207    }
208}
209
210impl WorkflowCreateTool {
211    /// 处理创建 workflow
212    async fn handle_create(&self, params: Value) -> Result<String> {
213        // 获取 workflow 定义
214        let workflow_def = if let Some(yaml_str) = params["yaml_content"].as_str() {
215            // 从 YAML 解析
216            parse_workflow(yaml_str)
217                .map_err(|e| anyhow::anyhow!("Failed to parse YAML: {}", e))?
218        } else if let Some(workflow_json) = params.get("workflow") {
219            // 从 JSON 解析
220            serde_json::from_value(workflow_json.clone())
221                .map_err(|e| anyhow::anyhow!("Failed to parse workflow JSON: {}", e))?
222        } else {
223            bail!("Missing workflow definition. Provide either 'workflow' (JSON) or 'yaml_content' (YAML string)");
224        };
225
226        // 验证 workflow
227        workflow_def.validate()
228            .map_err(|e| anyhow::anyhow!("Workflow validation failed: {}", e))?;
229
230        // 获取保存位置
231        let location = params["location"].as_str().unwrap_or("project");
232        let overwrite = params["overwrite"].as_bool().unwrap_or(false);
233
234        let save_dir = get_workflow_dir(location)?;
235
236        // 创建目录(如果不存在)
237        fs::create_dir_all(&save_dir)
238            .map_err(|e| anyhow::anyhow!("Failed to create directory {:?}: {}", save_dir, e))?;
239
240        // 检查文件是否存在
241        let file_path = save_dir.join(format!("{}.yaml", workflow_def.id));
242        if file_path.exists() && !overwrite {
243            bail!(
244                "Workflow file already exists: {:?}\nSet overwrite=true to replace it.",
245                file_path
246            );
247        }
248
249        // 转换为 YAML
250        let yaml_content = to_yaml(&workflow_def)
251            .map_err(|e| anyhow::anyhow!("Failed to convert to YAML: {}", e))?;
252
253        // 保存文件
254        fs::write(&file_path, &yaml_content)
255            .map_err(|e| anyhow::anyhow!("Failed to write file {:?}: {}", file_path, e))?;
256
257        Ok(format!(
258            "✓ Workflow created successfully!\n\n📄 File: {:?}\n📁 ID: {}\n📝 Name: {}\n\n```yaml\n{}\n```\n\nUse 'workflow_run' to execute this workflow.",
259            file_path,
260            workflow_def.id,
261            workflow_def.name,
262            yaml_content
263        ))
264    }
265
266    /// 处理模板请求
267    async fn handle_template(&self, params: Value) -> Result<String> {
268        let template_type = params["template_type"].as_str().unwrap_or("simple");
269
270        let template = match template_type {
271            "simple" => self.get_simple_template(),
272            "parallel" => self.get_parallel_template(),
273            "condition" => self.get_condition_template(),
274            "research" => self.get_research_template(),
275            "batch" => self.get_batch_template(),
276            _ => bail!("Unknown template type: {}. Available: simple, parallel, condition, research, batch", template_type),
277        };
278
279        Ok(format!(
280            "📋 Workflow Template: {}\n\n```yaml\n{}\n```\n\n💡 Tips:\n- Copy this template and modify as needed\n- Use 'workflow_create' with mode='create' to save it\n- Each workflow must have a start node and an end node",
281            template_type,
282            template
283        ))
284    }
285
286    /// 验证 workflow 结构
287    async fn handle_validate(&self, params: Value) -> Result<String> {
288        let workflow_def = if let Some(yaml_str) = params["yaml_content"].as_str() {
289            parse_workflow(yaml_str)
290                .map_err(|e| anyhow::anyhow!("YAML parsing failed: {}", e))?
291        } else if let Some(workflow_json) = params.get("workflow") {
292            serde_json::from_value(workflow_json.clone())
293                .map_err(|e| anyhow::anyhow!("JSON parsing failed: {}", e))?
294        } else {
295            bail!("Missing workflow definition. Provide either 'workflow' (JSON) or 'yaml_content' (YAML string)");
296        };
297
298        // 执行验证
299        match workflow_def.validate() {
300            Ok(()) => Ok(format!(
301                "✓ Workflow validation passed!\n\n📁 ID: {}\n📝 Name: {}\n📊 Nodes: {}\n🔗 Edges: {}\n📥 Inputs: {}\n📤 Outputs: {}",
302                workflow_def.id,
303                workflow_def.name,
304                workflow_def.nodes.len(),
305                workflow_def.edges.len(),
306                workflow_def.inputs.len(),
307                workflow_def.outputs.len()
308            )),
309            Err(e) => bail!("Workflow validation failed: {}", e),
310        }
311    }
312
313    /// 处理编辑 workflow
314    async fn handle_edit(&self, params: Value) -> Result<String> {
315        // 获取 workflow ID
316        let workflow_id = params["workflow_id"]
317            .as_str()
318            .ok_or_else(|| anyhow::anyhow!("Missing 'workflow_id' parameter"))?;
319
320        // 获取保存位置
321        let location = params["location"].as_str().unwrap_or("project");
322        let save_dir = get_workflow_dir(location)?;
323        let file_path = save_dir.join(format!("{}.yaml", workflow_id));
324
325        // 直接从文件加载 workflow
326        if !file_path.exists() {
327            bail!("Workflow '{}' not found at {:?}", workflow_id, file_path);
328        }
329
330        let mut workflow = parse_workflow_from_file(&file_path)
331            .map_err(|e| anyhow::anyhow!("Failed to load workflow: {}", e))?;
332
333        // 获取编辑操作类型
334        let edit_operation = params["edit_operation"]
335            .as_str()
336            .ok_or_else(|| anyhow::anyhow!("Missing 'edit_operation' parameter"))?;
337
338        let edit_target = params["edit_target"].as_str();
339        let edit_value = params.get("edit_value").cloned();
340
341        // 执行编辑操作
342        match edit_operation {
343            // 节点操作
344            "add_node" => {
345                let node_json = edit_value.ok_or_else(|| anyhow::anyhow!("Missing 'edit_value' for add_node"))?;
346                let new_node: NodeDef = serde_json::from_value(node_json)
347                    .map_err(|e| anyhow::anyhow!("Invalid node definition: {}", e))?;
348                
349                // 检查节点ID是否已存在
350                if workflow.nodes.iter().any(|n| n.id == new_node.id) {
351                    bail!("Node '{}' already exists", new_node.id);
352                }
353                
354                workflow.nodes.push(new_node);
355            }
356
357            "remove_node" => {
358                let node_id = edit_target.ok_or_else(|| anyhow::anyhow!("Missing 'edit_target' for remove_node"))?;
359                let idx = workflow.nodes.iter().position(|n| n.id == node_id)
360                    .ok_or_else(|| anyhow::anyhow!("Node '{}' not found", node_id))?;
361                
362                // 不能删除 start/end 节点
363                let node = &workflow.nodes[idx];
364                if node.node_type == NodeType::Start || node.node_type == NodeType::End {
365                    bail!("Cannot remove start/end nodes");
366                }
367                
368                // 删除相关边
369                workflow.edges.retain(|e| e.from != node_id && e.to != node_id);
370                workflow.nodes.remove(idx);
371            }
372
373            "update_node" => {
374                let node_id = edit_target.ok_or_else(|| anyhow::anyhow!("Missing 'edit_target' for update_node"))?;
375                let node = workflow.nodes.iter_mut().find(|n| n.id == node_id)
376                    .ok_or_else(|| anyhow::anyhow!("Node '{}' not found", node_id))?;
377                
378                let updates = edit_value.ok_or_else(|| anyhow::anyhow!("Missing 'edit_value' for update_node"))?;
379                
380                // 更新节点属性
381                if let Some(name) = updates.get("name").and_then(|v| v.as_str()) {
382                    node.name = name.to_string();
383                }
384                if let Some(task) = updates.get("task").and_then(|v| v.as_str()) {
385                    node.task = Some(task.to_string());
386                }
387                if let Some(params_val) = updates.get("params") {
388                    node.params = serde_json::from_value(params_val.clone())
389                        .map_err(|e| anyhow::anyhow!("Invalid params: {}", e))?;
390                }
391                if let Some(timeout) = updates.get("timeout_ms").and_then(|v| v.as_u64()) {
392                    node.timeout_ms = Some(timeout);
393                }
394            }
395
396            // 边操作
397            "add_edge" => {
398                let edge_json = edit_value.ok_or_else(|| anyhow::anyhow!("Missing 'edit_value' for add_edge"))?;
399                let new_edge: EdgeDef = serde_json::from_value(edge_json)
400                    .map_err(|e| anyhow::anyhow!("Invalid edge definition: {}", e))?;
401                
402                // 检查源和目标节点是否存在
403                if !workflow.nodes.iter().any(|n| n.id == new_edge.from) {
404                    bail!("Source node '{}' not found", new_edge.from);
405                }
406                if !workflow.nodes.iter().any(|n| n.id == new_edge.to) {
407                    bail!("Target node '{}' not found", new_edge.to);
408                }
409                
410                workflow.edges.push(new_edge);
411            }
412
413            "remove_edge" => {
414                let from = edit_target.ok_or_else(|| anyhow::anyhow!("Missing 'edit_target' (from node) for remove_edge"))?;
415                let to = params["to"].as_str()
416                    .ok_or_else(|| anyhow::anyhow!("Missing 'to' parameter for remove_edge"))?;
417                
418                workflow.edges.retain(|e| e.from != from || e.to != to);
419            }
420
421            // 输入参数操作
422            "add_input" => {
423                let input_json = edit_value.ok_or_else(|| anyhow::anyhow!("Missing 'edit_value' for add_input"))?;
424                let new_input: InputDef = serde_json::from_value(input_json)
425                    .map_err(|e| anyhow::anyhow!("Invalid input definition: {}", e))?;
426                
427                workflow.inputs.push(new_input);
428            }
429
430            "remove_input" => {
431                let input_name = edit_target.ok_or_else(|| anyhow::anyhow!("Missing 'edit_target' for remove_input"))?;
432                workflow.inputs.retain(|i| i.name != input_name);
433            }
434
435            "update_input" => {
436                let input_name = edit_target.ok_or_else(|| anyhow::anyhow!("Missing 'edit_target' for update_input"))?;
437                let input = workflow.inputs.iter_mut().find(|i| i.name == input_name)
438                    .ok_or_else(|| anyhow::anyhow!("Input '{}' not found", input_name))?;
439                
440                let updates = edit_value.ok_or_else(|| anyhow::anyhow!("Missing 'edit_value' for update_input"))?;
441                
442                if let Some(desc) = updates.get("description").and_then(|v| v.as_str()) {
443                    input.description = Some(desc.to_string());
444                }
445                if let Some(required) = updates.get("required").and_then(|v| v.as_bool()) {
446                    input.required = required;
447                }
448                if let Some(default) = updates.get("default") {
449                    input.default = Some(default.clone());
450                }
451            }
452
453            // 输出参数操作
454            "add_output" => {
455                let output_json = edit_value.ok_or_else(|| anyhow::anyhow!("Missing 'edit_value' for add_output"))?;
456                let new_output: OutputDef = serde_json::from_value(output_json)
457                    .map_err(|e| anyhow::anyhow!("Invalid output definition: {}", e))?;
458                
459                workflow.outputs.push(new_output);
460            }
461
462            "remove_output" => {
463                let output_name = edit_target.ok_or_else(|| anyhow::anyhow!("Missing 'edit_target' for remove_output"))?;
464                workflow.outputs.retain(|o| o.name != output_name);
465            }
466
467            "update_output" => {
468                let output_name = edit_target.ok_or_else(|| anyhow::anyhow!("Missing 'edit_target' for update_output"))?;
469                let output = workflow.outputs.iter_mut().find(|o| o.name == output_name)
470                    .ok_or_else(|| anyhow::anyhow!("Output '{}' not found", output_name))?;
471                
472                let updates = edit_value.ok_or_else(|| anyhow::anyhow!("Missing 'edit_value' for update_output"))?;
473                
474                if let Some(value) = updates.get("value").and_then(|v| v.as_str()) {
475                    output.value = value.to_string();
476                }
477                if let Some(desc) = updates.get("description").and_then(|v| v.as_str()) {
478                    output.description = Some(desc.to_string());
479                }
480            }
481
482            // 元数据操作
483            "update_metadata" => {
484                let updates = edit_value.ok_or_else(|| anyhow::anyhow!("Missing 'edit_value' for update_metadata"))?;
485                
486                if let Some(name) = updates.get("name").and_then(|v| v.as_str()) {
487                    workflow.name = name.to_string();
488                }
489                if let Some(desc) = updates.get("description").and_then(|v| v.as_str()) {
490                    workflow.description = Some(desc.to_string());
491                }
492                if let Some(version) = updates.get("version").and_then(|v| v.as_str()) {
493                    workflow.version = version.to_string();
494                }
495            }
496
497            _ => bail!("Unknown edit operation: {}", edit_operation),
498        }
499
500        // 验证修改后的 workflow
501        workflow.validate()
502            .map_err(|e| anyhow::anyhow!("Workflow validation failed after edit: {}", e))?;
503
504        // 保存文件
505        let yaml_content = to_yaml(&workflow)
506            .map_err(|e| anyhow::anyhow!("Failed to convert to YAML: {}", e))?;
507        fs::write(&file_path, &yaml_content)
508            .map_err(|e| anyhow::anyhow!("Failed to write file {:?}: {}", file_path, e))?;
509
510        Ok(format!(
511            "✓ Workflow '{}' updated successfully!\n\nOperation: {}\n📄 File: {:?}\n\n```yaml\n{}\n```\n\nUse 'info' mode to see detailed structure.",
512            workflow_id,
513            edit_operation,
514            file_path,
515            yaml_content
516        ))
517    }
518
519    /// 处理查看 workflow 信息
520    async fn handle_info(&self, params: Value) -> Result<String> {
521        let workflow_id = params["workflow_id"]
522            .as_str()
523            .ok_or_else(|| anyhow::anyhow!("Missing 'workflow_id' parameter"))?;
524
525        // 获取保存位置
526        let location = params["location"].as_str().unwrap_or("project");
527        let save_dir = get_workflow_dir(location)?;
528        let file_path = save_dir.join(format!("{}.yaml", workflow_id));
529
530        // 直接从文件加载
531        if !file_path.exists() {
532            bail!("Workflow '{}' not found at {:?}", workflow_id, file_path);
533        }
534
535        let workflow = parse_workflow_from_file(&file_path)
536            .map_err(|e| anyhow::anyhow!("Failed to load workflow: {}", e))?;
537
538        let mut info = format!(
539            "📋 Workflow: {}\n\n📁 ID: {}\n📝 Name: {}\n📌 Version: {}\n",
540            workflow_id,
541            workflow.id,
542            workflow.name,
543            workflow.version
544        );
545
546        if let Some(ref desc) = workflow.description {
547            info.push_str(&format!("📖 Description: {}\n", desc));
548        }
549
550        // 节点信息
551        info.push_str("\n📊 Nodes:\n");
552        for node in &workflow.nodes {
553            let node_type = match node.node_type {
554                NodeType::Start => "start",
555                NodeType::End => "end",
556                NodeType::Task => "task",
557                NodeType::Condition => "condition",
558                NodeType::Parallel => "parallel",
559                NodeType::Wait => "wait",
560                NodeType::Approval => "approval",
561                NodeType::SubWorkflow => "subworkflow",
562            };
563            info.push_str(&format!("  - {} [{}] {}\n", node.id, node_type, node.name));
564            
565            if let Some(ref task) = node.task {
566                info.push_str(&format!("    Task: {}\n", task));
567            }
568        }
569
570        // 边信息
571        info.push_str("\n🔗 Edges:\n");
572        for edge in &workflow.edges {
573            let label = edge.label.as_ref().map(|l| format!(" ({})", l)).unwrap_or_default();
574            info.push_str(&format!("  {} → {}{}\n", edge.from, edge.to, label));
575        }
576
577        // 输入参数
578        if !workflow.inputs.is_empty() {
579            info.push_str("\n📥 Inputs:\n");
580            for input in &workflow.inputs {
581                let required = if input.required { " (required)" } else { "" };
582                info.push_str(&format!("  - {} [{}]{}\n", input.name, input.input_type, required));
583                if let Some(ref desc) = input.description {
584                    info.push_str(&format!("    {}\n", desc));
585                }
586            }
587        }
588
589        // 输出参数
590        if !workflow.outputs.is_empty() {
591            info.push_str("\n📤 Outputs:\n");
592            for output in &workflow.outputs {
593                info.push_str(&format!("  - {}: {}\n", output.name, output.value));
594            }
595        }
596
597        Ok(info)
598    }
599
600    /// 简单工作流模板
601    fn get_simple_template(&self) -> String {
602        r#"id: my-workflow
603name: My Workflow
604version: "1.0.0"
605description: A simple workflow example
606
607inputs:
608  - name: param1
609    type: string
610    required: true
611    description: First parameter
612
613outputs:
614  - name: result
615    value: "{{nodes.end.output}}"
616
617nodes:
618  - id: start
619    type: start
620    name: Start
621
622  - id: task1
623    type: task
624    name: First Task
625    task: example_task
626    params:
627      input: "{{inputs.param1}}"
628    on_failure:
629      type: abort
630
631  - id: end
632    type: end
633    name: End
634
635edges:
636  - from: start
637    to: task1
638  - from: task1
639    to: end
640"#.to_string()
641    }
642
643    /// 并行工作流模板
644    fn get_parallel_template(&self) -> String {
645        r#"id: parallel-workflow
646name: Parallel Workflow
647version: "1.0.0"
648description: A workflow with parallel execution
649
650inputs:
651  - name: data
652    type: string
653    required: true
654    description: Input data to process
655
656nodes:
657  - id: start
658    type: start
659    name: Start
660
661  - id: parallel1
662    type: parallel
663    name: Parallel Processing
664    parallel_branches:
665      - name: Branch A
666        nodes:
667          - id: task_a
668            type: task
669            name: Task A
670            task: process_a
671            params:
672              data: "{{inputs.data}}"
673      - name: Branch B
674        nodes:
675          - id: task_b
676            type: task
677            name: Task B
678            task: process_b
679            params:
680              data: "{{inputs.data}}"
681
682  - id: merge
683    type: task
684    name: Merge Results
685    task: merge_results
686    params:
687      result_a: "{{nodes.task_a.output}}"
688      result_b: "{{nodes.task_b.output}}"
689
690  - id: end
691    type: end
692    name: End
693
694edges:
695  - from: start
696    to: parallel1
697  - from: parallel1
698    to: merge
699  - from: merge
700    to: end
701"#.to_string()
702    }
703
704    /// 条件工作流模板
705    fn get_condition_template(&self) -> String {
706        r#"id: condition-workflow
707name: Conditional Workflow
708version: "1.0.0"
709description: A workflow with conditional branches
710
711inputs:
712  - name: value
713    type: number
714    required: true
715    description: Value to check
716
717nodes:
718  - id: start
719    type: start
720    name: Start
721
722  - id: check_value
723    type: condition
724    name: Check Value
725    branches:
726      - name: Positive
727        condition: "{{inputs.value}} > 0"
728        target: positive_task
729      - name: Negative
730        condition: "{{inputs.value}} < 0"
731        target: negative_task
732      - name: Zero
733        condition: "{{inputs.value}} == 0"
734        target: zero_task
735
736  - id: positive_task
737    type: task
738    name: Handle Positive
739    task: handle_positive
740
741  - id: negative_task
742    type: task
743    name: Handle Negative
744    task: handle_negative
745
746  - id: zero_task
747    type: task
748    name: Handle Zero
749    task: handle_zero
750
751  - id: end
752    type: end
753    name: End
754
755edges:
756  - from: start
757    to: check_value
758  - from: positive_task
759    to: end
760  - from: negative_task
761    to: end
762  - from: zero_task
763    to: end
764"#.to_string()
765    }
766
767    /// 研究工作流模板
768    fn get_research_template(&self) -> String {
769        r#"id: research-workflow
770name: Research Workflow
771version: "1.0.0"
772description: Automated research and report generation
773
774inputs:
775  - name: topic
776    type: string
777    required: true
778    description: Research topic
779  - name: depth
780    type: string
781    required: false
782    default: "medium"
783    description: Research depth (shallow/medium/deep)
784
785outputs:
786  - name: report
787    value: "{{nodes.generate_report.output}}"
788
789nodes:
790  - id: start
791    type: start
792    name: Start
793
794  - id: search_web
795    type: task
796    name: Search Web
797    task: websearch
798    params:
799      query: "{{inputs.topic}}"
800      max_results: 10
801    on_failure:
802      type: retry
803      max_attempts: 3
804
805  - id: analyze_results
806    type: task
807    name: Analyze Results
808    task: analyze
809    params:
810      data: "{{nodes.search_web.output}}"
811      depth: "{{inputs.depth}}"
812
813  - id: generate_report
814    type: task
815    name: Generate Report
816    task: content_generation
817    params:
818      topic: "{{inputs.topic}}"
819      research_data: "{{nodes.analyze_results.output}}"
820      style: "professional"
821
822  - id: end
823    type: end
824    name: End
825
826edges:
827  - from: start
828    to: search_web
829  - from: search_web
830    to: analyze_results
831  - from: analyze_results
832    to: generate_report
833  - from: generate_report
834    to: end
835"#.to_string()
836    }
837
838    /// 批量操作工作流模板
839    fn get_batch_template(&self) -> String {
840        r#"id: batch-workflow
841name: Batch Processing Workflow
842version: "1.0.0"
843description: Process multiple items in batch
844
845inputs:
846  - name: items
847    type: array
848    required: true
849    description: Array of items to process
850  - name: operation
851    type: string
852    required: true
853    description: Operation to perform on each item
854
855nodes:
856  - id: start
857    type: start
858    name: Start
859
860  - id: prepare_batch
861    type: task
862    name: Prepare Batch
863    task: prepare_batch
864    params:
865      items: "{{inputs.items}}"
866
867  - id: process_items
868    type: parallel
869    name: Process Items
870    parallel_branches:
871      - name: Process Chunk 1
872        nodes:
873          - id: chunk1
874            type: task
875            name: Process Chunk 1
876            task: "{{inputs.operation}}"
877            params:
878              items: "{{nodes.prepare_batch.chunk1}}"
879      - name: Process Chunk 2
880        nodes:
881          - id: chunk2
882            type: task
883            name: Process Chunk 2
884            task: "{{inputs.operation}}"
885            params:
886              items: "{{nodes.prepare_batch.chunk2}}"
887
888  - id: aggregate_results
889    type: task
890    name: Aggregate Results
891    task: aggregate
892    params:
893      results:
894        - "{{nodes.chunk1.output}}"
895        - "{{nodes.chunk2.output}}"
896
897  - id: end
898    type: end
899    name: End
900
901edges:
902  - from: start
903    to: prepare_batch
904  - from: prepare_batch
905    to: process_items
906  - from: process_items
907    to: aggregate_results
908  - from: aggregate_results
909    to: end
910"#.to_string()
911    }
912}
913
914#[cfg(test)]
915mod tests {
916    use super::*;
917    use serde_json::json;
918
919    #[tokio::test]
920    async fn test_template_simple() {
921        let tool = WorkflowCreateTool;
922        let result = tool.execute(json!({
923            "mode": "template",
924            "template_type": "simple"
925        })).await.unwrap();
926        
927        assert!(result.contains("simple"));
928        assert!(result.contains("id:"));
929        assert!(result.contains("nodes:"));
930        assert!(result.contains("edges:"));
931    }
932
933    #[tokio::test]
934    async fn test_template_research() {
935        let tool = WorkflowCreateTool;
936        let result = tool.execute(json!({
937            "mode": "template",
938            "template_type": "research"
939        })).await.unwrap();
940        
941        assert!(result.contains("research"));
942        assert!(result.contains("websearch"));
943        assert!(result.contains("generate_report"));
944    }
945
946    #[tokio::test]
947    async fn test_validate_valid_workflow() {
948        let tool = WorkflowCreateTool;
949        let result = tool.execute(json!({
950            "mode": "validate",
951            "yaml_content": "id: test\nname: Test\nnodes:\n  - id: start\n    type: start\n    name: Start\n  - id: end\n    type: end\n    name: End\nedges:\n  - from: start\n    to: end"
952        })).await.unwrap();
953        
954        assert!(result.contains("validation passed"));
955        assert!(result.contains("Nodes: 2"));
956        assert!(result.contains("Edges: 1"));
957    }
958
959    #[tokio::test]
960    async fn test_validate_invalid_workflow() {
961        let tool = WorkflowCreateTool;
962        let result = tool.execute(json!({
963            "mode": "validate",
964            "yaml_content": "id: test\nname: Test\nnodes:\n  - id: task1\n    type: task\n    name: Task"
965        })).await;
966        
967        // Should fail because no start/end nodes
968        assert!(result.is_err());
969        assert!(result.unwrap_err().to_string().contains("validation failed"));
970    }
971
972    #[tokio::test]
973    async fn test_info_mode() {
974        let tool = WorkflowCreateTool;
975        
976        // 先创建一个 workflow
977        let create_result = tool.execute(json!({
978            "mode": "create",
979            "workflow": {
980                "id": "test-info-workflow",
981                "name": "Test Info Workflow",
982                "version": "1.0.0",
983                "description": "A test workflow for info mode",
984                "nodes": [
985                    {"id": "start", "type": "start", "name": "Start"},
986                    {"id": "task1", "type": "task", "name": "Task 1", "task": "process"},
987                    {"id": "end", "type": "end", "name": "End"}
988                ],
989                "edges": [
990                    {"from": "start", "to": "task1"},
991                    {"from": "task1", "to": "end"}
992                ],
993                "inputs": [
994                    {"name": "data", "type": "string", "required": true, "description": "Input data"}
995                ],
996                "outputs": [
997                    {"name": "result", "value": "{{nodes.task1.output}}"}
998                ]
999            },
1000            "location": "project",
1001            "overwrite": true
1002        })).await.unwrap();
1003        
1004        assert!(create_result.contains("created successfully"));
1005
1006        // 测试 info 模式
1007        let info_result = tool.execute(json!({
1008            "mode": "info",
1009            "workflow_id": "test-info-workflow"
1010        })).await.unwrap();
1011        
1012        assert!(info_result.contains("test-info-workflow"));
1013        assert!(info_result.contains("Test Info Workflow"));
1014        assert!(info_result.contains("Task 1"));
1015        assert!(info_result.contains("start → task1"));
1016        assert!(info_result.contains("data [string] (required)"));
1017    }
1018
1019    #[tokio::test]
1020    async fn test_edit_update_node() {
1021        let tool = WorkflowCreateTool;
1022        
1023        // 先创建一个 workflow
1024        tool.execute(json!({
1025            "mode": "create",
1026            "workflow": {
1027                "id": "test-edit-workflow",
1028                "name": "Test Edit Workflow",
1029                "version": "1.0.0",
1030                "nodes": [
1031                    {"id": "start", "type": "start", "name": "Start"},
1032                    {"id": "task1", "type": "task", "name": "Old Name", "task": "old_task"},
1033                    {"id": "end", "type": "end", "name": "End"}
1034                ],
1035                "edges": [
1036                    {"from": "start", "to": "task1"},
1037                    {"from": "task1", "to": "end"}
1038                ]
1039            },
1040            "location": "project",
1041            "overwrite": true
1042        })).await.unwrap();
1043
1044        // 测试 update_node
1045        let edit_result = tool.execute(json!({
1046            "mode": "edit",
1047            "workflow_id": "test-edit-workflow",
1048            "edit_operation": "update_node",
1049            "edit_target": "task1",
1050            "edit_value": {
1051                "name": "New Name",
1052                "task": "new_task"
1053            }
1054        })).await.unwrap();
1055        
1056        assert!(edit_result.contains("updated successfully"));
1057        assert!(edit_result.contains("update_node"));
1058
1059        // 验证更新
1060        let info_result = tool.execute(json!({
1061            "mode": "info",
1062            "workflow_id": "test-edit-workflow"
1063        })).await.unwrap();
1064        
1065        assert!(info_result.contains("New Name"));
1066        assert!(info_result.contains("new_task"));
1067        assert!(!info_result.contains("Old Name"));
1068    }
1069
1070    #[tokio::test]
1071    async fn test_edit_add_node() {
1072        let tool = WorkflowCreateTool;
1073        
1074        // 先创建一个 workflow
1075        tool.execute(json!({
1076            "mode": "create",
1077            "workflow": {
1078                "id": "test-add-node-workflow",
1079                "name": "Test Add Node",
1080                "version": "1.0.0",
1081                "nodes": [
1082                    {"id": "start", "type": "start", "name": "Start"},
1083                    {"id": "end", "type": "end", "name": "End"}
1084                ],
1085                "edges": [
1086                    {"from": "start", "to": "end"}
1087                ]
1088            },
1089            "location": "project",
1090            "overwrite": true
1091        })).await.unwrap();
1092
1093        // 测试 add_node
1094        let edit_result = tool.execute(json!({
1095            "mode": "edit",
1096            "workflow_id": "test-add-node-workflow",
1097            "edit_operation": "add_node",
1098            "edit_value": {
1099                "id": "new_task",
1100                "type": "task",
1101                "name": "New Task",
1102                "task": "process"
1103            }
1104        })).await.unwrap();
1105        
1106        assert!(edit_result.contains("updated successfully"));
1107
1108        // 测试 add_edge
1109        let edge_result = tool.execute(json!({
1110            "mode": "edit",
1111            "workflow_id": "test-add-node-workflow",
1112            "edit_operation": "add_edge",
1113            "edit_value": {
1114                "from": "start",
1115                "to": "new_task"
1116            }
1117        })).await.unwrap();
1118        
1119        assert!(edge_result.contains("updated successfully"));
1120
1121        // 再添加一条边
1122        tool.execute(json!({
1123            "mode": "edit",
1124            "workflow_id": "test-add-node-workflow",
1125            "edit_operation": "add_edge",
1126            "edit_value": {
1127                "from": "new_task",
1128                "to": "end"
1129            }
1130        })).await.unwrap();
1131
1132        // 验证节点和边
1133        let info_result = tool.execute(json!({
1134            "mode": "info",
1135            "workflow_id": "test-add-node-workflow"
1136        })).await.unwrap();
1137        
1138        assert!(info_result.contains("new_task"));
1139        assert!(info_result.contains("New Task"));
1140        assert!(info_result.contains("start → new_task"));
1141        assert!(info_result.contains("new_task → end"));
1142    }
1143
1144    #[tokio::test]
1145    async fn test_invalid_mode() {
1146        let tool = WorkflowCreateTool;
1147        let result = tool.execute(json!({
1148            "mode": "invalid"
1149        })).await;
1150        
1151        assert!(result.is_err());
1152    }
1153
1154    #[test]
1155    fn test_definition() {
1156        let tool = WorkflowCreateTool;
1157        let def = tool.definition();
1158        
1159        assert_eq!(def.name, "workflow_create");
1160        assert!(def.description.contains("创建并保存"));
1161        assert!(def.parameters["properties"]["mode"].is_object());
1162    }
1163}