package helps import ( "context" "net/http" "regexp" "strings" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/tidwall/gjson") const ( ClaudeCodeSessionHeader = "X-Claude-Code-Session-Id" ClaudeCodeAgentHeader = "X-Claude-Code-Agent-Id" ClaudeCodeMainAgentID = "main") var claudeCodeSessionSuffixPattern = regexp.MustCompile(`_session_([a-f0-9-]+)$`) // ExtractClaudeCodeSessionID resolves a Claude Code session ID, preferring X-Claude-Code-Session-Id over payload metadata.func ExtractClaudeCodeSessionID(ctx context.Context, payload []byte, headers http.Header) string { if sessionID := claudeCodeHeader(ctx, headers, ClaudeCodeSessionHeader); sessionID != "" { return sessionID } return extractClaudeCodeSessionIDFromPayload(payload)} // ExtractClaudeCodeAgentID resolves the Claude Code agent ID and uses a stable sentinel for the root agent.func ExtractClaudeCodeAgentID(ctx context.Context, headers http.Header) string { if agentID := claudeCodeHeader(ctx, headers, ClaudeCodeAgentHeader); agentID != "" { return agentID } return ClaudeCodeMainAgentID} // ClaudeCodeExecutionScope returns the stable root-session and agent identity used by Codex execution state.func ClaudeCodeExecutionScope(ctx context.Context, payload []byte, headers http.Header) (string, bool) { sessionID := ExtractClaudeCodeSessionID(ctx, payload, headers) if sessionID == "" { return "", false } return "claude:" + sessionID + ":agent:" + ExtractClaudeCodeAgentID(ctx, headers), true} func claudeCodeHeader(ctx context.Context, headers http.Header, name string) string { if value := headerValueCaseInsensitive(headers, name); value != "" { return value } if ctx != nil { if ginCtx, ok := ctx.Value("gin").(*gin.Context); ok && ginCtx != nil && ginCtx.Request != nil { return headerValueCaseInsensitive(ginCtx.Request.Header, name) } } return ""} // HeaderValueCaseInsensitive returns the first non-empty header value matching name case-insensitively.func HeaderValueCaseInsensitive(headers http.Header, name string) string { return headerValueCaseInsensitive(headers, name)} // HeaderValuesCaseInsensitive returns all non-empty header values matching name case-insensitively.func HeaderValuesCaseInsensitive(headers http.Header, name string) []string { if headers == nil { return nil } var result []string for key, values := range headers { if strings.EqualFold(key, name) { for _, value := range values { if trimmed := strings.TrimSpace(value); trimmed != "" { result = append(result, trimmed) } } } } return result} func headerValueCaseInsensitive(headers http.Header, name string) string { if headers == nil { return "" } if value := strings.TrimSpace(headers.Get(name)); value != "" { return value } for key, values := range headers { if !strings.EqualFold(key, name) { continue } for _, value := range values { if value = strings.TrimSpace(value); value != "" { return value } } } return ""} func extractClaudeCodeSessionIDFromPayload(payload []byte) string { if len(payload) == 0 { return "" } userID := gjson.GetBytes(payload, "metadata.user_id").String() if userID == "" { return "" } if matches := claudeCodeSessionSuffixPattern.FindStringSubmatch(userID); len(matches) >= 2 { return matches[1] } if len(userID) > 0 && userID[0] == '{' { return strings.TrimSpace(gjson.Get(userID, "session_id").String()) } return ""} // ClaudeCodePromptCache derives a deterministic upstream prompt_cache_key for one Claude Code agent.func ClaudeCodePromptCache(ctx context.Context, modelName string, payload []byte, headers http.Header) (CodexCache, bool, error) { modelName = strings.TrimSpace(modelName) executionScope, ok := ClaudeCodeExecutionScope(ctx, payload, headers) if modelName == "" || !ok { return CodexCache{}, false, nil } identity := strings.Join([]string{"cli-proxy-api:codex:claude-code", modelName, executionScope}, "\x00") return CodexCache{ID: uuid.NewSHA1(uuid.NameSpaceOID, []byte(identity)).String()}, true, nil}
package helps
import (
"context"
"net/http"
"regexp"
"strings"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/tidwall/gjson"
)
const (
ClaudeCodeSessionHeader = "X-Claude-Code-Session-Id"
ClaudeCodeAgentHeader = "X-Claude-Code-Agent-Id"
ClaudeCodeMainAgentID = "main"
)
var claudeCodeSessionSuffixPattern = regexp.MustCompile(`_session_([a-f0-9-]+)$`)
// ExtractClaudeCodeSessionID resolves a Claude Code session ID, preferring X-Claude-Code-Session-Id over payload metadata.
func ExtractClaudeCodeSessionID(ctx context.Context, payload []byte, headers http.Header) string {
if sessionID := claudeCodeHeader(ctx, headers, ClaudeCodeSessionHeader); sessionID != "" {
return sessionID
}
return extractClaudeCodeSessionIDFromPayload(payload)
}
// ExtractClaudeCodeAgentID resolves the Claude Code agent ID and uses a stable sentinel for the root agent.
func ExtractClaudeCodeAgentID(ctx context.Context, headers http.Header) string {
if agentID := claudeCodeHeader(ctx, headers, ClaudeCodeAgentHeader); agentID != "" {
return agentID
}
return ClaudeCodeMainAgentID
}
// ClaudeCodeExecutionScope returns the stable root-session and agent identity used by Codex execution state.
func ClaudeCodeExecutionScope(ctx context.Context, payload []byte, headers http.Header) (string, bool) {
sessionID := ExtractClaudeCodeSessionID(ctx, payload, headers)
if sessionID == "" {
return "", false
}
return "claude:" + sessionID + ":agent:" + ExtractClaudeCodeAgentID(ctx, headers), true
}
func claudeCodeHeader(ctx context.Context, headers http.Header, name string) string {
if value := headerValueCaseInsensitive(headers, name); value != "" {
return value
}
if ctx != nil {
if ginCtx, ok := ctx.Value("gin").(*gin.Context); ok && ginCtx != nil && ginCtx.Request != nil {
return headerValueCaseInsensitive(ginCtx.Request.Header, name)
}
}
return ""
}
// HeaderValueCaseInsensitive returns the first non-empty header value matching name case-insensitively.
func HeaderValueCaseInsensitive(headers http.Header, name string) string {
return headerValueCaseInsensitive(headers, name)
}
// HeaderValuesCaseInsensitive returns all non-empty header values matching name case-insensitively.
func HeaderValuesCaseInsensitive(headers http.Header, name string) []string {
if headers == nil {
return nil
}
var result []string
for key, values := range headers {
if strings.EqualFold(key, name) {
for _, value := range values {
if trimmed := strings.TrimSpace(value); trimmed != "" {
result = append(result, trimmed)
}
}
}
}
return result
}
func headerValueCaseInsensitive(headers http.Header, name string) string {
if headers == nil {
return ""
}
if value := strings.TrimSpace(headers.Get(name)); value != "" {
return value
}
for key, values := range headers {
if !strings.EqualFold(key, name) {
continue
}
for _, value := range values {
if value = strings.TrimSpace(value); value != "" {
return value
}
}
}
return ""
}
func extractClaudeCodeSessionIDFromPayload(payload []byte) string {
if len(payload) == 0 {
return ""
}
userID := gjson.GetBytes(payload, "metadata.user_id").String()
if userID == "" {
return ""
}
if matches := claudeCodeSessionSuffixPattern.FindStringSubmatch(userID); len(matches) >= 2 {
return matches[1]
}
if len(userID) > 0 && userID[0] == '{' {
return strings.TrimSpace(gjson.Get(userID, "session_id").String())
}
return ""
}
// ClaudeCodePromptCache derives a deterministic upstream prompt_cache_key for one Claude Code agent.
func ClaudeCodePromptCache(ctx context.Context, modelName string, payload []byte, headers http.Header) (CodexCache, bool, error) {
modelName = strings.TrimSpace(modelName)
executionScope, ok := ClaudeCodeExecutionScope(ctx, payload, headers)
if modelName == "" || !ok {
return CodexCache{}, false, nil
}
identity := strings.Join([]string{"cli-proxy-api:codex:claude-code", modelName, executionScope}, "\x00")
return CodexCache{ID: uuid.NewSHA1(uuid.NameSpaceOID, []byte(identity)).String()}, true, nil
}