engine_fingerprint_signal.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. package openai
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "strings"
  7. "github.com/tidwall/gjson"
  8. )
  9. // EngineFingerprintSignal 描述引擎指纹统一列表的一条信号。
  10. // Required=true(勾选)= 该信号必须命中;多条 Required 之间 AND。
  11. // Match 为同一信号的等价写法/变体,行内 OR(命中任一即算该条满足)。
  12. type EngineFingerprintSignal struct {
  13. Type string `json:"type"` // header_exact | header_prefix | body_path
  14. Match []string `json:"match"` // 行内 OR 变体
  15. Required bool `json:"required"` // 勾选=true
  16. }
  17. const (
  18. FingerprintSignalHeaderExact = "header_exact"
  19. FingerprintSignalHeaderPrefix = "header_prefix"
  20. FingerprintSignalBodyPath = "body_path"
  21. )
  22. // DefaultEngineFingerprintSignals 默认种子:只勾 x-codex- 前缀,其余预填不勾。
  23. // 依据:实测真 codex(含旧版)约 98.8% 必带 x-codex-window-id 头。
  24. var DefaultEngineFingerprintSignals = []EngineFingerprintSignal{
  25. {Type: FingerprintSignalHeaderPrefix, Match: []string{"x-codex-"}, Required: true},
  26. {Type: FingerprintSignalHeaderExact, Match: []string{"session-id", "session_id"}, Required: false},
  27. {Type: FingerprintSignalHeaderExact, Match: []string{"thread-id", "thread_id"}, Required: false},
  28. {Type: FingerprintSignalBodyPath, Match: []string{"client_metadata.x-codex-window-id", "client_metadata.x-codex-installation-id"}, Required: false},
  29. }
  30. // EvaluateEngineFingerprint 应用「勾选 AND / 行内变体 OR」规则。
  31. // 只有 Required=true 的条目参与;全部命中→true;任一缺失→false;无任何 Required→true。
  32. func EvaluateEngineFingerprint(h http.Header, body []byte, signals []EngineFingerprintSignal) bool {
  33. for _, s := range signals {
  34. if !s.Required {
  35. continue
  36. }
  37. if !engineSignalMatches(h, body, s) {
  38. return false
  39. }
  40. }
  41. return true
  42. }
  43. func engineSignalMatches(h http.Header, body []byte, s EngineFingerprintSignal) bool {
  44. switch s.Type {
  45. case FingerprintSignalHeaderExact:
  46. for _, name := range s.Match {
  47. if n := strings.TrimSpace(name); n != "" && h != nil && strings.TrimSpace(h.Get(n)) != "" {
  48. return true
  49. }
  50. }
  51. case FingerprintSignalHeaderPrefix:
  52. if h == nil {
  53. return false
  54. }
  55. for k := range h {
  56. lk := strings.ToLower(k)
  57. for _, p := range s.Match {
  58. if np := strings.ToLower(strings.TrimSpace(p)); np != "" && strings.HasPrefix(lk, np) {
  59. return true
  60. }
  61. }
  62. }
  63. case FingerprintSignalBodyPath:
  64. if len(body) == 0 {
  65. return false
  66. }
  67. for _, path := range s.Match {
  68. if p := strings.TrimSpace(path); p != "" && gjson.GetBytes(body, p).Exists() {
  69. return true
  70. }
  71. }
  72. }
  73. return false
  74. }
  75. // ParseEngineFingerprintSignals 解析 JSON;空串→(nil,true);非法→(nil,false)。
  76. func ParseEngineFingerprintSignals(raw string) ([]EngineFingerprintSignal, bool) {
  77. if strings.TrimSpace(raw) == "" {
  78. return nil, true
  79. }
  80. var sigs []EngineFingerprintSignal
  81. if json.Unmarshal([]byte(raw), &sigs) != nil {
  82. return nil, false
  83. }
  84. return sigs, true
  85. }
  86. // ValidateEngineFingerprintSignalsJSON 校验:空=合法;非空须为合法数组,
  87. // 每条 type 合法且 match 至少一个非空项。供管理端写入校验复用。
  88. func ValidateEngineFingerprintSignalsJSON(raw string) error {
  89. trimmed := strings.TrimSpace(raw)
  90. if trimmed == "" {
  91. return nil
  92. }
  93. var sigs []EngineFingerprintSignal
  94. if err := json.Unmarshal([]byte(trimmed), &sigs); err != nil {
  95. return fmt.Errorf("must be empty or a valid JSON array of {type, match[], required}")
  96. }
  97. for i, s := range sigs {
  98. switch s.Type {
  99. case FingerprintSignalHeaderExact, FingerprintSignalHeaderPrefix, FingerprintSignalBodyPath:
  100. default:
  101. return fmt.Errorf("entry %d: type must be one of header_exact/header_prefix/body_path", i)
  102. }
  103. hasMatch := false
  104. for _, m := range s.Match {
  105. if strings.TrimSpace(m) != "" {
  106. hasMatch = true
  107. break
  108. }
  109. }
  110. if !hasMatch {
  111. return fmt.Errorf("entry %d: match must contain at least one non-empty value", i)
  112. }
  113. }
  114. return nil
  115. }
  116. // DefaultEngineFingerprintSignalsJSON 默认种子的 JSON 字符串。
  117. func DefaultEngineFingerprintSignalsJSON() string {
  118. b, _ := json.Marshal(DefaultEngineFingerprintSignals)
  119. return string(b)
  120. }