feat: add TypeSafe Jev as HITL audit backend

Let audit_agent approve or reject with one System One call instead of chat JSON, while keeping the OpenAI-compatible backend as an option.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ed1s0nZandCursor committed 2026-09-22 21:15:53 +08:00
1 parent 3aa9274675
commit 38b96ec67a
30 files changed
+1372 -21

No files matched your search

+59
View File
@@ -24,6 +24,7 @@ import (
"cyberstrike-ai/internal/openai"
"cyberstrike-ai/internal/security"
"cyberstrike-ai/internal/toolguard"
"cyberstrike-ai/internal/typesafe"
"github.com/cloudwego/eino/schema"
"github.com/gin-gonic/gin"
@@ -891,6 +892,7 @@ func (h *ConfigHandler) UpdateConfig(c *gin.Context) {
}
if req.Hitl != nil {
h.config.Hitl.AuditBackend = req.Hitl.EffectiveAuditBackend()
h.config.Hitl.AuditModel = req.Hitl.AuditModel
h.config.Hitl.ToolWhitelist = mergeHitlToolWhitelistSlice(nil, req.Hitl.ToolWhitelist)
if strings.TrimSpace(req.Hitl.DefaultMode) != "" {
@@ -911,6 +913,7 @@ func (h *ConfigHandler) UpdateConfig(c *gin.Context) {
h.config.Hitl.RetentionDays = &v
}
h.logger.Info("更新HITL配置",
zap.String("audit_backend", h.config.Hitl.AuditBackend),
zap.String("default_reviewer", h.config.Hitl.DefaultReviewer),
zap.Int("tool_whitelist", len(h.config.Hitl.ToolWhitelist)),
)
@@ -1317,6 +1320,61 @@ func (h *ConfigHandler) TestOpenAI(c *gin.Context) {
})
}
// TestTypeSafeRequest 测试 TypeSafe / Jev 连接。
type TestTypeSafeRequest struct {
BaseURL string `json:"base_url"`
APIKey string `json:"api_key"`
Model string `json:"model"`
}
// TestTypeSafe 用一条最小 Noul 验证 TypeSafe System One 是否可用。
func (h *ConfigHandler) TestTypeSafe(c *gin.Context) {
var req TestTypeSafeRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的请求参数: " + err.Error()})
return
}
if strings.TrimSpace(req.APIKey) == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "TypeSafe API Key 不能为空"})
return
}
client := typesafe.NewClient(req.BaseURL, req.APIKey, req.Model, nil)
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
defer cancel()
start := time.Now()
result, err := client.SystemOne(ctx, "connectivity ping", map[string]typesafe.Question{
"ok": typesafe.Noul("Is this a connectivity test ping?", "Yes, this is only a ping.", "No."),
})
if err != nil {
if apiErr, ok := err.(*typesafe.APIError); ok {
c.JSON(http.StatusOK, gin.H{
"success": false,
"error": fmt.Sprintf("API 返回错误 (HTTP %d): %s", apiErr.StatusCode, apiErr.Body),
"status_code": apiErr.StatusCode,
})
return
}
c.JSON(http.StatusOK, gin.H{
"success": false,
"error": "连接失败: " + err.Error(),
})
return
}
model := strings.TrimSpace(req.Model)
if result != nil && strings.TrimSpace(result.Model) != "" {
model = result.Model
}
if model == "" {
model = config.TypeSafeDefaultModel
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"model": model,
"latency_ms": time.Since(start).Milliseconds(),
})
}
// ListModelsRequest 获取模型列表请求(OpenAI 兼容 GET /models)。
type ListModelsRequest struct {
Provider string `json:"provider"`
@@ -2149,6 +2207,7 @@ func (h *ConfigHandler) MergeHitlToolWhitelistIntoConfig(add []string) error {
func updateHitlConfig(doc *yaml.Node, cfg config.HitlConfig) {
root := doc.Content[0]
hitlNode := ensureMap(root, "hitl")
setStringInMap(hitlNode, "audit_backend", cfg.EffectiveAuditBackend())
auditModelNode := ensureMap(hitlNode, "audit_model")
setStringInMap(auditModelNode, "provider", cfg.AuditModel.Provider)
setStringInMap(auditModelNode, "base_url", cfg.AuditModel.BaseURL)