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::Wait => "wait",
592                NodeType::Approval => "approval",
593                NodeType::SubWorkflow => "subworkflow",
594            };
595            info.push_str(&format!("  - {} [{}] {}\n", node.id, node_type, node.name));
596
597            if let Some(ref task) = node.task {
598                info.push_str(&format!("    Task: {}\n", task));
599            }
600        }
601
602        // 边信息
603        info.push_str("\n🔗 Edges:\n");
604        for edge in &workflow.edges {
605            let label = edge
606                .label
607                .as_ref()
608                .map(|l| format!(" ({})", l))
609                .unwrap_or_default();
610            info.push_str(&format!("  {} → {}{}\n", edge.from, edge.to, label));
611        }
612
613        // 输入参数
614        if !workflow.inputs.is_empty() {
615            info.push_str("\n📥 Inputs:\n");
616            for input in &workflow.inputs {
617                let required = if input.required { " (required)" } else { "" };
618                info.push_str(&format!(
619                    "  - {} [{}]{}\n",
620                    input.name, input.input_type, required
621                ));
622                if let Some(ref desc) = input.description {
623                    info.push_str(&format!("    {}\n", desc));
624                }
625            }
626        }
627
628        // 输出参数
629        if !workflow.outputs.is_empty() {
630            info.push_str("\n📤 Outputs:\n");
631            for output in &workflow.outputs {
632                info.push_str(&format!("  - {}: {}\n", output.name, output.value));
633            }
634        }
635
636        Ok(info)
637    }
638
639    /// 简单工作流模板
640    fn get_simple_template(&self) -> String {
641        r#"id: my-workflow
642name: My Workflow
643version: "1.0.0"
644description: A simple workflow example
645
646inputs:
647  - name: param1
648    type: string
649    required: true
650    description: First parameter
651
652outputs:
653  - name: result
654    value: "{{nodes.end.output}}"
655
656nodes:
657  - id: start
658    type: start
659    name: Start
660
661  - id: task1
662    type: task
663    name: First Task
664    task: example_task
665    params:
666      input: "{{inputs.param1}}"
667    on_failure:
668      type: abort
669
670  - id: end
671    type: end
672    name: End
673
674edges:
675  - from: start
676    to: task1
677  - from: task1
678    to: end
679"#
680        .to_string()
681    }
682
683    /// 并行工作流模板
684    fn get_parallel_template(&self) -> String {
685        r#"id: parallel-workflow
686name: Parallel Workflow
687version: "1.0.0"
688description: A workflow with parallel execution
689
690inputs:
691  - name: data
692    type: string
693    required: true
694    description: Input data to process
695
696nodes:
697  - id: start
698    type: start
699    name: Start
700
701  - id: parallel1
702    type: parallel
703    name: Parallel Processing
704    parallel_branches:
705      - name: Branch A
706        nodes:
707          - id: task_a
708            type: task
709            name: Task A
710            task: process_a
711            params:
712              data: "{{inputs.data}}"
713      - name: Branch B
714        nodes:
715          - id: task_b
716            type: task
717            name: Task B
718            task: process_b
719            params:
720              data: "{{inputs.data}}"
721
722  - id: merge
723    type: task
724    name: Merge Results
725    task: merge_results
726    params:
727      result_a: "{{nodes.task_a.output}}"
728      result_b: "{{nodes.task_b.output}}"
729
730  - id: end
731    type: end
732    name: End
733
734edges:
735  - from: start
736    to: parallel1
737  - from: parallel1
738    to: merge
739  - from: merge
740    to: end
741"#
742        .to_string()
743    }
744
745    /// 条件工作流模板
746    fn get_condition_template(&self) -> String {
747        r#"id: condition-workflow
748name: Conditional Workflow
749version: "1.0.0"
750description: A workflow with conditional branches
751
752inputs:
753  - name: value
754    type: number
755    required: true
756    description: Value to check
757
758nodes:
759  - id: start
760    type: start
761    name: Start
762
763  - id: check_value
764    type: condition
765    name: Check Value
766    branches:
767      - name: Positive
768        condition: "{{inputs.value}} > 0"
769        target: positive_task
770      - name: Negative
771        condition: "{{inputs.value}} < 0"
772        target: negative_task
773      - name: Zero
774        condition: "{{inputs.value}} == 0"
775        target: zero_task
776
777  - id: positive_task
778    type: task
779    name: Handle Positive
780    task: handle_positive
781
782  - id: negative_task
783    type: task
784    name: Handle Negative
785    task: handle_negative
786
787  - id: zero_task
788    type: task
789    name: Handle Zero
790    task: handle_zero
791
792  - id: end
793    type: end
794    name: End
795
796edges:
797  - from: start
798    to: check_value
799  - from: positive_task
800    to: end
801  - from: negative_task
802    to: end
803  - from: zero_task
804    to: end
805"#
806        .to_string()
807    }
808
809    /// 研究工作流模板
810    fn get_research_template(&self) -> String {
811        r#"id: research-workflow
812name: Research Workflow
813version: "1.0.0"
814description: Automated research and report generation
815
816inputs:
817  - name: topic
818    type: string
819    required: true
820    description: Research topic
821  - name: depth
822    type: string
823    required: false
824    default: "medium"
825    description: Research depth (shallow/medium/deep)
826
827outputs:
828  - name: report
829    value: "{{nodes.generate_report.output}}"
830
831nodes:
832  - id: start
833    type: start
834    name: Start
835
836  - id: search_web
837    type: task
838    name: Search Web
839    task: websearch
840    params:
841      query: "{{inputs.topic}}"
842      max_results: 10
843    on_failure:
844      type: retry
845      max_attempts: 3
846
847  - id: analyze_results
848    type: task
849    name: Analyze Results
850    task: analyze
851    params:
852      data: "{{nodes.search_web.output}}"
853      depth: "{{inputs.depth}}"
854
855  - id: generate_report
856    type: task
857    name: Generate Report
858    task: content_generation
859    params:
860      topic: "{{inputs.topic}}"
861      research_data: "{{nodes.analyze_results.output}}"
862      style: "professional"
863
864  - id: end
865    type: end
866    name: End
867
868edges:
869  - from: start
870    to: search_web
871  - from: search_web
872    to: analyze_results
873  - from: analyze_results
874    to: generate_report
875  - from: generate_report
876    to: end
877"#
878        .to_string()
879    }
880
881    /// 批量操作工作流模板
882    fn get_batch_template(&self) -> String {
883        r#"id: batch-workflow
884name: Batch Processing Workflow
885version: "1.0.0"
886description: Process multiple items in batch
887
888inputs:
889  - name: items
890    type: array
891    required: true
892    description: Array of items to process
893  - name: operation
894    type: string
895    required: true
896    description: Operation to perform on each item
897
898nodes:
899  - id: start
900    type: start
901    name: Start
902
903  - id: prepare_batch
904    type: task
905    name: Prepare Batch
906    task: prepare_batch
907    params:
908      items: "{{inputs.items}}"
909
910  - id: process_items
911    type: parallel
912    name: Process Items
913    parallel_branches:
914      - name: Process Chunk 1
915        nodes:
916          - id: chunk1
917            type: task
918            name: Process Chunk 1
919            task: "{{inputs.operation}}"
920            params:
921              items: "{{nodes.prepare_batch.chunk1}}"
922      - name: Process Chunk 2
923        nodes:
924          - id: chunk2
925            type: task
926            name: Process Chunk 2
927            task: "{{inputs.operation}}"
928            params:
929              items: "{{nodes.prepare_batch.chunk2}}"
930
931  - id: aggregate_results
932    type: task
933    name: Aggregate Results
934    task: aggregate
935    params:
936      results:
937        - "{{nodes.chunk1.output}}"
938        - "{{nodes.chunk2.output}}"
939
940  - id: end
941    type: end
942    name: End
943
944edges:
945  - from: start
946    to: prepare_batch
947  - from: prepare_batch
948    to: process_items
949  - from: process_items
950    to: aggregate_results
951  - from: aggregate_results
952    to: end
953"#
954        .to_string()
955    }
956}
957
958#[cfg(test)]
959mod tests {
960    use super::*;
961    use serde_json::json;
962
963    #[tokio::test]
964    async fn test_template_simple() {
965        let tool = WorkflowCreateTool;
966        let result = tool
967            .execute(json!({
968                "mode": "template",
969                "template_type": "simple"
970            }))
971            .await
972            .unwrap();
973
974        assert!(result.contains("simple"));
975        assert!(result.contains("id:"));
976        assert!(result.contains("nodes:"));
977        assert!(result.contains("edges:"));
978    }
979
980    #[tokio::test]
981    async fn test_template_research() {
982        let tool = WorkflowCreateTool;
983        let result = tool
984            .execute(json!({
985                "mode": "template",
986                "template_type": "research"
987            }))
988            .await
989            .unwrap();
990
991        assert!(result.contains("research"));
992        assert!(result.contains("websearch"));
993        assert!(result.contains("generate_report"));
994    }
995
996    #[tokio::test]
997    async fn test_validate_valid_workflow() {
998        let tool = WorkflowCreateTool;
999        let result = tool.execute(json!({
1000            "mode": "validate",
1001            "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"
1002        })).await.unwrap();
1003
1004        assert!(result.contains("validation passed"));
1005        assert!(result.contains("Nodes: 2"));
1006        assert!(result.contains("Edges: 1"));
1007    }
1008
1009    #[tokio::test]
1010    async fn test_validate_invalid_workflow() {
1011        let tool = WorkflowCreateTool;
1012        let result = tool.execute(json!({
1013            "mode": "validate",
1014            "yaml_content": "id: test\nname: Test\nnodes:\n  - id: task1\n    type: task\n    name: Task"
1015        })).await;
1016
1017        // Should fail because no start/end nodes
1018        assert!(result.is_err());
1019        assert!(
1020            result
1021                .unwrap_err()
1022                .to_string()
1023                .contains("validation failed")
1024        );
1025    }
1026
1027    #[tokio::test]
1028    async fn test_info_mode() {
1029        let tool = WorkflowCreateTool;
1030
1031        // 先创建一个 workflow
1032        let create_result = tool.execute(json!({
1033            "mode": "create",
1034            "workflow": {
1035                "id": "test-info-workflow",
1036                "name": "Test Info Workflow",
1037                "version": "1.0.0",
1038                "description": "A test workflow for info mode",
1039                "nodes": [
1040                    {"id": "start", "type": "start", "name": "Start"},
1041                    {"id": "task1", "type": "task", "name": "Task 1", "task": "process"},
1042                    {"id": "end", "type": "end", "name": "End"}
1043                ],
1044                "edges": [
1045                    {"from": "start", "to": "task1"},
1046                    {"from": "task1", "to": "end"}
1047                ],
1048                "inputs": [
1049                    {"name": "data", "type": "string", "required": true, "description": "Input data"}
1050                ],
1051                "outputs": [
1052                    {"name": "result", "value": "{{nodes.task1.output}}"}
1053                ]
1054            },
1055            "location": "project",
1056            "overwrite": true
1057        })).await.unwrap();
1058
1059        assert!(create_result.contains("created successfully"));
1060
1061        // 测试 info 模式
1062        let info_result = tool
1063            .execute(json!({
1064                "mode": "info",
1065                "workflow_id": "test-info-workflow"
1066            }))
1067            .await
1068            .unwrap();
1069
1070        assert!(info_result.contains("test-info-workflow"));
1071        assert!(info_result.contains("Test Info Workflow"));
1072        assert!(info_result.contains("Task 1"));
1073        assert!(info_result.contains("start → task1"));
1074        assert!(info_result.contains("data [string] (required)"));
1075    }
1076
1077    #[tokio::test]
1078    async fn test_edit_update_node() {
1079        let tool = WorkflowCreateTool;
1080
1081        // 先创建一个 workflow
1082        tool.execute(json!({
1083            "mode": "create",
1084            "workflow": {
1085                "id": "test-edit-workflow",
1086                "name": "Test Edit Workflow",
1087                "version": "1.0.0",
1088                "nodes": [
1089                    {"id": "start", "type": "start", "name": "Start"},
1090                    {"id": "task1", "type": "task", "name": "Old Name", "task": "old_task"},
1091                    {"id": "end", "type": "end", "name": "End"}
1092                ],
1093                "edges": [
1094                    {"from": "start", "to": "task1"},
1095                    {"from": "task1", "to": "end"}
1096                ]
1097            },
1098            "location": "project",
1099            "overwrite": true
1100        }))
1101        .await
1102        .unwrap();
1103
1104        // 测试 update_node
1105        let edit_result = tool
1106            .execute(json!({
1107                "mode": "edit",
1108                "workflow_id": "test-edit-workflow",
1109                "edit_operation": "update_node",
1110                "edit_target": "task1",
1111                "edit_value": {
1112                    "name": "New Name",
1113                    "task": "new_task"
1114                }
1115            }))
1116            .await
1117            .unwrap();
1118
1119        assert!(edit_result.contains("updated successfully"));
1120        assert!(edit_result.contains("update_node"));
1121
1122        // 验证更新
1123        let info_result = tool
1124            .execute(json!({
1125                "mode": "info",
1126                "workflow_id": "test-edit-workflow"
1127            }))
1128            .await
1129            .unwrap();
1130
1131        assert!(info_result.contains("New Name"));
1132        assert!(info_result.contains("new_task"));
1133        assert!(!info_result.contains("Old Name"));
1134    }
1135
1136    #[tokio::test]
1137    async fn test_edit_add_node() {
1138        let tool = WorkflowCreateTool;
1139
1140        // 先创建一个 workflow
1141        tool.execute(json!({
1142            "mode": "create",
1143            "workflow": {
1144                "id": "test-add-node-workflow",
1145                "name": "Test Add Node",
1146                "version": "1.0.0",
1147                "nodes": [
1148                    {"id": "start", "type": "start", "name": "Start"},
1149                    {"id": "end", "type": "end", "name": "End"}
1150                ],
1151                "edges": [
1152                    {"from": "start", "to": "end"}
1153                ]
1154            },
1155            "location": "project",
1156            "overwrite": true
1157        }))
1158        .await
1159        .unwrap();
1160
1161        // 测试 add_node
1162        let edit_result = tool
1163            .execute(json!({
1164                "mode": "edit",
1165                "workflow_id": "test-add-node-workflow",
1166                "edit_operation": "add_node",
1167                "edit_value": {
1168                    "id": "new_task",
1169                    "type": "task",
1170                    "name": "New Task",
1171                    "task": "process"
1172                }
1173            }))
1174            .await
1175            .unwrap();
1176
1177        assert!(edit_result.contains("updated successfully"));
1178
1179        // 测试 add_edge
1180        let edge_result = tool
1181            .execute(json!({
1182                "mode": "edit",
1183                "workflow_id": "test-add-node-workflow",
1184                "edit_operation": "add_edge",
1185                "edit_value": {
1186                    "from": "start",
1187                    "to": "new_task"
1188                }
1189            }))
1190            .await
1191            .unwrap();
1192
1193        assert!(edge_result.contains("updated successfully"));
1194
1195        // 再添加一条边
1196        tool.execute(json!({
1197            "mode": "edit",
1198            "workflow_id": "test-add-node-workflow",
1199            "edit_operation": "add_edge",
1200            "edit_value": {
1201                "from": "new_task",
1202                "to": "end"
1203            }
1204        }))
1205        .await
1206        .unwrap();
1207
1208        // 验证节点和边
1209        let info_result = tool
1210            .execute(json!({
1211                "mode": "info",
1212                "workflow_id": "test-add-node-workflow"
1213            }))
1214            .await
1215            .unwrap();
1216
1217        assert!(info_result.contains("new_task"));
1218        assert!(info_result.contains("New Task"));
1219        assert!(info_result.contains("start → new_task"));
1220        assert!(info_result.contains("new_task → end"));
1221    }
1222
1223    #[tokio::test]
1224    async fn test_invalid_mode() {
1225        let tool = WorkflowCreateTool;
1226        let result = tool
1227            .execute(json!({
1228                "mode": "invalid"
1229            }))
1230            .await;
1231
1232        assert!(result.is_err());
1233    }
1234
1235    #[test]
1236    fn test_definition() {
1237        let tool = WorkflowCreateTool;
1238        let def = tool.definition();
1239
1240        assert_eq!(def.name, "workflow_create");
1241        assert!(def.description.contains("创建并保存"));
1242        assert!(def.parameters["properties"]["mode"].is_object());
1243    }
1244}