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 2f817dd248
commit b285fb7b2f
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}
func sendTelemetryEvents(events []eventWrapper, session *sessionState) {
func sendTelemetryEvents(events []eventWrapper, session *sessionState, authToken string) {
if len(events) == 0 {
return
}
@ -341,6 +341,9 @@ func sendTelemetryEvents(events []eventWrapper, session *sessionState) {
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", "claude-code/"+claude.DefaultCLIVersion)
req.Header.Set("x-service-name", "claude-code")
if authToken != "" {
req.Header.Set("Authorization", "Bearer "+authToken)
}
resp, err := httpClient.Do(req)
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),
"message": eventName,
"service": "claude-code",
"hostname": hostID.Hostname,
"hostname": "claude-code",
"env": "external",
"model": model,
"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("Content-Type", "application/json")
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)
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.
// 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).
// 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").
// betaHeader is the anthropic-beta header value.
func EmitPreRequest(accountSeed, authHeader, model, betaHeader string) {
func EmitPreRequest(accountSeed, authHeader, authToken, model, betaHeader string) {
authSuffix := authHeader
if len(authSuffix) > 16 {
authSuffix = authSuffix[len(authSuffix)-16:]
@ -479,29 +483,28 @@ func EmitPreRequest(accountSeed, authHeader, model, betaHeader string) {
}
session.RipgrepReported = true
go sendTelemetryEvents(batch1, session)
go sendTelemetryEvents(batch1, session, authToken)
go sendDatadogLog("tengu_started", session, model)
go sendDatadogLog("tengu_init", session, model)
// Delayed batch (~25-35s later, matches real CLI timing)
go func() {
time.Sleep(time.Duration(25000+rand.Intn(10000)) * time.Millisecond)
batch2 := []eventWrapper{
sendTelemetryEvents([]eventWrapper{
buildEvent("tengu_session_init", session, model, betas, nil, ""),
buildEvent("tengu_context_loaded", session, model, betas, nil, ""),
}
sendTelemetryEvents(batch2, session)
}, session, authToken)
}()
}
// Every request: request_started
// Every request: tengu_api_query (real CLI event name)
go sendTelemetryEvents([]eventWrapper{
buildEvent("tengu_api_request_started", session, model, betas, nil, ""),
}, session)
buildEvent("tengu_api_query", session, model, betas, nil, ""),
}, session, authToken)
}
// 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
if len(authSuffix) > 16 {
authSuffix = authSuffix[len(authSuffix)-16:]
@ -517,15 +520,14 @@ func EmitPostRequest(accountSeed, authHeader, model, betaHeader string, statusCo
betas = claude.DefaultBetaHeader
}
events := []eventWrapper{
buildEvent("tengu_api_request_completed", session, model, betas, nil, ""),
buildEvent("tengu_conversation_turn_completed", session, model, betas, nil, ""),
}
go sendTelemetryEvents(events, session)
go sendDatadogLog("tengu_api_request_completed", session, model)
// Error telemetry
if statusCode >= 400 && rand.Float64() < 0.5 {
// Real CLI uses tengu_api_success on success, tengu_api_error on failure
if statusCode < 400 {
events := []eventWrapper{
buildEvent("tengu_api_success", session, model, betas, nil, ""),
}
go sendTelemetryEvents(events, session, authToken)
go sendDatadogLog("tengu_api_success", session, model)
} else {
var errMsg string
switch {
case statusCode == 429:
@ -537,12 +539,13 @@ func EmitPostRequest(accountSeed, authHeader, model, betaHeader string, statusCo
default:
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_code": statusCode,
"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)
@ -550,8 +553,8 @@ func EmitPostRequest(accountSeed, authHeader, model, betaHeader string, statusCo
go func() {
time.Sleep(time.Duration(2000+rand.Intn(5000)) * time.Millisecond)
sendTelemetryEvents([]eventWrapper{
buildEvent("tengu_tool_use_completed", session, model, betas, nil, ""),
}, session)
buildEvent("tengu_tool_use_success", session, model, betas, nil, ""),
}, session, authToken)
}()
}
}

View File

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

View File

@ -4228,10 +4228,11 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A
// 真实 CLI 在首次 messages 请求前 fire-and-forget 调用此端点。
if tokenType == "oauth" && 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(
fmt.Sprintf("%d", account.ID),
token,
token,
reqModel,
getHeaderRaw(c.Request.Header, "anthropic-beta"),
)
@ -4657,6 +4658,7 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A
go telemetry.EmitPostRequest(
fmt.Sprintf("%d", account.ID),
token,
token,
reqModel,
getHeaderRaw(c.Request.Header, "anthropic-beta"),
resp.StatusCode,