====================== 2. Skills 管理 API ====================== SkillsListOrCreateHandler 处理: - GET /v1/skills 列出所有 Skills - POST /v1/skills 安装或更新一个 Skill (JSON 形式) 这里使用简化的 JSON 协议, 方便集成: POST /v1/skills { "id": "pdf-to-markdown", "files": [ {"path": "pdf-to-markdown/SKILL.md", "cont
()
| 240 | // ] |
| 241 | // } |
| 242 | func (s *Server) SkillsListOrCreateHandler() http.Handler { |
| 243 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 244 | if s.skillsManager == nil { |
| 245 | http.Error(w, "skills manager not initialized", http.StatusServiceUnavailable) |
| 246 | return |
| 247 | } |
| 248 | |
| 249 | switch r.Method { |
| 250 | case http.MethodGet: |
| 251 | ctx := r.Context() |
| 252 | infos, err := s.skillsManager.List(ctx) |
| 253 | if err != nil { |
| 254 | http.Error(w, "list skills failed: "+err.Error(), http.StatusInternalServerError) |
| 255 | return |
| 256 | } |
| 257 | writeJSON(w, http.StatusOK, infos) |
| 258 | |
| 259 | case http.MethodPost: |
| 260 | var req struct { |
| 261 | ID string `json:"id"` |
| 262 | Files []struct { |
| 263 | Path string `json:"path"` |
| 264 | Content string `json:"content"` |
| 265 | } `json:"files"` |
| 266 | } |
| 267 | if err := json.NewDecoder(r.Body).Decode(&req); err != nil { |
| 268 | http.Error(w, "invalid JSON body", http.StatusBadRequest) |
| 269 | return |
| 270 | } |
| 271 | if req.ID == "" { |
| 272 | http.Error(w, "id is required", http.StatusBadRequest) |
| 273 | return |
| 274 | } |
| 275 | if len(req.Files) == 0 { |
| 276 | http.Error(w, "files is required", http.StatusBadRequest) |
| 277 | return |
| 278 | } |
| 279 | |
| 280 | // 将文件聚合为 map[path]content, 交给 Manager 处理 |
| 281 | ctx := r.Context() |
| 282 | files := make(map[string]string, len(req.Files)) |
| 283 | for _, f := range req.Files { |
| 284 | if f.Path == "" { |
| 285 | continue |
| 286 | } |
| 287 | relPath := f.Path |
| 288 | // 允许传入包含 id 前缀的路径, 也允许省略前缀。 |
| 289 | if strings.HasPrefix(relPath, req.ID+"/") || strings.HasPrefix(relPath, req.ID+"\\") { |
| 290 | // 去掉可能的前缀 "id/" |
| 291 | relPath = relPath[len(req.ID)+1:] |
| 292 | } |
| 293 | relPath = filepath.ToSlash(relPath) |
| 294 | files[relPath] = f.Content |
| 295 | } |
| 296 | |
| 297 | if err := s.skillsManager.InstallFromFiles(ctx, req.ID, files); err != nil { |
| 298 | http.Error(w, "install skill failed: "+err.Error(), http.StatusInternalServerError) |
| 299 | return |
nothing calls this directly
no test coverage detected