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