ops.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. package tools
  2. import (
  3. "context"
  4. "encoding/json"
  5. "strings"
  6. "github.com/mark3labs/mcp-go/mcp"
  7. "fwh-toolbox-server/agentmcp/kit"
  8. "fwh-toolbox-server/dto"
  9. "fwh-toolbox-server/services"
  10. )
  11. // ServiceAccount 写操作在业务系统中的执行身份(Agent MCP 不区分调用者)
  12. const ServiceAccount = "AI 助手"
  13. // ---- feedback_create ----
  14. type feedbackCreateArgs struct {
  15. Title string `json:"title" jsonschema:"标题"`
  16. Type string `json:"type" jsonschema:"类型:bug 或 feature"`
  17. Priority string `json:"priority,omitempty" jsonschema:"优先级:urgent、high、medium(默认)、low、none"`
  18. Category string `json:"category,omitempty" jsonschema:"分类:bug 类为 bug、ui、performance、other;feature 类为 new、improvement、experience"`
  19. Description string `json:"description,omitempty" jsonschema:"详细描述:背景、现象、影响站点、复现步骤等"`
  20. }
  21. var FeedbackCreate = kit.Def[feedbackCreateArgs]{
  22. Name: "feedback_create",
  23. Title: "提交问题反馈到 Plane",
  24. Description: "在 Plane 中创建一条 Intake 工作项(问题反馈或需求)。提交人固定为服务账号“AI 助手”,请在描述中写明背景与来源。" +
  25. "返回 Plane 创建结果(工作项 ID 等)。这是写操作,执行前会请求确认。",
  26. ReadOnly: false,
  27. Handler: func(ctx context.Context, a feedbackCreateArgs) (*mcp.CallToolResult, error) {
  28. title, err := kit.Required("title", a.Title)
  29. if err != nil {
  30. return kit.Fail(err), nil
  31. }
  32. typ, err := kit.Enum("type", a.Type, "", "bug", "feature")
  33. if err != nil {
  34. return kit.Fail(err), nil
  35. }
  36. if typ == "" {
  37. return kit.Failf("参数 type 必填:bug 或 feature"), nil
  38. }
  39. priority, err := kit.Enum("priority", a.Priority, "medium", "urgent", "high", "medium", "low", "none")
  40. if err != nil {
  41. return kit.Fail(err), nil
  42. }
  43. res, err := services.SubmitFeedback(dto.FeedbackSubmitDTO{
  44. Title: title,
  45. Type: typ,
  46. Priority: priority,
  47. Category: strings.TrimSpace(a.Category),
  48. Description: a.Description,
  49. }, ServiceAccount)
  50. if err != nil {
  51. return kit.Upstream("Plane", err), nil
  52. }
  53. return kit.Object(res), nil
  54. },
  55. }
  56. // ---- tou_template_save ----
  57. type touTemplateSaveArgs struct {
  58. Payload string `json:"payload" jsonschema:"模版请求体 JSON:{\"template\":{...},\"strategyList\":[{...}],\"lang\":\"ZH_CN\"}。template.templateId 有值为更新、省略为新增;字段定义见技能 references 中的 TOU 模版说明"`
  59. }
  60. var TouTemplateSave = kit.Def[touTemplateSaveArgs]{
  61. Name: "tou_template_save",
  62. Title: "保存 TOU 调度模版(测试服)",
  63. Description: "在 FWH 测试服新增或更新 TOU 调度模版,只支持测试服。保存前校验每个策略的时段首尾衔接覆盖全天、sourceType 为 0;" +
  64. "返回 templateId 与按调度模式识别后的策略摘要。这是写操作,执行前会请求确认。",
  65. ReadOnly: false,
  66. // 更新会覆盖测试服上已有的模版,不可恢复:声明为破坏性,自动模式下也每次审批
  67. Destructive: true,
  68. Handler: func(ctx context.Context, a touTemplateSaveArgs) (*mcp.CallToolResult, error) {
  69. raw, err := kit.Required("payload", a.Payload)
  70. if err != nil {
  71. return kit.Fail(err), nil
  72. }
  73. var payload dto.FwhTestUpdateOrSaveTouTemplatePayload
  74. if err := json.Unmarshal([]byte(raw), &payload); err != nil {
  75. return kit.Failf("参数 payload 不是有效的 JSON:%v", err), nil
  76. }
  77. if payload.Template.SourceType != 0 {
  78. return kit.Failf("template.sourceType 只能为 0(系统模版)"), nil
  79. }
  80. if len(payload.StrategyList) == 0 {
  81. return kit.Failf("strategyList 不能为空"), nil
  82. }
  83. organized, _ := OrganizeTouDetail(payload)
  84. if issues := strategyIssues(organized); len(issues) > 0 {
  85. return kit.Failf("模版校验未通过:%s", strings.Join(issues, ";")), nil
  86. }
  87. if payload.Lang == "" {
  88. payload.Lang = "ZH_CN"
  89. }
  90. id, err := services.FwhTestSaveTouTemplate(payload)
  91. if err != nil {
  92. return kit.Upstream("FWH 测试服 TOU 模版接口", err), nil
  93. }
  94. return kit.Object(map[string]any{"templateId": id, "message": "已保存到测试服", "strategies": organized["strategies"]}), nil
  95. },
  96. }
  97. // strategyIssues 汇总整理结果中的时段衔接问题。
  98. func strategyIssues(organized map[string]any) []string {
  99. var out []string
  100. sts, _ := organized["strategies"].([]map[string]any)
  101. for _, st := range sts {
  102. if is, ok := st["issues"].([]string); ok {
  103. name, _ := st["seasonName"].(string)
  104. day, _ := st["dayName"].(string)
  105. for _, i := range is {
  106. out = append(out, name+"/"+day+":"+i)
  107. }
  108. }
  109. }
  110. return out
  111. }