Initial commit: CarrotAssistant backend (Go + Gin + SQLite, OpenAI-compatible agent runtime)

This commit is contained in:
2026-07-06 16:10:55 +08:00
commit 5f84739b18
40 changed files with 4781 additions and 0 deletions

101
internal/skill/manual.go Normal file
View File

@@ -0,0 +1,101 @@
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
}