Skip to content

[Optimize/优化]: TraceAwareStrategy 支持 OpenCode 工具的会话亲和性字段 #1740

@w568w

Description

@w568w

Pre-submission Checklist / 提交前检查

  • I have searched the existing issues and this optimization has not been suggested / 我已搜索现有 issues,此优化建议尚未被提出
  • I have read the documentation / 我已阅读文档

FAQ Check / FAQ 检查

  • I have checked the FAQ and this optimization has not been suggested there / 我已查阅 FAQ,此优化建议未被提及

Current State / 当前状态

目前 AxonHub 在提取会话 ID 时,默认只考虑 AH-Trace-Id

// traceHeaderName returns the name of the header used for trace IDs.
func traceHeaderName(config tracing.Config) string {
if config.TraceHeader != "" {
return config.TraceHeader
}
return "AH-Trace-Id"
}
// getTraceIDFromHeader extracts the trace ID from the request headers.
func getTraceIDFromHeader(c *gin.Context, config tracing.Config) string {
traceID := c.GetHeader(traceHeaderName(config))
if traceID != "" {
return traceID
}
for _, header := range config.ExtraTraceHeaders {
traceID = c.GetHeader(header)
if traceID != "" {
return traceID
}
}
return ""
}
// tryGetTraceIDFromBody attempts to extract a trace ID from the request body
// based on the configured ExtraTraceBodyFields.
func tryGetTraceIDFromBody(c *gin.Context, config tracing.Config) (string, error) {
if len(config.ExtraTraceBodyFields) == 0 {
return "", nil
}
body, err := io.ReadAll(c.Request.Body)
if err != nil {
return "", fmt.Errorf("failed to read request body: %w", err)
}
c.Request.Body = io.NopCloser(bytes.NewReader(body))
if len(body) == 0 {
return "", nil
}
for _, field := range config.ExtraTraceBodyFields {
result := gjson.GetBytes(body, field)
if result.Exists() && result.String() != "" {
return result.String(), nil
}
}
return "", nil
}

或者特别适配了 Claude Code、Codex 的请求:

func tryExtractTraceIDFromClaudeCodeRequest(c *gin.Context, config tracing.Config) (string, error) {
if c.Request.Method != http.MethodPost {
return "", nil
}
path := c.Request.URL.Path
if path != "/anthropic/v1/messages" && path != "/v1/messages" {
return "", nil
}
bodyBytes, err := io.ReadAll(c.Request.Body)
if err != nil {
return "", fmt.Errorf("failed to read request body: %w", err)
}
c.Request.Body = io.NopCloser(bytes.NewReader(bodyBytes))
if len(bodyBytes) == 0 {
return "", nil
}
userID := gjson.GetBytes(bodyBytes, "metadata.user_id").String()
if userID == "" {
return "", nil
}
uid := claudecode.ParseUserID(userID)
if uid == nil {
return "", nil
}
traceID := uid.SessionID
log.Debug(c.Request.Context(), "Extracted trace ID from claude code payload", log.String("trace_id", traceID))
return traceID, nil
}
// tryExtractTraceIDFromCodexRequest extracts the trace ID from the Codex session header.
func tryExtractTraceIDFromCodexRequest(c *gin.Context) string {
traceID := codex.GetSessionIDFromHeaders(c.Request.Header)
if traceID == "" {
return ""
}
traceSource := "codex header"
if strings.TrimSpace(c.GetHeader(codex.SessionHeader)) == "" {
traceSource = "codex turn metadata"
}
log.Debug(c.Request.Context(), "Extracted trace ID from "+traceSource, log.String("trace_id", traceID))
return traceID
}

Problem or Bottleneck / 问题或瓶颈

但是,OpenCode 默认并不发送以上支持的三种类型的任何 Trace 字段。

这会导致缓存命中率大大下降,AxonHub 将每次请求都作为单独的会话,从而轮询请求(可以看到当存在 2 个健康后端时,缓存命中率降低到 100/2=50% 左右。实际数值略高于 50% 是因为我还有其他单后端的渠道模型):

Image

Proposed Optimization / 优化方案

OpenCode 会在 Header 发送两个相关字段来标识会话上下文:

https://github.com/anomalyco/opencode/blob/031f82adc89e254bed8bc7a3a88fde5c4066dc8b/packages/opencode/src/session/llm/request.ts#L177-L181

  • x-session-affinity:会话 ID,同一个会话使用相同的 ID;
  • x-parent-session-id:如果当前是 Subagent 请求,则包含父会话的 ID。

当前可以通过 server.trace.extra_trace_headers 配置来支持该字段,例如传入环境变量 AXONHUB_SERVER_TRACE_EXTRA_TRACE_HEADERS=["x-session-affinity"]。但鉴于中转站用户使用 OpenCode 的流行度较高,可能可以考虑默认支持该字段。

Expected Benefit / 期望收益

将能够正确识别 OpenCode 的会话列表,较大程度提高多渠道调度的缓存命中率。

Optimization Type / 优化类型

Performance / 性能

Affected Area / 影响区域

Channel Management / 渠道管理

Additional Context / 其他补充信息

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions