feat(openai): cyber_policy 硬阻断全链路透传、审计与计费

上游对单次请求下发 error.code=cyber_policy 硬阻断时,网关在所有端点
(/v1/responses、/v1/chat/completions、/v1/messages、WebSocket)及流式/
非流式路径下,将该结果原样透传给客户端,绝不 failover、换号或同步拦截;
命中后异步完成审计与计费:

- 风控中心记录 cyber_policy 留痕并发送通知邮件,落库先于发信,SMTP 阻塞
  不影响留痕
- ops 错误请求记录,状态码对齐客户端实际接收(流式 200 / 非流式 400)
- 用量明细标记 request_type=cyber,按上游真实 token 计费,HTTP 与
  WebSocket 计费口径统一,零 token 命中不误扣
- 会话级自动屏蔽(管理员开关,默认关):命中的会话在可配 TTL 内本地拦截
  不再发往上游,仅屏蔽该会话不影响同 Key 其他会话
- 封号计数排除开关:可选让 cyber 命中不计入自动封号,命中当次不判定且
  历史行在违规计数中一并排除

WebSocket 多轮连接下 cyber 标记按 turn 生命周期管理,逐轮独立检测与记录;
透传的错误响应不被兜底逻辑追加内容污染。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
DaydreamCoding
2026-06-12 01:47:01 +08:00
co-authored by Claude Opus 4.8
parent e34ad2b194
commit b62b573f7f
56 changed files with 3036 additions and 184 deletions
@@ -233,7 +233,8 @@ const requestTypeOptions = ref<SelectOption[]>([
{ value: null, label: t('admin.usage.allTypes') },
{ value: 'ws_v2', label: t('usage.ws') },
{ value: 'stream', label: t('usage.stream') },
{ value: 'sync', label: t('usage.sync') }
{ value: 'sync', label: t('usage.sync') },
{ value: 'cyber', label: t('usage.cyber') }
])
const billingTypeOptions = ref<SelectOption[]>([
@@ -475,6 +475,7 @@ const tokenTooltipData = ref<AdminUsageLog | null>(null)
const getRequestTypeLabel = (row: AdminUsageLog): string => {
const requestType = resolveUsageRequestType(row)
if (requestType === 'cyber') return t('usage.cyber')
if (requestType === 'ws_v2') return t('usage.ws')
if (requestType === 'stream') return t('usage.stream')
if (requestType === 'sync') return t('usage.sync')
@@ -483,6 +484,7 @@ const getRequestTypeLabel = (row: AdminUsageLog): string => {
const getRequestTypeBadgeClass = (row: AdminUsageLog): string => {
const requestType = resolveUsageRequestType(row)
if (requestType === 'cyber') return 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'
if (requestType === 'ws_v2') return 'bg-violet-100 text-violet-800 dark:bg-violet-900 dark:text-violet-200'
if (requestType === 'stream') return 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200'
if (requestType === 'sync') return 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200'