SkillMarkdownParser.java 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. package com.agent.management.parser;
  2. import com.agent.management.model.dto.IOField;
  3. import com.agent.management.model.dto.SkillDTO;
  4. import com.fasterxml.jackson.core.type.TypeReference;
  5. import com.fasterxml.jackson.databind.ObjectMapper;
  6. import lombok.extern.slf4j.Slf4j;
  7. import org.springframework.stereotype.Component;
  8. import java.io.BufferedReader;
  9. import java.io.IOException;
  10. import java.nio.charset.StandardCharsets;
  11. import java.nio.file.Files;
  12. import java.nio.file.Path;
  13. import java.util.List;
  14. import java.util.Optional;
  15. /**
  16. * SKILL.md 文件解析器
  17. * 解析文件头部的 name、description、inputs、outputs 字段
  18. */
  19. @Slf4j
  20. @Component
  21. public class SkillMarkdownParser {
  22. private static final String NAME_PREFIX = "name:";
  23. private static final String DESC_PREFIX = "description:";
  24. private static final String INPUTS_PREFIX = "inputs:";
  25. private static final String OUTPUTS_PREFIX = "outputs:";
  26. private final ObjectMapper objectMapper = new ObjectMapper();
  27. /**
  28. * 解析 SKILL.md 文件,提取 name、description、inputs、outputs
  29. *
  30. * @param skillMdPath SKILL.md 文件路径
  31. * @return 解析后的 SkillDTO,若解析失败返回 empty
  32. */
  33. public Optional<SkillDTO> parse(Path skillMdPath) {
  34. SkillDTO dto = new SkillDTO();
  35. dto.setPath(skillMdPath.getParent().toString());
  36. dto.setFolderName(skillMdPath.getParent().getFileName().toString());
  37. try (BufferedReader reader = Files.newBufferedReader(skillMdPath, StandardCharsets.UTF_8)) {
  38. String name = null;
  39. String description = null;
  40. List<IOField> inputs = null;
  41. List<IOField> outputs = null;
  42. String line;
  43. int lineCount = 0;
  44. int maxLinesToScan = 30;
  45. while ((line = reader.readLine()) != null && lineCount < maxLinesToScan) {
  46. lineCount++;
  47. String trimmed = line.trim();
  48. // 跳过空行、注释行、Markdown 标题行、frontmatter 分隔符
  49. if (trimmed.isEmpty() || trimmed.startsWith("<!--") || trimmed.startsWith("#") || trimmed.equals("---")) {
  50. continue;
  51. }
  52. if (name == null && trimmed.toLowerCase().startsWith(NAME_PREFIX)) {
  53. name = trimmed.substring(NAME_PREFIX.length()).trim();
  54. continue;
  55. }
  56. if (description == null && trimmed.toLowerCase().startsWith(DESC_PREFIX)) {
  57. description = trimmed.substring(DESC_PREFIX.length()).trim();
  58. continue;
  59. }
  60. if (inputs == null && trimmed.toLowerCase().startsWith(INPUTS_PREFIX)) {
  61. inputs = parseJsonArray(trimmed.substring(INPUTS_PREFIX.length()).trim());
  62. continue;
  63. }
  64. if (outputs == null && trimmed.toLowerCase().startsWith(OUTPUTS_PREFIX)) {
  65. outputs = parseJsonArray(trimmed.substring(OUTPUTS_PREFIX.length()).trim());
  66. continue;
  67. }
  68. // 所有字段都已解析完成,提前退出
  69. if (name != null && description != null && inputs != null && outputs != null) {
  70. break;
  71. }
  72. }
  73. if (name == null || name.isEmpty() || description == null || description.isEmpty()) {
  74. log.warn("SKILL.md 解析失败,缺少必要字段: name={}, description={}, path={}",
  75. name, description, skillMdPath);
  76. return Optional.empty();
  77. }
  78. dto.setName(name);
  79. dto.setDescription(description);
  80. dto.setInputs(inputs);
  81. dto.setOutputs(outputs);
  82. return Optional.of(dto);
  83. } catch (IOException e) {
  84. log.error("读取 SKILL.md 文件失败: {}", skillMdPath, e);
  85. return Optional.empty();
  86. }
  87. }
  88. private List<IOField> parseJsonArray(String json) {
  89. if (json == null || json.isEmpty()) {
  90. return null;
  91. }
  92. try {
  93. return objectMapper.readValue(json, new TypeReference<List<IOField>>() {});
  94. } catch (IOException e) {
  95. log.warn("解析 JSON 数组失败: {}", e.getMessage());
  96. return null;
  97. }
  98. }
  99. }