feat(canvas): 云端 Agent - 完成策略分层、节点能力注册与可解释审批改造
This commit is contained in:
@@ -21,7 +21,82 @@ func RegisterAgentRoutes(r *gin.RouterGroup, svc *service.Service) {
|
||||
failService(c, err)
|
||||
return
|
||||
}
|
||||
ok(c, gin.H{"version": 2, "permissionModes": []string{"read_only", "request_approval", "auto"}, "contextScopes": []string{"canvas"}, "skills": true, "writeTools": true, "billing": "fixed_request", "maxHistoryPairs": 8, "maxSteps": 8, "tools": []string{"canvas_list_node_types", "canvas_get_state", "skill_read_file", "task_get", "model_list", "canvas_apply_ops", "generate_media"}})
|
||||
capabilities := service.CloudAgentCapabilitySetInfo()
|
||||
ok(c, gin.H{"version": 2, "permissionModes": []string{"read_only", "request_approval", "auto"}, "contextScopes": []string{"canvas"}, "skills": true, "writeTools": true, "billing": "fixed_request", "maxHistoryPairs": 8, "maxSteps": 8, "tools": service.CloudAgentSupportedToolNames(), "capabilitySetVersion": capabilities.Version, "capabilitySetHash": capabilities.Hash, "nodeTypes": capabilities.Nodes})
|
||||
})
|
||||
// Profiles are durable preference data, not an authorization surface. The
|
||||
// service validates scope ownership and the compiler injects the effective
|
||||
// layers only after the code-level policy has been fixed.
|
||||
r.GET("/agent/profile", func(c *gin.Context) {
|
||||
user, err := currentUser(c, svc)
|
||||
if err != nil {
|
||||
failService(c, err)
|
||||
return
|
||||
}
|
||||
scope := strings.TrimSpace(c.Query("scope"))
|
||||
projectID := strings.TrimSpace(c.Query("projectId"))
|
||||
canvasID := strings.TrimSpace(c.Query("canvasId"))
|
||||
if scope == "" {
|
||||
switch {
|
||||
case canvasID != "":
|
||||
scope = "canvas"
|
||||
case projectID != "":
|
||||
scope = "project"
|
||||
default:
|
||||
scope = "user"
|
||||
}
|
||||
}
|
||||
switch scope {
|
||||
case "user":
|
||||
if projectID != "" || canvasID != "" {
|
||||
fail(c, http.StatusBadRequest, errors.New("用户偏好不能带项目或画布 ID"))
|
||||
return
|
||||
}
|
||||
case "project":
|
||||
if projectID == "" || canvasID != "" {
|
||||
fail(c, http.StatusBadRequest, errors.New("项目偏好需要 projectId,且不能带 canvasId"))
|
||||
return
|
||||
}
|
||||
case "canvas":
|
||||
if canvasID == "" {
|
||||
fail(c, http.StatusBadRequest, errors.New("画布偏好需要 canvasId"))
|
||||
return
|
||||
}
|
||||
default:
|
||||
fail(c, http.StatusBadRequest, errors.New("无效的 Agent 偏好作用域"))
|
||||
return
|
||||
}
|
||||
view, err := svc.CloudAgentProfileForScope(user.ID, projectID, canvasID)
|
||||
if err != nil {
|
||||
failService(c, err)
|
||||
return
|
||||
}
|
||||
ok(c, view)
|
||||
})
|
||||
r.PATCH("/agent/profile", func(c *gin.Context) {
|
||||
user, err := currentUser(c, svc)
|
||||
if err != nil {
|
||||
failService(c, err)
|
||||
return
|
||||
}
|
||||
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 64<<10)
|
||||
var req service.AgentProfileRequest
|
||||
decoder := json.NewDecoder(c.Request.Body)
|
||||
decoder.DisallowUnknownFields()
|
||||
if err := decoder.Decode(&req); err != nil {
|
||||
fail(c, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
if err := decoder.Decode(&struct{}{}); err != io.EOF {
|
||||
fail(c, http.StatusBadRequest, errors.New("请求必须只包含一个 JSON 对象"))
|
||||
return
|
||||
}
|
||||
view, err := svc.UpdateCloudAgentProfile(user.ID, req)
|
||||
if err != nil {
|
||||
failService(c, err)
|
||||
return
|
||||
}
|
||||
ok(c, view)
|
||||
})
|
||||
create := func(c *gin.Context) {
|
||||
user, err := currentUser(c, svc)
|
||||
|
||||
@@ -29,7 +29,44 @@ paths:
|
||||
summary: 读取云端 Agent 能力
|
||||
responses:
|
||||
"200":
|
||||
description: 信封 data 包含 version、permissionModes、contextScopes、skills、writeTools、billing、maxHistoryPairs
|
||||
description: 信封 data 包含 version、permissionModes、contextScopes、skills、writeTools、billing、maxHistoryPairs、capabilitySetVersion、capabilitySetHash、nodeTypes、tools
|
||||
/agent/profile:
|
||||
get:
|
||||
tags: [agent]
|
||||
summary: 读取生效的用户、项目、画布 Agent 偏好层
|
||||
parameters:
|
||||
- in: query
|
||||
name: scope
|
||||
schema:
|
||||
type: string
|
||||
enum: [user, project, canvas]
|
||||
- in: query
|
||||
name: projectId
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: canvasId
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: 信封 data 为合并 revision、hash 和按 user/project/canvas 顺序排列的 layers;每层含独立数字 revision
|
||||
"403":
|
||||
description: 项目或画布不属于当前用户
|
||||
patch:
|
||||
tags: [agent]
|
||||
summary: 使用单层 revision CAS 更新 Agent 偏好
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/AgentProfileRequest"
|
||||
responses:
|
||||
"200":
|
||||
description: 返回更新后的生效偏好视图
|
||||
"409":
|
||||
description: 偏好 revision 已变化,须重新读取后保存
|
||||
/agent/runs:
|
||||
post:
|
||||
tags: [agent]
|
||||
@@ -314,6 +351,25 @@ components:
|
||||
in: cookie
|
||||
name: open_ai_canvas_session
|
||||
schemas:
|
||||
AgentProfileRequest:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [scope, content, revision]
|
||||
properties:
|
||||
scope:
|
||||
type: string
|
||||
enum: [user, project, canvas]
|
||||
projectId:
|
||||
type: string
|
||||
canvasId:
|
||||
type: string
|
||||
content:
|
||||
type: string
|
||||
description: 常驻偏好文档,最多 12000 个 Unicode 字符;不能授权节点、工具、预算或绕过审批
|
||||
revision:
|
||||
type: integer
|
||||
minimum: 0
|
||||
description: 待更新层的数字 revision;首次创建为 0
|
||||
CloudAgentRequest:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
@@ -336,6 +392,13 @@ components:
|
||||
permissionMode:
|
||||
type: string
|
||||
enum: [read_only, request_approval, auto]
|
||||
reasoningMode:
|
||||
type: string
|
||||
enum: [off, auto, deep]
|
||||
description: 本轮推理策略;供应商是否支持由后端校验
|
||||
profileRevision:
|
||||
type: string
|
||||
description: GET /agent/profile 的生效合并 revision;变化时拒绝创建新轮次
|
||||
skillIds:
|
||||
type: array
|
||||
maxItems: 8
|
||||
|
||||
Reference in New Issue
Block a user