package main import ( "context" "encoding/json" "errors" "flag" "fmt" "io" "net/http" "os" "strings" "time" "data-terminal-cloud/internal/agenteval" "github.com/google/uuid" ) func main() { os.Exit(run(os.Args[1:], os.Stdout, os.Stderr)) } func run(args []string, stdout, stderr io.Writer) int { flags := flag.NewFlagSet("agent-eval-register", flag.ContinueOnError) flags.SetOutput(stderr) suitePath := flags.String("suite", "", "评测集JSON路径") observationsPath := flags.String("observations", "", "生成本报告的脱敏观测JSON路径") reportPath := flags.String("report", "", "agent-eval生成的完整报告路径") tenantID := flags.String("tenant", "", "运营范围客户组织UUID") configID := flags.String("config", "", "待登记证据的运行配置版本UUID") apiBaseURL := flags.String("api", "http://127.0.0.1:58080", "SaaS API基础地址") tokenFile := flags.String("access-token-file", "", "OIDC访问令牌文件;省略时读取AGENT_EVAL_ACCESS_TOKEN") allowFailed := flags.Bool("allow-failed", false, "明确允许登记FAILED报告;FAILED不会满足发布门禁") dryRun := flags.Bool("dry-run", false, "只校验并显示登记摘要,不发送请求") if err := flags.Parse(args); err != nil { return 2 } if *suitePath == "" || *observationsPath == "" || *reportPath == "" || *tenantID == "" || *configID == "" || flags.NArg() != 0 { fmt.Fprintln(stderr, "usage: agent-eval-register -suite -observations -report -tenant -config [-api ] [-access-token-file ] [-allow-failed] [-dry-run]") return 2 } if _, err := uuid.Parse(*tenantID); err != nil { fmt.Fprintln(stderr, "tenant必须是有效UUID") return 2 } if _, err := uuid.Parse(*configID); err != nil { fmt.Fprintln(stderr, "config必须是有效UUID") return 2 } var suite agenteval.Suite if err := decodeFile(*suitePath, &suite); err != nil { fmt.Fprintf(stderr, "读取评测集失败: %v\n", err) return 2 } var artifact agenteval.Artifact if err := decodeFile(*reportPath, &artifact); err != nil { fmt.Fprintf(stderr, "读取评测报告失败: %v\n", err) return 2 } var observations agenteval.ObservationRun if err := decodeFile(*observationsPath, &observations); err != nil { fmt.Fprintf(stderr, "读取评测观测失败: %v\n", err) return 2 } if err := agenteval.ValidateArtifact(suite, observations, artifact, *allowFailed); err != nil { fmt.Fprintf(stderr, "评测报告不能登记: %v\n", err) return 2 } key, err := agenteval.RegistrationIdempotencyKey(*configID, artifact.RegistrationPayload) if err != nil { fmt.Fprintf(stderr, "生成幂等键失败: %v\n", err) return 2 } if *dryRun { summary := map[string]any{ "dry_run": true, "tenant_id": *tenantID, "config_version_id": *configID, "suite_version": artifact.Report.SuiteVersion, "suite_sha256": artifact.Report.SuiteSHA256, "agent_version": artifact.Report.AgentVersion, "result": artifact.Report.Result, "report_ref": artifact.RegistrationPayload.ReportRef, "idempotency_key": key, } _ = json.NewEncoder(stdout).Encode(summary) return 0 } token, err := readToken(*tokenFile) if err != nil { fmt.Fprintf(stderr, "读取访问令牌失败: %v\n", err) return 2 } registrar, err := agenteval.NewRegistrar(*apiBaseURL, token, &http.Client{Timeout: 15 * time.Second}) if err != nil { fmt.Fprintf(stderr, "创建登记客户端失败: %v\n", err) return 2 } result, err := registrar.Register(context.Background(), *tenantID, *configID, artifact.RegistrationPayload) if err != nil { fmt.Fprintf(stderr, "登记评测证据失败: %v;request_id=%s\n", err, result.RequestID) return 1 } _ = json.NewEncoder(stdout).Encode(map[string]any{ "evaluation_id": result.EvaluationID, "status_code": result.StatusCode, "idempotency_replayed": result.Replayed, "request_id": result.RequestID, "idempotency_key": key, }) return 0 } func readToken(path string) (string, error) { if strings.TrimSpace(path) == "" { value := strings.TrimSpace(os.Getenv("AGENT_EVAL_ACCESS_TOKEN")) if value == "" { return "", errors.New("set AGENT_EVAL_ACCESS_TOKEN or -access-token-file") } return value, nil } content, err := os.ReadFile(path) if err != nil { return "", err } value := strings.TrimSpace(string(content)) if value == "" || strings.ContainsAny(value, "\r\n\x00") { return "", errors.New("token file must contain one non-empty line") } return value, nil } func decodeFile(path string, target any) error { file, err := os.Open(path) if err != nil { return err } defer file.Close() decoder := json.NewDecoder(file) decoder.DisallowUnknownFields() if err = decoder.Decode(target); err != nil { return err } var trailing any if err = decoder.Decode(&trailing); !errors.Is(err, io.EOF) { if err == nil { return errors.New("JSON文件包含多个顶层值") } return err } return nil }