fix: 对齐 Claude Code 2.1.88 源码指纹

- 1P event_logging/batch 添加 OAuth Bearer auth header
- DD hostname 改为固定 "claude-code"(与真实 CLI 一致)
- 事件名对齐真实 CLI: tengu_api_query/tengu_api_success/tengu_api_error/tengu_tool_use_success
- DD header 大小写改为 DD-API-KEY
- ResponseHeaderTimeout 300s → 600s(与真实 CLI 10min 超时对齐)
This commit is contained in:
win 2026-04-01 08:53:39 +08:00
parent d8d8adb37f
commit 58ad47ba80
3 changed files with 32 additions and 27 deletions

View File

@ -322,7 +322,7 @@ func buildEvent(eventName string, session *sessionState, model, betas string, ex
var httpClient = &http.Client{Timeout: telemetryTimeout} var httpClient = &http.Client{Timeout: telemetryTimeout}
func sendTelemetryEvents(events []eventWrapper, session *sessionState) { func sendTelemetryEvents(events []eventWrapper, session *sessionState, authToken string) {
if len(events) == 0 { if len(events) == 0 {
return return
} }
@ -341,6 +341,9 @@ func sendTelemetryEvents(events []eventWrapper, session *sessionState) {
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", "claude-code/"+claude.DefaultCLIVersion) req.Header.Set("User-Agent", "claude-code/"+claude.DefaultCLIVersion)
req.Header.Set("x-service-name", "claude-code") req.Header.Set("x-service-name", "claude-code")
if authToken != "" {
req.Header.Set("Authorization", "Bearer "+authToken)
}
resp, err := httpClient.Do(req) resp, err := httpClient.Do(req)
if err != nil { if err != nil {
@ -383,7 +386,7 @@ func sendDatadogLog(eventName string, session *sessionState, model string) {
"ddtags": fmt.Sprintf("event:%s,arch:%s,client_type:cli,model:%s,platform:darwin,user_type:external,version:%s,version_base:%s", eventName, hostID.Arch, model, claude.DefaultCLIVersion, claude.DefaultCLIVersion), "ddtags": fmt.Sprintf("event:%s,arch:%s,client_type:cli,model:%s,platform:darwin,user_type:external,version:%s,version_base:%s", eventName, hostID.Arch, model, claude.DefaultCLIVersion, claude.DefaultCLIVersion),
"message": eventName, "message": eventName,
"service": "claude-code", "service": "claude-code",
"hostname": hostID.Hostname, "hostname": "claude-code",
"env": "external", "env": "external",
"model": model, "model": model,
"session_id": session.SessionID, "session_id": session.SessionID,
@ -415,7 +418,7 @@ func sendDatadogLog(eventName string, session *sessionState, model string) {
req.Header.Set("Accept", "application/json, text/plain, */*") req.Header.Set("Accept", "application/json, text/plain, */*")
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", "axios/1.13.6") req.Header.Set("User-Agent", "axios/1.13.6")
req.Header.Set("dd-api-key", ddAPIKey) req.Header.Set("DD-API-KEY", ddAPIKey)
resp, err := httpClient.Do(req) resp, err := httpClient.Do(req)
if err != nil { if err != nil {
@ -429,9 +432,10 @@ func sendDatadogLog(eventName string, session *sessionState, model string) {
// EmitPreRequest fires pre-request telemetry events for a /v1/messages request. // EmitPreRequest fires pre-request telemetry events for a /v1/messages request.
// accountSeed should be a stable identifier for the account (e.g. account ID or OAuth token suffix). // accountSeed should be a stable identifier for the account (e.g. account ID or OAuth token suffix).
// authHeader is the Authorization header value (used for device ID derivation). // authHeader is the Authorization header value (used for device ID derivation).
// authToken is the raw OAuth token (without "Bearer " prefix) for 1P auth.
// model is the model name from the request body (e.g. "claude-sonnet-4-6"). // model is the model name from the request body (e.g. "claude-sonnet-4-6").
// betaHeader is the anthropic-beta header value. // betaHeader is the anthropic-beta header value.
func EmitPreRequest(accountSeed, authHeader, model, betaHeader string) { func EmitPreRequest(accountSeed, authHeader, authToken, model, betaHeader string) {
authSuffix := authHeader authSuffix := authHeader
if len(authSuffix) > 16 { if len(authSuffix) > 16 {
authSuffix = authSuffix[len(authSuffix)-16:] authSuffix = authSuffix[len(authSuffix)-16:]
@ -479,29 +483,28 @@ func EmitPreRequest(accountSeed, authHeader, model, betaHeader string) {
} }
session.RipgrepReported = true session.RipgrepReported = true
go sendTelemetryEvents(batch1, session) go sendTelemetryEvents(batch1, session, authToken)
go sendDatadogLog("tengu_started", session, model) go sendDatadogLog("tengu_started", session, model)
go sendDatadogLog("tengu_init", session, model) go sendDatadogLog("tengu_init", session, model)
// Delayed batch (~25-35s later, matches real CLI timing) // Delayed batch (~25-35s later, matches real CLI timing)
go func() { go func() {
time.Sleep(time.Duration(25000+rand.Intn(10000)) * time.Millisecond) time.Sleep(time.Duration(25000+rand.Intn(10000)) * time.Millisecond)
batch2 := []eventWrapper{ sendTelemetryEvents([]eventWrapper{
buildEvent("tengu_session_init", session, model, betas, nil, ""), buildEvent("tengu_session_init", session, model, betas, nil, ""),
buildEvent("tengu_context_loaded", session, model, betas, nil, ""), buildEvent("tengu_context_loaded", session, model, betas, nil, ""),
} }, session, authToken)
sendTelemetryEvents(batch2, session)
}() }()
} }
// Every request: request_started // Every request: tengu_api_query (real CLI event name)
go sendTelemetryEvents([]eventWrapper{ go sendTelemetryEvents([]eventWrapper{
buildEvent("tengu_api_request_started", session, model, betas, nil, ""), buildEvent("tengu_api_query", session, model, betas, nil, ""),
}, session) }, session, authToken)
} }
// EmitPostRequest fires post-request telemetry events after upstream response. // EmitPostRequest fires post-request telemetry events after upstream response.
func EmitPostRequest(accountSeed, authHeader, model, betaHeader string, statusCode int) { func EmitPostRequest(accountSeed, authHeader, authToken, model, betaHeader string, statusCode int) {
authSuffix := authHeader authSuffix := authHeader
if len(authSuffix) > 16 { if len(authSuffix) > 16 {
authSuffix = authSuffix[len(authSuffix)-16:] authSuffix = authSuffix[len(authSuffix)-16:]
@ -517,15 +520,14 @@ func EmitPostRequest(accountSeed, authHeader, model, betaHeader string, statusCo
betas = claude.DefaultBetaHeader betas = claude.DefaultBetaHeader
} }
events := []eventWrapper{ // Real CLI uses tengu_api_success on success, tengu_api_error on failure
buildEvent("tengu_api_request_completed", session, model, betas, nil, ""), if statusCode < 400 {
buildEvent("tengu_conversation_turn_completed", session, model, betas, nil, ""), events := []eventWrapper{
} buildEvent("tengu_api_success", session, model, betas, nil, ""),
go sendTelemetryEvents(events, session) }
go sendDatadogLog("tengu_api_request_completed", session, model) go sendTelemetryEvents(events, session, authToken)
go sendDatadogLog("tengu_api_success", session, model)
// Error telemetry } else {
if statusCode >= 400 && rand.Float64() < 0.5 {
var errMsg string var errMsg string
switch { switch {
case statusCode == 429: case statusCode == 429:
@ -537,12 +539,13 @@ func EmitPostRequest(accountSeed, authHeader, model, betaHeader string, statusCo
default: default:
errMsg = "client_error" errMsg = "client_error"
} }
errEvent := buildEvent("tengu_api_request_error", session, model, betas, map[string]any{ errEvent := buildEvent("tengu_api_error", session, model, betas, map[string]any{
"error_type": "TelemetrySafeError", "error_type": "TelemetrySafeError",
"error_code": statusCode, "error_code": statusCode,
"error_message": errMsg, "error_message": errMsg,
}, "") }, "")
go sendTelemetryEvents([]eventWrapper{errEvent}, session) go sendTelemetryEvents([]eventWrapper{errEvent}, session, authToken)
go sendDatadogLog("tengu_api_error", session, model)
} }
// Random tool_use event (30% probability, 2-7s delay) // Random tool_use event (30% probability, 2-7s delay)
@ -550,8 +553,8 @@ func EmitPostRequest(accountSeed, authHeader, model, betaHeader string, statusCo
go func() { go func() {
time.Sleep(time.Duration(2000+rand.Intn(5000)) * time.Millisecond) time.Sleep(time.Duration(2000+rand.Intn(5000)) * time.Millisecond)
sendTelemetryEvents([]eventWrapper{ sendTelemetryEvents([]eventWrapper{
buildEvent("tengu_tool_use_completed", session, model, betas, nil, ""), buildEvent("tengu_tool_use_success", session, model, betas, nil, ""),
}, session) }, session, authToken)
}() }()
} }
} }

View File

@ -46,7 +46,7 @@ const (
defaultIdleConnTimeout = 90 * time.Second defaultIdleConnTimeout = 90 * time.Second
// defaultResponseHeaderTimeout: 默认等待响应头超时时间5分钟 // defaultResponseHeaderTimeout: 默认等待响应头超时时间5分钟
// LLM 请求可能排队较久,需要较长超时 // LLM 请求可能排队较久,需要较长超时
defaultResponseHeaderTimeout = 300 * time.Second defaultResponseHeaderTimeout = 600 * time.Second
// defaultMaxUpstreamClients: 默认最大客户端缓存数量 // defaultMaxUpstreamClients: 默认最大客户端缓存数量
// 超出后会淘汰最久未使用的客户端 // 超出后会淘汰最久未使用的客户端
defaultMaxUpstreamClients = 5000 defaultMaxUpstreamClients = 5000

View File

@ -4165,10 +4165,11 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A
// 真实 CLI 在首次 messages 请求前 fire-and-forget 调用此端点。 // 真实 CLI 在首次 messages 请求前 fire-and-forget 调用此端点。
if tokenType == "oauth" && token != "" { if tokenType == "oauth" && token != "" {
TriggerBootstrapIfNeeded(account.ID, token) TriggerBootstrapIfNeeded(account.ID, token)
// OTEL telemetry: emit pre-request events (tengu_started, tengu_api_request_started etc.) // OTEL telemetry: emit pre-request events (tengu_started, tengu_api_query etc.)
go telemetry.EmitPreRequest( go telemetry.EmitPreRequest(
fmt.Sprintf("%d", account.ID), fmt.Sprintf("%d", account.ID),
token, token,
token,
reqModel, reqModel,
getHeaderRaw(c.Request.Header, "anthropic-beta"), getHeaderRaw(c.Request.Header, "anthropic-beta"),
) )
@ -4594,6 +4595,7 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A
go telemetry.EmitPostRequest( go telemetry.EmitPostRequest(
fmt.Sprintf("%d", account.ID), fmt.Sprintf("%d", account.ID),
token, token,
token,
reqModel, reqModel,
getHeaderRaw(c.Request.Header, "anthropic-beta"), getHeaderRaw(c.Request.Header, "anthropic-beta"),
resp.StatusCode, resp.StatusCode,