Files
Cyrene_v2/internal/runtime/trace/errortoken.go
T
AskaEth 656c35f1e8
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
Merge branch 'feat/m51-lastexits-close-remaining-upstream-f' into dev
# Conflicts:
#	internal/runtime/trace/errortoken.go
2026-10-26 21:25:48 +08:00

63 lines
3.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package trace
import "strings"
// 本文件是「上游错误里的某个字段能不能出镜」的守卫之一:只放行「像协议枚举」的短串。
//
// 背景:UpstreamErrorInfo 只解析 error.code / error.type 两个字段,而它们是**自由
// 文本面** —— 主流上游里它们确实是枚举(invalid_request_error、overloaded_error、
// Throttling.RateQuota …),但网关、代理与兼容层可以往里面塞任何东西,包括回显的
// 用户原文。因此这两个字段出镜前必须像令牌:短、只含标识符字符。
//
// 丢弃的代价只是少一条分类提示;保留一个不受控字段的代价是她的话进日志。
//
// 🔴 M49 收口的是**应用点**,不是调用权:UpstreamError 构造时对 error.code /
// error.type 各守一次(构造即安全),构造后的注入走 SetCode / SetType(同一道守
// 卫)—— 这样一来「新调用点默认安全」,不必也不该在 model/voice 里各抄一份。
//
// 🔴 M51 纠正口径(2026-10-26,复审 p2 裁定):本文件旧注释写的是「包外不应出现
// ErrorToken 调用」。那句话把 M48 的真实教训(**调用方逐点补守卫**会漏)错译成了
// 「只有本包能守」,于是把「按**字段类别**判定」这条正确口径一并否掉,而现实里包外
// 一直有正当调用。正确口径按字段类别分:
//
// - **协议枚举字段** —— error.code / error.type、CDP `error.message` 里的协议错误
// 名、各类状态码:值域是协议定义的短标识符 ⇒ 用 ErrorToken 守。像枚举就保留,
// 散文(带空格、标点、超长)一律丢弃;
// - **自由文本字段** —— error.message、模型/对端「说的话」、页面异常文本:**默认
// 不带**;确实要带时按「带上限地带」处理(rune 上限 + RedactText,见
// channel/adapter 的 reviewExcerpt、services/channelbridge 的 judgeReason、trace.UpstreamError 的 WithUpstreamMessage 与 httpclient 的 WithEnvelopeMessage),
// 绝不拿 ErrorToken 当清洗(它只会把整段话删掉,不是清洗)。
//
// 包外调用的现存例子(都是枚举口径,属正当用法):
// - `internal/capability/browser/cdp.go` 的 cdpError.Error():CDP error.message
// 只有像协议枚举才出镜,其余只留字节数(M51);
// - `internal/runtime/multimodal/voice/dashscope_error.go`:DashScope 的 code/type
// (M46 起就在包外调用)。
//
// 因此现在的约束只有一条:**调用前先确认那个字段是枚举口径**;自由文本字段不许用
// 它冒充清洗,也不许为了「新调用点安全」而在本文件之外复制一份守卫实现 —— 守卫本身
// 仍然只有这一份。
// maxErrorTokenBytes 是 error.code / error.type / request_id 允许出镜的长度上限。
// 64 字节足够容纳任何真实的枚举与 UUID 形状,又远小于「一段话」。
const maxErrorTokenBytes = 64
// ErrorToken 只放行「像协议令牌」的短串(字母、数字与 `_ . -`),其余一律丢弃并返回
// 空串 —— 包括带空格、标点、引号的散文,以及超长串。调用方据此决定「这个字段出不
// 出镜」,而不是先出镜再清洗。
func ErrorToken(s string) string {
s = strings.TrimSpace(s)
if s == "" || len(s) > maxErrorTokenBytes {
return ""
}
for _, r := range s {
switch {
case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', r >= '0' && r <= '9':
case r == '_' || r == '.' || r == '-':
default:
return ""
}
}
return s
}