package sysadmin import ( "bytes" "context" "encoding/json" "fmt" "net/http" "strings" "time" ) // RunnerApplyPayload 对齐 deploy/pi-agent/runner.mjs POST /admin/reload。 type RunnerApplyPayload struct { Minimax struct { Endpoint string `json:"endpoint"` APIKey string `json:"apiKey"` ModelID string `json:"modelId"` APIType string `json:"apiType"` } `json:"minimax"` TavilyKeys []string `json:"tavilyKeys"` PIModel string `json:"piModel,omitempty"` } // NewRunnerApplyFunc 构造推送到 PI runner 的 ApplyFunc。 func NewRunnerApplyFunc(svc *Service, runnerURL, authToken string, client *http.Client) ApplyFunc { if client == nil { client = &http.Client{Timeout: 8 * time.Second} } base := strings.TrimRight(strings.TrimSpace(runnerURL), "/") token := strings.TrimSpace(authToken) return func(ctx context.Context) (string, error) { if base == "" { return "n/a", fmt.Errorf("sysadmin: PI runner URL empty") } payload, err := BuildRunnerPayload(ctx, svc) if err != nil { return "error", err } body, err := json.Marshal(payload) if err != nil { return "error", err } req, err := http.NewRequestWithContext(ctx, http.MethodPost, base+"/admin/reload", bytes.NewReader(body)) if err != nil { return "error", err } req.Header.Set("Content-Type", "application/json") if token != "" { req.Header.Set("Authorization", "Bearer "+token) } resp, err := client.Do(req) if err != nil { return "error", fmt.Errorf("sysadmin: runner reload: %w", err) } defer resp.Body.Close() if resp.StatusCode >= 300 { return "error", fmt.Errorf("sysadmin: runner reload HTTP %d", resp.StatusCode) } return "ok", nil } } // BuildRunnerPayload 从白名单解析值组装 reload 载荷。 func BuildRunnerPayload(ctx context.Context, svc *Service) (RunnerApplyPayload, error) { var out RunnerApplyPayload if svc == nil { return out, fmt.Errorf("sysadmin: nil service") } get := func(key string) string { v, _ := svc.ResolveRawString(ctx, key) return strings.TrimSpace(v) } out.Minimax.Endpoint = get("agent.minimax_endpoint_url") out.Minimax.ModelID = get("agent.minimax_model_id") out.Minimax.APIType = get("agent.minimax_api_type") out.Minimax.APIKey = get("secrets.minimax_api_key") out.PIModel = get("agent.pi_agent_model") rawKeys := get("secrets.tavily_api_keys") if rawKeys != "" { for _, part := range strings.FieldsFunc(rawKeys, func(r rune) bool { return r == ',' || r == ';' || r == '\n' || r == ' ' }) { if part != "" { out.TavilyKeys = append(out.TavilyKeys, part) } } } return out, nil }