Download internal/upstream/headers.go from a3216/gcli2api: direct link, hf CLI and curl.
- Browser
- Download file 18.9 kB
-
https://huggingface.co/spaces/a3216/gcli2api/resolve/main/internal/upstream/headers.go
- Command line
-
hf download hf://spaces/a3216/gcli2api/internal/upstream/headers.go
-
curl -L -o headers.go https://huggingface.co/spaces/a3216/gcli2api/resolve/main/internal/upstream/headers.go
18.9 kB
| // Package headers 构造三类上游请求头(common / chat / billing / refresh)。 | |
| // 规则来自 docs/api-reference.md §0/§4/§6。 | |
| package upstream | |
| import ( | |
| "crypto/sha256" | |
| "encoding/hex" | |
| "net/http" | |
| "strings" | |
| "github.com/linguo2625469/workbuddy2api-panel/internal/auth" | |
| "github.com/linguo2625469/workbuddy2api-panel/internal/session" | |
| ) | |
| const ( | |
| // defaultClientVersion 出站 WorkBuddy 客户端版本段(UA 的 `WorkBuddy/<ver>` 与 | |
| // 白名单头组的 X-IDE-Version)。对齐官方 WorkBuddy Desktop 分发包版本(5.5.4)。 | |
| // config upstream.client_version 可覆盖(空 = 内置默认)。 | |
| defaultClientVersion = "5.5.4" | |
| // defaultCliVersion 出站 UA 中 `CLI/<ver>` 段版本。对齐官方内置 CLI(2.137.1)。 | |
| // config upstream.cli_version 可覆盖(空 = 内置默认)。 | |
| defaultCliVersion = "2.137.1" | |
| originRefererCN = "https://www.codebuddy.cn" | |
| originRefererGlobal = "https://www.workbuddy.ai" | |
| ) | |
| // originRefererFor 按账号 realm 返回 Origin/Referer 基础域: | |
| // global → https://www.workbuddy.ai;cn(含全局开关未开)→ https://www.codebuddy.cn。 | |
| func originRefererFor(a *auth.Auth) string { | |
| if a != nil && a.IsGlobal() { | |
| return originRefererGlobal | |
| } | |
| return originRefererCN | |
| } | |
| // clientVersion 生效的 WorkBuddy 客户端版本:Client.ClientVersion 非空则取之, | |
| // 否则内置默认 defaultClientVersion。 | |
| func (c *Client) clientVersion() string { | |
| if c != nil && c.ClientVersion != "" { | |
| return c.ClientVersion | |
| } | |
| return defaultClientVersion | |
| } | |
| // cliVersion 生效的 CLI 版本:Client.CliVersion 非空则取之,否则内置默认 defaultCliVersion。 | |
| func (c *Client) cliVersion() string { | |
| if c != nil && c.CliVersion != "" { | |
| return c.CliVersion | |
| } | |
| return defaultCliVersion | |
| } | |
| // defaultWorkBuddyUAFor 组装默认客户端出站 UA(官方桌面端 RestOperations 层形状): | |
| // `WorkBuddy/<clientVersion> <platform>/<clientVersion> CLI/<cliVersion>`。 | |
| // 平台段(第二段)品牌按 realm 切换——CN 用 applicationName 同值 `WorkBuddy`, | |
| // global 用官方国际版 productName `WorkBuddy AI`(intl 项目逆向证据: | |
| // `WorkBuddy/5.5.2 WorkBuddy AI/5.5.2 CLI/5.5.2`)。 | |
| // global 账号送错平台段(`WorkBuddy` 非 `WorkBuddy AI`)可能触发上游 403 code 11140 | |
| // "request illegal" 风控。官方无任何 UA 随机化,故默认确定性。 | |
| func (c *Client) defaultWorkBuddyUAFor(a *auth.Auth) string { | |
| platform := "WorkBuddy" | |
| if a != nil && a.IsGlobal() { | |
| platform = "WorkBuddy AI" | |
| } | |
| return "WorkBuddy/" + c.clientVersion() + " " + platform + "/" + c.clientVersion() + " CLI/" + c.cliVersion() | |
| } | |
| // defaultWorkBuddyUA 返回 CN 形态的默认 UA(默认账号形态即 CN,零回归兼容既有调用/测试)。 | |
| func (c *Client) defaultWorkBuddyUA() string { | |
| return c.defaultWorkBuddyUAFor(nil) | |
| } | |
| // userAgent 返回当前出站 UA(客户端出站路径:chat/refresh/FetchModels)。 | |
| // 优先级:Client.UserAgent(config user_agent)显式覆盖 > 按账号 realm 的默认 WorkBuddy 三段式。 | |
| // 显式覆盖兼容既有覆盖逻辑:用户配了即以用户值为准(自定义品牌/版本), | |
| // 未配则走官方桌面端默认形态(global 换 `WorkBuddy AI` 平台段)。 | |
| func (c *Client) userAgent(a *auth.Auth) string { | |
| if c != nil && c.UserAgent != "" { | |
| return c.UserAgent | |
| } | |
| return c.defaultWorkBuddyUAFor(a) | |
| } | |
| // billingUA 白名单类(billing/checkin/banner)出站 UA:单段 `WorkBuddy/<clientVersion>` | |
| // (官方 banner 显式覆写形态,不带 CLI 段)。默认生效(伪造官方桌面端指纹); | |
| // 显式 client_name="SaaS" 才不设 UA(还原旧行为,Go 默认 UA)。 | |
| func (c *Client) billingUA() string { | |
| if c == nil || c.attributionClientName() == "SaaS" { | |
| return "" | |
| } | |
| return "WorkBuddy/" + c.clientVersion() | |
| } | |
| // resolveDeviceToken 解析本次请求的 X-Device-Token 取值。 | |
| // 优先级:auth.Auth.DeviceToken(每号)> Client.DeviceToken(config 全局)> 文件兜底。 | |
| // 三者皆空/读失败则返回空串(调用方不注入该头,优雅降级)。 | |
| func (c *Client) resolveDeviceToken(a *auth.Auth) string { | |
| if a != nil && a.DeviceToken != "" { | |
| return a.DeviceToken | |
| } | |
| if c != nil && c.DeviceToken != "" { | |
| return c.DeviceToken | |
| } | |
| if c != nil && c.DeviceTokenFile != "" { | |
| return readDeviceTokenFile(c.DeviceTokenFile) | |
| } | |
| return "" | |
| } | |
| // injectDeviceToken 在 req 注入 X-Device-Token 头(仅当取到非空 token)。 | |
| func (c *Client) injectDeviceToken(req *http.Request, a *auth.Auth) { | |
| if tok := c.resolveDeviceToken(a); tok != "" { | |
| req.Header.Set("X-Device-Token", tok) | |
| } | |
| } | |
| // deriveAccountStableID 按 uid + 用途盐稳定派生 36 hex 设备/会话标识。 | |
| // 跨重启稳定(固定盐 "wb2a:",不随进程换——这是与 session 包派生盐的本质差异: | |
| // 那是会话键维度的进程级随机盐,重启换新;本函数是账号维度,必须跨重启恒定)、 | |
| // 账号间互异(uid 不同则不同)、同 uid 同用途恒同值(幂等)。用 sha256 与项目 | |
| // 既有派生(session/ids.go、cache_key.go)保持一致;截 36 hex 提供更长熵。 | |
| // | |
| // 两个用途: | |
| // - purpose="machine" → X-Machine-ID(设备级,跨会话稳定) | |
| // - purpose="session" → X-Session-ID(账号固定会话,跨重启稳定) | |
| // | |
| // 与 injectDeviceToken 的 X-Device-Token 并存不冲突:那是登录时上游签发的 | |
| // 真实设备令牌(有则发,权威);本对头是「每账号一台固定虚拟设备」的稳定指纹, | |
| // 防多号被上游按设备指纹缺失/漂移关联风控。两者是不同头族,官方桌面端都发。 | |
| func deriveAccountStableID(uid, purpose string) string { | |
| sum := sha256.Sum256([]byte("wb2a:" + purpose + ":" + uid)) | |
| return hex.EncodeToString(sum[:18]) // 36 hex chars | |
| } | |
| // injectAccountStableHeaders 在 req 注入 X-Machine-ID / X-Session-ID:按 uid 稳定 | |
| // 派生,跨重启固定、账号间互异。uid 为空时不注入(匿名请求无设备标识,上游不要求)。 | |
| func (c *Client) injectAccountStableHeaders(req *http.Request, a *auth.Auth) { | |
| if a == nil || a.UID == "" { | |
| return | |
| } | |
| req.Header.Set("X-Machine-ID", deriveAccountStableID(a.UID, "machine")) | |
| req.Header.Set("X-Session-ID", deriveAccountStableID(a.UID, "session")) | |
| } | |
| // CommonHeaders 设置所有 API 共享的请求头。 | |
| func (c *Client) CommonHeaders(req *http.Request, a *auth.Auth) { | |
| req.Header.Set("Content-Type", "application/json") | |
| // Accept 非流式默认 application/json(D6:去掉宽松的 text/plain, */*)。 | |
| // chat 流式路径在 ChatHeaders 覆盖为 event-stream。 | |
| req.Header.Set("Accept", "application/json") | |
| req.Header.Set("X-Requested-With", "XMLHttpRequest") | |
| origin := originRefererFor(a) | |
| req.Header.Set("Origin", origin) | |
| req.Header.Set("Referer", origin+"/") | |
| req.Header.Set("User-Agent", c.userAgent(a)) | |
| // X-CodeBuddy-Request: 1(官方客户端风控闸门头,所有 API 请求必带,D1)。 | |
| req.Header.Set("X-CodeBuddy-Request", "1") | |
| // Accept-Language 按 realm 切(D5):CN zh-CN,global en-US。官方客户端按账号域 | |
| // 发对应语言标识,对齐避免上游风控按语言缺失误判。 | |
| req.Header.Set("Accept-Language", acceptLanguageFor(a)) | |
| // X-Machine-ID / X-Session-ID:按 uid 稳定派生的账号级设备头(见 | |
| // injectAccountStableHeaders)。注入在 CommonHeaders——chat 经 ChatHeaders | |
| // 叠加 CommonHeaders 天然继承;billing 域另行注入,全出站覆盖。 | |
| c.injectAccountStableHeaders(req, a) | |
| } | |
| // acceptLanguageFor 按账号 realm 返回 Accept-Language:global → en-US,cn → zh-CN。 | |
| func acceptLanguageFor(a *auth.Auth) string { | |
| if a != nil && a.IsGlobal() { | |
| return "en-US" | |
| } | |
| return "zh-CN" | |
| } | |
| // injectCodeBuddyRequest 在 req 注入 X-CodeBuddy-Request: 1。 | |
| // billing 域未走 CommonHeaders,单独注入保证全出站覆盖(D1)。 | |
| func (c *Client) injectCodeBuddyRequest(req *http.Request) { | |
| req.Header.Set("X-CodeBuddy-Request", "1") | |
| } | |
| // ChatMeta 一次 chat 出站的会话头族元数据(issue #35:后台按 X-Conversation-Request-ID | |
| // 聚合请求,官方客户端一次 user send 内所有 tool call/重试/换号复用同一个 ID)。 | |
| // handler 在轮转循环外生成 conversationID / conversationRequestID,循环内每次出站 | |
| // 复用同值;TraceID 透传入站值(空 = 回落 conversationRequestID)。 | |
| // messageID(消息级,每条独立)由 ChatHeaders 内部生成,无需外部可见。 | |
| type ChatMeta struct { | |
| ConversationID string // X-Conversation-ID:body 提取的入站值,空则不发(透传优先,不伪造) | |
| ConversationRequestID string // X-Conversation-Request-ID / X-Root-Request-ID:聚合主键,必发 | |
| TraceID string // X-Trace-ID:入站透传值,空则回落 conversationRequestID | |
| } | |
| // ChatHeaders 在 common 之上加 chat 专属的账号头。 | |
| // 缺省字段用 X-No-* 约定(与 CodeBuddy 官方 CLI 一致)。 | |
| // clientIP 为本次请求的客户端 IP(按参数传递,不读共享字段——避免并发串扰); | |
| // PassthroughIP=false 或 clientIP 为空时不注入 IP 头。 | |
| // meta 为会话头族元数据(纯新增,不改既有头),见 injectConversationHeaders。 | |
| func (c *Client) ChatHeaders(req *http.Request, a *auth.Auth, clientIP string, meta ChatMeta) { | |
| c.CommonHeaders(req, a) | |
| // chat 流式 Accept 覆盖 CommonHeaders 的非流式默认(D6)。 | |
| req.Header.Set("Accept", "application/json, text/event-stream") | |
| // AccessToken 加锁快照:keepalive 定时刷新会在 a.mu 内改写它,锁外直读构成数据竞争 | |
| // (见 auth.AccessTokenValue 注释)。 | |
| if at := a.AccessTokenValue(); at != "" { | |
| req.Header.Set("Authorization", "Bearer "+at) | |
| } else { | |
| req.Header.Set("X-No-Authorization", "1") | |
| } | |
| if a.UID != "" { | |
| req.Header.Set("X-User-Id", a.UID) | |
| } else { | |
| req.Header.Set("X-No-User-Id", "1") | |
| } | |
| // 安全红线:绝不在 chat 请求里携带 X-Refresh-Token。 | |
| // 企业与域头按 realm 分发:CN 走既有分支(EnterpriseID/Domain 原样透传,缺省 X-No-*); | |
| // global 账号由 injectGlobalChatHeaders 统一覆写为国际客户端形态 | |
| // (X-No-Enterprise-Id=1 声明无企业 + X-Domain=www.workbuddy.ai 声明国际版域), | |
| // 且不回退 X-Domain 到登录会话原值——对齐 intl 项目出站头。 | |
| if a != nil && !a.IsGlobal() { | |
| if a.EnterpriseID != "" { | |
| req.Header.Set("X-Enterprise-Id", a.EnterpriseID) | |
| } else { | |
| req.Header.Set("X-No-Enterprise-Id", "1") | |
| } | |
| if d := a.DomainValue(); d != "" { | |
| req.Header.Set("X-Domain", d) | |
| } else { | |
| req.Header.Set("X-No-Department-Info", "1") | |
| } | |
| } else { | |
| c.injectGlobalChatHeaders(req, a) | |
| } | |
| // 用量归属头:默认伪造 WorkBuddy 桌面端指纹(client_name="SaaS" 还原旧行为)。 | |
| c.injectAttribution(req) | |
| // 客户端 IP 透传(仅 PassthroughIP=true 且本次请求带 IP)。 | |
| c.injectClientIP(req, clientIP) | |
| // 设备风控头:auth 每号 > config 全局 > 文件兜底;空则不注入。 | |
| c.injectDeviceToken(req, a) | |
| // 会话头族(对话/请求/消息/B3 链路),见 injectConversationHeaders。 | |
| c.injectConversationHeaders(req, meta) | |
| } | |
| // injectGlobalChatHeaders global 账号(无企业 ID)的 chat 专属声明头,对齐 intl 项目 | |
| // (ANALYSIS-global-chat-solutions.md): | |
| // - X-No-Enterprise-Id: 1 个人账号无企业 ID,显式声明(避免上游按缺省/可疑判定) | |
| // - X-Domain: www.workbuddy.ai 显式声明国际版域(与 Origin/Referer 同域) | |
| // | |
| // 仅 global realm 注入;CN 账号走既有 X-No-Department-Info 等分支,零回归。 | |
| func (c *Client) injectGlobalChatHeaders(req *http.Request, a *auth.Auth) { | |
| if a == nil || !a.IsGlobal() { | |
| return | |
| } | |
| req.Header.Set("X-No-Enterprise-Id", "1") | |
| req.Header.Set("X-Domain", "www.workbuddy.ai") | |
| } | |
| // injectConversationHeaders 注入官方客户端会话头族(issue #35 后台聚合)。 | |
| // 头族分四层,各司其职: | |
| // - X-Conversation-ID:会话级,多轮稳定(body 的 conversationId)。空则不发—— | |
| // 透传客户端原值优先,客户端没给就不伪造,避免误导后台建错会话。 | |
| // - X-Conversation-Request-ID:**对话轮级聚合主键**,必发。一次 user send 内的 | |
| // 所有 tool call/重试/换号/降级复用同一个 → 后台按它聚合成一条(不再碎片化)。 | |
| // - X-Conversation-Message-ID = X-Request-ID:消息级,每条独立(32 位 hex)。 | |
| // - X-Root-Request-ID:= conversationRequestID(根请求追踪)。 | |
| // - X-Trace-ID:入站透传或 = conversationRequestID。 | |
| // - X-B3-TraceId / X-B3-SpanId / X-B3-Sampled:链路族。B3 规范只认 16/32 hex | |
| // TraceId 与 16 hex SpanId;入站 conversationRequestID 非法时 TraceId 回落 | |
| // messageID(恒 32 hex),SpanId 取 messageID[:16](每消息新)。 | |
| func (c *Client) injectConversationHeaders(req *http.Request, meta ChatMeta) { | |
| convReqID := meta.ConversationRequestID | |
| if convReqID == "" { | |
| // 零值 meta(直接调 ChatHeaders 的调用方/测试)也要保证聚合主键必发: | |
| // 本级补一个 32 hex,调用方(handler)已生成稳定的不走到这里。 | |
| convReqID = session.NewMessageID() | |
| } | |
| messageID := session.NewMessageID() | |
| if meta.ConversationID != "" { | |
| req.Header.Set("X-Conversation-ID", meta.ConversationID) | |
| } | |
| req.Header.Set("X-Conversation-Request-ID", convReqID) | |
| req.Header.Set("X-Conversation-Message-ID", messageID) | |
| req.Header.Set("X-Request-ID", messageID) | |
| req.Header.Set("X-Root-Request-ID", convReqID) | |
| traceID := meta.TraceID | |
| if traceID == "" { | |
| traceID = convReqID | |
| } | |
| req.Header.Set("X-Trace-ID", traceID) | |
| b3Trace := convReqID | |
| if !validTraceID(b3Trace) { | |
| b3Trace = messageID // 非法 B3 TraceId → 回落恒 32 hex 的消息级 ID | |
| } | |
| req.Header.Set("X-B3-TraceId", b3Trace) | |
| req.Header.Set("X-B3-SpanId", messageID[:16]) | |
| req.Header.Set("X-B3-Sampled", "1") | |
| } | |
| // validTraceID 判断 B3 TraceId 是否合法:16 或 32 位 hex(大小写均可)。 | |
| // 官方客户端生成的 conversationRequestId 是 32 位 hex(UUID 去横线),入站透传值 | |
| // 可能是任意形状(含横线/超长/非 hex),直接塞进 B3 头会破坏链路关联(issue #35)。 | |
| func validTraceID(s string) bool { | |
| if len(s) != 16 && len(s) != 32 { | |
| return false | |
| } | |
| for i := 0; i < len(s); i++ { | |
| ch := s[i] | |
| if !((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')) { | |
| return false | |
| } | |
| } | |
| return true | |
| } | |
| // attributionClientName 生效的用量归属名:ClientName 非空取之; | |
| // 空默认 "WorkBuddy"(伪造官方桌面端指纹;显式配 "SaaS" 可还原旧行为)。 | |
| func (c *Client) attributionClientName() string { | |
| if c != nil && c.ClientName != "" { | |
| return c.ClientName | |
| } | |
| return "WorkBuddy" | |
| } | |
| // injectAttribution 注入用量归属头(X-Agent-Purpose / X-IDE-* / X-Product)。 | |
| // 仅在 chat/completions 路径生效(ChatHeaders 调用)。 | |
| // | |
| // 默认(ClientName 空)即对齐官方 WorkBuddy 桌面端指纹:X-Agent-Purpose="conversation" | |
| // + X-IDE-Name/Type/Product="WorkBuddy" + X-IDE-Version=client_version,上游用量归因 | |
| // 不再出现 client/agentPurpose 为空的「网关特征」。显式 ClientName="SaaS" 还原旧行为 | |
| // (仅 X-Product="SaaS",不设 X-IDE-*);配其他值则四头跟随该值。 | |
| func (c *Client) injectAttribution(req *http.Request) { | |
| name := c.attributionClientName() | |
| if name == "SaaS" { | |
| req.Header.Set("X-Product", "SaaS") | |
| return | |
| } | |
| req.Header.Set("X-Agent-Purpose", "conversation") | |
| req.Header.Set("X-IDE-Name", name) | |
| req.Header.Set("X-IDE-Type", name) | |
| req.Header.Set("X-IDE-Version", c.clientVersion()) | |
| req.Header.Set("X-Product", name) | |
| } | |
| // injectClientIP 在 PassthroughIP 开启时把 clientIP 参数透传给上游(三等价头)。 | |
| func (c *Client) injectClientIP(req *http.Request, clientIP string) { | |
| if c == nil || !c.PassthroughIP || clientIP == "" { | |
| return | |
| } | |
| req.Header.Set("X-Forwarded-For", clientIP) | |
| req.Header.Set("X-Real-IP", clientIP) | |
| req.Header.Set("X-Client-IP", clientIP) | |
| } | |
| // ExtractClientIP 从入站请求提取客户端 IP 首段(X-Forwarded-For 首段,回落 X-Real-IP)。 | |
| func ExtractClientIP(r *http.Request) string { | |
| if r == nil { | |
| return "" | |
| } | |
| if xff := r.Header.Get("X-Forwarded-For"); xff != "" { | |
| for i := 0; i < len(xff); i++ { | |
| if xff[i] == ',' { | |
| return strings.TrimSpace(xff[:i]) | |
| } | |
| } | |
| return strings.TrimSpace(xff) | |
| } | |
| if real := strings.TrimSpace(r.Header.Get("X-Real-IP")); real != "" { | |
| return real | |
| } | |
| return "" | |
| } | |
| // BillingHeaders billing 接口请求头。 | |
| // UA 语义:默认**不设置**(保持现状,Go 客户端自带默认 UA);仅当显式配置 | |
| // c.UserAgent 非空才覆盖——避免默认路径给 billing 引入新的 UA 指纹。 | |
| func (c *Client) BillingHeaders(req *http.Request, a *auth.Auth) { | |
| // AccessToken 加锁快照(同 ChatHeaders:keepalive 可在 a.mu 内改写)。 | |
| req.Header.Set("Authorization", "Bearer "+a.AccessTokenValue()) | |
| req.Header.Set("Accept", "application/json") | |
| req.Header.Set("Content-Type", "application/json") | |
| c.injectCodeBuddyRequest(req) | |
| // Accept-Language 按 realm 切(D5,billing 域未走 CommonHeaders,单独注入)。 | |
| req.Header.Set("Accept-Language", acceptLanguageFor(a)) | |
| if c != nil && c.UserAgent != "" { | |
| req.Header.Set("User-Agent", c.UserAgent) | |
| } else if ua := c.billingUA(); ua != "" { | |
| req.Header.Set("User-Agent", ua) | |
| } | |
| if a.UID != "" { | |
| req.Header.Set("X-User-Id", a.UID) | |
| } | |
| if a.EnterpriseID != "" { | |
| req.Header.Set("X-Enterprise-Id", a.EnterpriseID) | |
| req.Header.Set("X-Tenant-Id", a.EnterpriseID) | |
| } | |
| if d := a.DomainValue(); d != "" { | |
| req.Header.Set("X-Domain", d) | |
| } | |
| // 设备风控头:billing 域(report/travel/balance/checkin)同样注入。 | |
| c.injectDeviceToken(req, a) | |
| } | |
| // RefreshHeaders refresh 端点专属头(X-Refresh-Token 只允许出现在这里)。 | |
| func (c *Client) RefreshHeaders(req *http.Request, a *auth.Auth) { | |
| c.CommonHeaders(req, a) | |
| req.Header.Set("X-Refresh-Token", a.RefreshToken) | |
| if a.EnterpriseID != "" { | |
| req.Header.Set("X-Enterprise-Id", a.EnterpriseID) | |
| } | |
| // X-Auth-Refresh-Source 对齐官方客户端 refresh 渠道标识 "plugin"(D3)。 | |
| req.Header.Set("X-Auth-Refresh-Source", "plugin") | |
| } | |