0.10.1: 安全与业务逻辑加固、新品牌与部署加固
三轮审查修复(60+ 项),相对远端 main(b536672)的关键变更:
- 安全: 数据面 SSRF 拨号防护(防 DNS rebinding)/上游凭据剥离/登录防枚举
与锁定态统一/可信代理(X-Forwarded-For)限流加固/会话版本失效机制/
撤销即时传播/弱密钥拒绝启动/脱敏字节级重写(保签名契约)
- 业务逻辑: 裸 body 上传 panic/bootstrap 审计管线卡死/定价通配符优先级/
全局工具可见性/调度器停机补跑/TOTP 挑战令牌消费顺序/熔断探针语义/
>4MB 响应 token 计量/管理员重置密码作废会话 等
- 前端: 新 logo(语枢 AI 网关主题)/Provider 凭据异常警示/删除入口/
后端错误消息透传/localStorage 敏感数据收敛
- 部署: CREDENTIAL_MASTER_KEY 持久化与弱值拒绝/Provider DELETE 接口/
nginx 安全头/worker 内存限制
- 新增迁移 000029(key_hash 索引)/000030(usage_daily 币种维度)
This commit is contained in:
@@ -447,6 +447,145 @@ paths:
|
||||
responses:
|
||||
"200": { description: Daily request, failure, and token aggregates }
|
||||
"403": { description: usage:read permission required }
|
||||
/api/v1/admin/traces:
|
||||
get:
|
||||
operationId: listLLMTraces
|
||||
security: [{ bearerAuth: [] }]
|
||||
parameters:
|
||||
- { name: from, in: query, schema: { type: string, format: date-time } }
|
||||
- { name: to, in: query, schema: { type: string, format: date-time } }
|
||||
- { name: trace_type, in: query, schema: { type: string, enum: [application, digital_employee] } }
|
||||
- { name: target_code, in: query, schema: { type: string } }
|
||||
- { name: request_id, in: query, schema: { type: string } }
|
||||
- { name: status, in: query, schema: { type: string, enum: [running, success, error] } }
|
||||
- { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 200, default: 50 } }
|
||||
responses:
|
||||
"200": { description: Metadata-only LLM Trace summaries }
|
||||
"403": { description: trace:read permission required }
|
||||
/api/v1/admin/traces/{trace_id}:
|
||||
get:
|
||||
operationId: getLLMTrace
|
||||
security: [{ bearerAuth: [] }]
|
||||
parameters: [{ name: trace_id, in: path, required: true, schema: { type: string, format: uuid } }]
|
||||
responses:
|
||||
"200":
|
||||
description: Trace detail with ordered model, retrieval, and tool spans
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/LLMTrace" }
|
||||
"403": { description: trace:read permission required }
|
||||
"404": { description: Trace not found }
|
||||
/api/v1/admin/agent-sessions:
|
||||
get:
|
||||
operationId: listAgentSessions
|
||||
security: [{ bearerAuth: [] }]
|
||||
parameters:
|
||||
- { name: from, in: query, schema: { type: string, format: date-time } }
|
||||
- { name: to, in: query, schema: { type: string, format: date-time } }
|
||||
- { name: trace_type, in: query, schema: { type: string, enum: [application, digital_employee] } }
|
||||
- { name: target_code, in: query, schema: { type: string, maxLength: 128 } }
|
||||
- { name: session_id, in: query, schema: { type: string, maxLength: 512 } }
|
||||
- { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 200, default: 50 } }
|
||||
responses:
|
||||
"200":
|
||||
description: Metadata-only application and digital-employee session aggregates
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [code, data]
|
||||
properties:
|
||||
code: { type: integer }
|
||||
data:
|
||||
type: object
|
||||
required: [items]
|
||||
properties:
|
||||
items: { type: array, items: { $ref: "#/components/schemas/AgentSession" } }
|
||||
"403": { description: trace:read permission required }
|
||||
/api/v1/admin/agent-nodes:
|
||||
get:
|
||||
operationId: listAgentNodes
|
||||
security: [{ bearerAuth: [] }]
|
||||
responses:
|
||||
"200":
|
||||
description: Registered agent nodes with derived heartbeat status
|
||||
content:
|
||||
application/json:
|
||||
schema: { type: array, items: { $ref: "#/components/schemas/AgentNode" } }
|
||||
"403": { description: agent_node:read permission required }
|
||||
post:
|
||||
operationId: createAgentNode
|
||||
security: [{ bearerAuth: [] }]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/AgentNodeInput" }
|
||||
responses:
|
||||
"200": { description: Node metadata and one-time registration token }
|
||||
"403": { description: agent_node:manage permission required }
|
||||
"409": { description: Node code already exists }
|
||||
/api/v1/admin/agent-nodes/route-preview:
|
||||
post:
|
||||
operationId: previewAgentNodeRoute
|
||||
security: [{ bearerAuth: [] }]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/AgentNodeRoutePreviewInput" }
|
||||
responses:
|
||||
"200":
|
||||
description: Read-only online candidate list and stable-hash selection; no remote task is dispatched
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/AgentNodeRoutePreview" }
|
||||
"400": { description: Invalid pool, request key or capability constraints }
|
||||
"403": { description: agent_node:read permission required }
|
||||
/api/v1/admin/agent-nodes/{node_id}:
|
||||
put:
|
||||
operationId: updateAgentNode
|
||||
security: [{ bearerAuth: [] }]
|
||||
parameters: [{ name: node_id, in: path, required: true, schema: { type: string, format: uuid } }]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/AgentNodeInput" }
|
||||
responses:
|
||||
"200": { description: Updated node metadata }
|
||||
"403": { description: agent_node:manage permission required }
|
||||
"404": { description: Node not found }
|
||||
delete:
|
||||
operationId: deleteAgentNode
|
||||
security: [{ bearerAuth: [] }]
|
||||
parameters: [{ name: node_id, in: path, required: true, schema: { type: string, format: uuid } }]
|
||||
responses:
|
||||
"200": { description: Node removed and its token invalidated }
|
||||
"403": { description: agent_node:manage permission required }
|
||||
"404": { description: Node not found }
|
||||
/api/v1/admin/agent-nodes/{node_id}/rotate-token:
|
||||
post:
|
||||
operationId: rotateAgentNodeToken
|
||||
security: [{ bearerAuth: [] }]
|
||||
parameters: [{ name: node_id, in: path, required: true, schema: { type: string, format: uuid } }]
|
||||
responses:
|
||||
"200": { description: New one-time registration token; previous token is immediately invalid }
|
||||
"403": { description: agent_node:manage permission required }
|
||||
"404": { description: Node not found }
|
||||
/api/v1/agent/nodes/{code}/heartbeat:
|
||||
post:
|
||||
operationId: heartbeatAgentNode
|
||||
security: [{ agentTokenAuth: [] }]
|
||||
parameters: [{ name: code, in: path, required: true, schema: { type: string, pattern: "^[a-z0-9][a-z0-9._-]{0,127}$" } }]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/AgentNodeHeartbeat" }
|
||||
responses:
|
||||
"200": { description: Heartbeat accepted and derived online status returned }
|
||||
"401": { description: Invalid or disabled node token }
|
||||
/api/v1/admin/outbox-events:
|
||||
get:
|
||||
operationId: listOutboxEvents
|
||||
@@ -1164,6 +1303,65 @@ paths:
|
||||
operationId: listAdministrativeModelRequests
|
||||
security: [{ bearerAuth: [] }]
|
||||
responses: { "200": { description: Portal model access requests by status } }
|
||||
/api/v1/admin/scheduled-tasks:
|
||||
get:
|
||||
operationId: listScheduledTasks
|
||||
security: [{ bearerAuth: [] }]
|
||||
responses: { "200": { description: Scheduled task definitions with next and last execution state } }
|
||||
post:
|
||||
operationId: createScheduledTask
|
||||
security: [{ bearerAuth: [] }]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/ScheduledTaskInput" }
|
||||
responses: { "200": { description: Task created with KEK-encrypted execution credential } }
|
||||
/api/v1/admin/scheduled-tasks/{task_id}:
|
||||
get:
|
||||
operationId: getScheduledTask
|
||||
security: [{ bearerAuth: [] }]
|
||||
parameters: [{ name: task_id, in: path, required: true, schema: { type: string, format: uuid } }]
|
||||
responses: { "200": { description: Task detail without plaintext credentials } }
|
||||
put:
|
||||
operationId: updateScheduledTask
|
||||
security: [{ bearerAuth: [] }]
|
||||
parameters: [{ name: task_id, in: path, required: true, schema: { type: string, format: uuid } }]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/ScheduledTaskInput" }
|
||||
responses: { "200": { description: Task updated and next execution recomputed } }
|
||||
delete:
|
||||
operationId: deleteScheduledTask
|
||||
security: [{ bearerAuth: [] }]
|
||||
parameters: [{ name: task_id, in: path, required: true, schema: { type: string, format: uuid } }]
|
||||
responses: { "200": { description: Task and execution history deleted } }
|
||||
/api/v1/admin/scheduled-tasks/{task_id}/start:
|
||||
post:
|
||||
operationId: startScheduledTask
|
||||
security: [{ bearerAuth: [] }]
|
||||
parameters: [{ name: task_id, in: path, required: true, schema: { type: string, format: uuid } }]
|
||||
responses: { "200": { description: Task enabled and next execution computed } }
|
||||
/api/v1/admin/scheduled-tasks/{task_id}/pause:
|
||||
post:
|
||||
operationId: pauseScheduledTask
|
||||
security: [{ bearerAuth: [] }]
|
||||
parameters: [{ name: task_id, in: path, required: true, schema: { type: string, format: uuid } }]
|
||||
responses: { "200": { description: Task paused without deleting history } }
|
||||
/api/v1/admin/scheduled-tasks/{task_id}/run:
|
||||
post:
|
||||
operationId: runScheduledTaskNow
|
||||
security: [{ bearerAuth: [] }]
|
||||
parameters: [{ name: task_id, in: path, required: true, schema: { type: string, format: uuid } }]
|
||||
responses: { "200": { description: Manual execution durably queued } }
|
||||
/api/v1/admin/scheduled-tasks/{task_id}/runs:
|
||||
get:
|
||||
operationId: listScheduledTaskRuns
|
||||
security: [{ bearerAuth: [] }]
|
||||
parameters: [{ name: task_id, in: path, required: true, schema: { type: string, format: uuid } }]
|
||||
responses: { "200": { description: Execution history with status, response and error } }
|
||||
/api/v1/admin/model-requests/{request_id}/approve:
|
||||
post:
|
||||
operationId: approveModelRequest
|
||||
@@ -1287,6 +1485,11 @@ components:
|
||||
type: http
|
||||
scheme: bearer
|
||||
description: Gateway API Key with gateway:invoke scope; X-Gateway-API-Key is also accepted.
|
||||
agentTokenAuth:
|
||||
type: apiKey
|
||||
in: header
|
||||
name: X-Agent-Token
|
||||
description: One-time-issued node registration token used for heartbeats.
|
||||
schemas:
|
||||
Health:
|
||||
type: object
|
||||
@@ -1625,3 +1828,156 @@ components:
|
||||
maxItems: 100
|
||||
items: { type: string, description: Exact event type or a prefix ending in one asterisk. }
|
||||
enabled: { type: boolean }
|
||||
ScheduledTaskInput:
|
||||
type: object
|
||||
required: [code, name, description, cron_expression, timezone, target_type, target_code, prompt, variables, skill_ids, mcp_server_ids, conversation_id, enabled]
|
||||
properties:
|
||||
code: { type: string, pattern: "^[a-z][a-z0-9_-]{1,63}$" }
|
||||
name: { type: string, minLength: 1, maxLength: 128 }
|
||||
description: { type: string, maxLength: 4000 }
|
||||
cron_expression: { type: string, maxLength: 128, description: Standard five-field cron expression. }
|
||||
timezone: { type: string, maxLength: 128, example: Asia/Shanghai }
|
||||
target_type: { type: string, enum: [application, digital_employee] }
|
||||
target_code: { type: string, maxLength: 64 }
|
||||
prompt: { type: string, minLength: 1, maxLength: 100000 }
|
||||
variables: { type: object, additionalProperties: true }
|
||||
skill_ids: { type: array, maxItems: 100, items: { type: string, format: uuid } }
|
||||
mcp_server_ids: { type: array, maxItems: 100, items: { type: string, format: uuid } }
|
||||
conversation_id: { type: string, maxLength: 128 }
|
||||
notification_channel_id: { type: [string, "null"], format: uuid }
|
||||
api_key:
|
||||
type: string
|
||||
writeOnly: true
|
||||
maxLength: 512
|
||||
description: Required on create; omit on update to retain the encrypted credential.
|
||||
enabled: { type: boolean }
|
||||
LLMTraceSpan:
|
||||
type: object
|
||||
required: [id, trace_id, span_type, name, status, started_at, input_tokens, output_tokens, round, error, metadata]
|
||||
properties:
|
||||
id: { type: string, format: uuid }
|
||||
trace_id: { type: string, format: uuid }
|
||||
parent_id: { type: string, format: uuid }
|
||||
span_type: { type: string, enum: [model, tool, retrieval] }
|
||||
name: { type: string }
|
||||
status: { type: string, enum: [running, success, error] }
|
||||
started_at: { type: string, format: date-time }
|
||||
finished_at: { type: string, format: date-time }
|
||||
latency_ms: { type: integer, minimum: 0 }
|
||||
provider_code: { type: string }
|
||||
model: { type: string }
|
||||
input_tokens: { type: integer, minimum: 0 }
|
||||
output_tokens: { type: integer, minimum: 0 }
|
||||
round: { type: integer, minimum: 0 }
|
||||
error: { type: string }
|
||||
metadata: { type: object, additionalProperties: true }
|
||||
LLMTrace:
|
||||
type: object
|
||||
required: [id, request_id, trace_type, target_code, conversation_id, status, started_at, retrieval_count, model_call_count, tool_call_count, error, metadata]
|
||||
properties:
|
||||
id: { type: string, format: uuid }
|
||||
request_id: { type: string }
|
||||
api_key_id: { type: string, format: uuid }
|
||||
tenant_id: { type: string, format: uuid }
|
||||
trace_type: { type: string, enum: [application, digital_employee] }
|
||||
target_id: { type: string, format: uuid }
|
||||
target_code: { type: string }
|
||||
conversation_id: { type: string }
|
||||
status: { type: string, enum: [running, success, error] }
|
||||
started_at: { type: string, format: date-time }
|
||||
finished_at: { type: string, format: date-time }
|
||||
latency_ms: { type: integer, minimum: 0 }
|
||||
retrieval_count: { type: integer, minimum: 0 }
|
||||
model_call_count: { type: integer, minimum: 0 }
|
||||
tool_call_count: { type: integer, minimum: 0 }
|
||||
error: { type: string }
|
||||
metadata: { type: object, additionalProperties: true }
|
||||
spans: { type: array, items: { $ref: "#/components/schemas/LLMTraceSpan" } }
|
||||
AgentSession:
|
||||
type: object
|
||||
required: [id, trace_type, target_code, trace_count, latest_trace_id, latest_status, started_at, updated_at, retrieval_count, model_call_count, tool_call_count]
|
||||
properties:
|
||||
id: { type: string, description: Session key; request-derived for stateless calls }
|
||||
trace_type: { type: string, enum: [application, digital_employee] }
|
||||
target_code: { type: string }
|
||||
trace_count: { type: integer, minimum: 1 }
|
||||
latest_trace_id: { type: string, format: uuid }
|
||||
latest_status: { type: string, enum: [running, success, error] }
|
||||
started_at: { type: string, format: date-time }
|
||||
updated_at: { type: string, format: date-time }
|
||||
retrieval_count: { type: integer, minimum: 0 }
|
||||
model_call_count: { type: integer, minimum: 0 }
|
||||
tool_call_count: { type: integer, minimum: 0 }
|
||||
AgentNodeInput:
|
||||
type: object
|
||||
required: [name, description, endpoint, node_type, pool_type, pool_code, enabled]
|
||||
properties:
|
||||
code: { type: string, pattern: "^[a-z0-9][a-z0-9._-]{0,127}$" }
|
||||
name: { type: string, minLength: 1, maxLength: 128 }
|
||||
description: { type: string, maxLength: 4000 }
|
||||
endpoint: { type: string, maxLength: 512 }
|
||||
node_type: { type: string, enum: [worker, gateway, executor] }
|
||||
pool_type: { type: string, enum: [public, private] }
|
||||
pool_code: { type: string, minLength: 1, maxLength: 64 }
|
||||
enabled: { type: boolean }
|
||||
AgentNodeRoutePreviewInput:
|
||||
type: object
|
||||
required: [pool_type, pool_code, request_key]
|
||||
properties:
|
||||
pool_type: { type: string, enum: [public, private] }
|
||||
pool_code: { type: string, minLength: 1, maxLength: 64 }
|
||||
required_capabilities: { type: array, maxItems: 32, items: { type: string, maxLength: 128 } }
|
||||
request_key: { type: string, minLength: 1, maxLength: 512 }
|
||||
AgentNodeHeartbeat:
|
||||
type: object
|
||||
required: [version, capabilities, metadata, error]
|
||||
properties:
|
||||
version: { type: string, maxLength: 128 }
|
||||
capabilities: { type: object, additionalProperties: true }
|
||||
metadata: { type: object, additionalProperties: true }
|
||||
error: { type: string, maxLength: 4000 }
|
||||
AgentNode:
|
||||
type: object
|
||||
required: [id, code, name, description, endpoint, node_type, pool_type, pool_code, enabled, status, token_prefix, version, capabilities, metadata, last_error, created_at, updated_at]
|
||||
properties:
|
||||
id: { type: string, format: uuid }
|
||||
code: { type: string }
|
||||
name: { type: string }
|
||||
description: { type: string }
|
||||
endpoint: { type: string }
|
||||
node_type: { type: string, enum: [worker, gateway, executor] }
|
||||
pool_type: { type: string, enum: [public, private] }
|
||||
pool_code: { type: string }
|
||||
enabled: { type: boolean }
|
||||
status: { type: string, enum: [pending, online, offline, disabled] }
|
||||
token_prefix: { type: string }
|
||||
version: { type: string }
|
||||
capabilities: { type: object, additionalProperties: true }
|
||||
metadata: { type: object, additionalProperties: true }
|
||||
last_heartbeat_at: { type: string, format: date-time }
|
||||
last_heartbeat_ip: { type: string }
|
||||
last_error: { type: string }
|
||||
created_at: { type: string, format: date-time }
|
||||
updated_at: { type: string, format: date-time }
|
||||
AgentNodeTokenResponse:
|
||||
type: object
|
||||
required: [node, token, warning]
|
||||
properties:
|
||||
node: { $ref: "#/components/schemas/AgentNode" }
|
||||
token: { type: string, writeOnly: true }
|
||||
warning: { type: string }
|
||||
AgentNodeRoutePreview:
|
||||
type: object
|
||||
required: [pool_type, pool_code, required_capabilities, request_key, selection_policy, reason, selected, candidates]
|
||||
properties:
|
||||
pool_type: { type: string, enum: [public, private] }
|
||||
pool_code: { type: string }
|
||||
required_capabilities: { type: array, items: { type: string } }
|
||||
request_key: { type: string }
|
||||
selection_policy: { type: string, example: stable-hash(request_key,node_id) }
|
||||
reason: { type: string, enum: [selected_online_node, no_online_node, no_capable_node] }
|
||||
selected:
|
||||
anyOf:
|
||||
- { $ref: "#/components/schemas/AgentNode" }
|
||||
- { type: 'null' }
|
||||
candidates: { type: array, items: { $ref: "#/components/schemas/AgentNode" } }
|
||||
|
||||
Reference in New Issue
Block a user