package skill import ( "encoding/json" "fmt" "strings" ) // ManualInput is the form payload for the "manual" source type: the operator // types one tool's details directly. Everything the synthesis pass needs is // here, so manual skills can be produced without any LLM call. type ManualInput struct { Name string `json:"name"` Description string `json:"description"` Method string `json:"method"` URL string `json:"url"` QueryParams map[string]string `json:"query_params"` // name -> placeholder target; we store as {{name}} PathParams []string `json:"path_params"` // names appearing in URL as {name} Headers map[string]string `json:"headers"` // ParamSchema, when supplied, overrides the flat param construction below. ParamSchema json.RawMessage `json:"param_schema"` } // ManualCandidate builds a CandidateTool from a manual form submission. It is // the simplest importer: the operator has already done the LLM's job by // hand, so we just normalise into our shapes. func ManualCandidate(in ManualInput) (CandidateTool, error) { name := strings.TrimSpace(in.Name) if name == "" { return CandidateTool{}, fmt.Errorf("工具名称必填") } if strings.TrimSpace(in.URL) == "" { return CandidateTool{}, fmt.Errorf("URL 必填") } method := strings.ToUpper(strings.TrimSpace(in.Method)) if method == "" { method = "GET" } // Default param schema: every query and path param is a required string. props := map[string]any{} var required []string for _, n := range in.PathParams { n = strings.TrimSpace(n) if n == "" { continue } props[n] = map[string]any{"type": "string"} required = append(required, n) } query := map[string]string{} for n := range in.QueryParams { n = strings.TrimSpace(n) if n == "" { continue } props[n] = map[string]any{"type": "string"} query[n] = "{{" + n + "}}" } for n := range in.Headers { n = strings.TrimSpace(n) if n == "" { continue } props[n] = map[string]any{"type": "string"} } var paramsJSON jsonObject if len(in.ParamSchema) > 0 && strings.TrimSpace(string(in.ParamSchema)) != "" { paramsJSON = jsonObject(in.ParamSchema) } else { schema := map[string]any{"type": "object", "properties": props} if len(required) > 0 { schema["required"] = required } b, _ := json.Marshal(schema) paramsJSON = jsonObject(b) } // Path params map: stored so the executor can template {name} in the URL. pathParams := map[string]string{} for _, n := range in.PathParams { n = strings.TrimSpace(n) if n != "" { pathParams[n] = "{{" + n + "}}" } } return CandidateTool{ Name: name, Description: strings.TrimSpace(in.Description), Parameters: paramsJSON, Endpoint: Endpoint{ Method: method, URL: strings.TrimSpace(in.URL), Path: pathParams, Query: query, Header: in.Headers, }, }, nil }