fix(claude): centralize oauth tool-name transform flow
This commit is contained in:
@@ -191,14 +191,8 @@ func (e *ClaudeExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, r
|
||||
bodyForUpstream := body
|
||||
oauthToken := isClaudeOAuthToken(apiKey)
|
||||
var oauthToolNamesReverseMap map[string]string
|
||||
if oauthToken && !auth.ToolPrefixDisabled() {
|
||||
bodyForUpstream = applyClaudeToolPrefix(body, claudeToolPrefix)
|
||||
}
|
||||
// Remap third-party tool names to Claude Code equivalents and remove
|
||||
// tools without official counterparts. This prevents Anthropic from
|
||||
// fingerprinting the request as third-party via tool naming patterns.
|
||||
if oauthToken {
|
||||
bodyForUpstream, oauthToolNamesReverseMap = remapOAuthToolNames(bodyForUpstream)
|
||||
bodyForUpstream, oauthToolNamesReverseMap = prepareClaudeOAuthToolNamesForUpstream(bodyForUpstream, claudeToolPrefix, auth.ToolPrefixDisabled())
|
||||
}
|
||||
// Enable cch signing by default for OAuth tokens (not just experimental flag).
|
||||
// Claude Code always computes cch; missing or invalid cch is a detectable fingerprint.
|
||||
@@ -292,13 +286,7 @@ func (e *ClaudeExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, r
|
||||
} else {
|
||||
reporter.Publish(ctx, helps.ParseClaudeUsage(data))
|
||||
}
|
||||
if isClaudeOAuthToken(apiKey) && !auth.ToolPrefixDisabled() {
|
||||
data = stripClaudeToolPrefixFromResponse(data, claudeToolPrefix)
|
||||
}
|
||||
// Reverse the OAuth tool name remap so the downstream client sees original names.
|
||||
if isClaudeOAuthToken(apiKey) && len(oauthToolNamesReverseMap) > 0 {
|
||||
data = reverseRemapOAuthToolNames(data, oauthToolNamesReverseMap)
|
||||
}
|
||||
data = restoreClaudeOAuthToolNamesFromResponse(data, claudeToolPrefix, auth.ToolPrefixDisabled(), oauthToolNamesReverseMap)
|
||||
var param any
|
||||
out := sdktranslator.TranslateNonStream(
|
||||
ctx,
|
||||
@@ -373,14 +361,8 @@ func (e *ClaudeExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A
|
||||
bodyForUpstream := body
|
||||
oauthToken := isClaudeOAuthToken(apiKey)
|
||||
var oauthToolNamesReverseMap map[string]string
|
||||
if oauthToken && !auth.ToolPrefixDisabled() {
|
||||
bodyForUpstream = applyClaudeToolPrefix(body, claudeToolPrefix)
|
||||
}
|
||||
// Remap third-party tool names to Claude Code equivalents and remove
|
||||
// tools without official counterparts. This prevents Anthropic from
|
||||
// fingerprinting the request as third-party via tool naming patterns.
|
||||
if oauthToken {
|
||||
bodyForUpstream, oauthToolNamesReverseMap = remapOAuthToolNames(bodyForUpstream)
|
||||
bodyForUpstream, oauthToolNamesReverseMap = prepareClaudeOAuthToolNamesForUpstream(bodyForUpstream, claudeToolPrefix, auth.ToolPrefixDisabled())
|
||||
}
|
||||
// Enable cch signing by default for OAuth tokens (not just experimental flag).
|
||||
if oauthToken || experimentalCCHSigningEnabled(e.cfg, auth) {
|
||||
@@ -471,12 +453,7 @@ func (e *ClaudeExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A
|
||||
if detail, ok := helps.ParseClaudeStreamUsage(line); ok {
|
||||
reporter.Publish(ctx, detail)
|
||||
}
|
||||
if isClaudeOAuthToken(apiKey) && !auth.ToolPrefixDisabled() {
|
||||
line = stripClaudeToolPrefixFromStreamLine(line, claudeToolPrefix)
|
||||
}
|
||||
if isClaudeOAuthToken(apiKey) && len(oauthToolNamesReverseMap) > 0 {
|
||||
line = reverseRemapOAuthToolNamesFromStreamLine(line, oauthToolNamesReverseMap)
|
||||
}
|
||||
line = restoreClaudeOAuthToolNamesFromStreamLine(line, claudeToolPrefix, auth.ToolPrefixDisabled(), oauthToolNamesReverseMap)
|
||||
// Forward the line as-is to preserve SSE format
|
||||
cloned := make([]byte, len(line)+1)
|
||||
copy(cloned, line)
|
||||
@@ -501,12 +478,7 @@ func (e *ClaudeExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A
|
||||
if detail, ok := helps.ParseClaudeStreamUsage(line); ok {
|
||||
reporter.Publish(ctx, detail)
|
||||
}
|
||||
if isClaudeOAuthToken(apiKey) && !auth.ToolPrefixDisabled() {
|
||||
line = stripClaudeToolPrefixFromStreamLine(line, claudeToolPrefix)
|
||||
}
|
||||
if isClaudeOAuthToken(apiKey) && len(oauthToolNamesReverseMap) > 0 {
|
||||
line = reverseRemapOAuthToolNamesFromStreamLine(line, oauthToolNamesReverseMap)
|
||||
}
|
||||
line = restoreClaudeOAuthToolNamesFromStreamLine(line, claudeToolPrefix, auth.ToolPrefixDisabled(), oauthToolNamesReverseMap)
|
||||
chunks := sdktranslator.TranslateStream(
|
||||
ctx,
|
||||
to,
|
||||
@@ -556,12 +528,8 @@ func (e *ClaudeExecutor) CountTokens(ctx context.Context, auth *cliproxyauth.Aut
|
||||
// Extract betas from body and convert to header (for count_tokens too)
|
||||
var extraBetas []string
|
||||
extraBetas, body = extractAndRemoveBetas(body)
|
||||
if isClaudeOAuthToken(apiKey) && !auth.ToolPrefixDisabled() {
|
||||
body = applyClaudeToolPrefix(body, claudeToolPrefix)
|
||||
}
|
||||
// Remap tool names for OAuth token requests to avoid third-party fingerprinting.
|
||||
if isClaudeOAuthToken(apiKey) {
|
||||
body, _ = remapOAuthToolNames(body)
|
||||
body, _ = prepareClaudeOAuthToolNamesForUpstream(body, claudeToolPrefix, auth.ToolPrefixDisabled())
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/v1/messages/count_tokens?beta=true", baseURL)
|
||||
@@ -1001,6 +969,36 @@ func isClaudeOAuthToken(apiKey string) bool {
|
||||
return strings.Contains(apiKey, "sk-ant-oat")
|
||||
}
|
||||
|
||||
// prepareClaudeOAuthToolNamesForUpstream applies the Claude OAuth tool-name
|
||||
// transforms in the same order across request paths. Remap runs before prefixing
|
||||
// so any future non-empty prefix still composes correctly with the per-request
|
||||
// reverse map.
|
||||
func prepareClaudeOAuthToolNamesForUpstream(body []byte, prefix string, prefixDisabled bool) ([]byte, map[string]string) {
|
||||
body, reverseMap := remapOAuthToolNames(body)
|
||||
if !prefixDisabled {
|
||||
body = applyClaudeToolPrefix(body, prefix)
|
||||
}
|
||||
return body, reverseMap
|
||||
}
|
||||
|
||||
// restoreClaudeOAuthToolNamesFromResponse undoes the Claude OAuth tool-name
|
||||
// transforms for non-stream responses in reverse order.
|
||||
func restoreClaudeOAuthToolNamesFromResponse(body []byte, prefix string, prefixDisabled bool, reverseMap map[string]string) []byte {
|
||||
if !prefixDisabled {
|
||||
body = stripClaudeToolPrefixFromResponse(body, prefix)
|
||||
}
|
||||
return reverseRemapOAuthToolNames(body, reverseMap)
|
||||
}
|
||||
|
||||
// restoreClaudeOAuthToolNamesFromStreamLine undoes the Claude OAuth tool-name
|
||||
// transforms for SSE lines in reverse order.
|
||||
func restoreClaudeOAuthToolNamesFromStreamLine(line []byte, prefix string, prefixDisabled bool, reverseMap map[string]string) []byte {
|
||||
if !prefixDisabled {
|
||||
line = stripClaudeToolPrefixFromStreamLine(line, prefix)
|
||||
}
|
||||
return reverseRemapOAuthToolNamesFromStreamLine(line, reverseMap)
|
||||
}
|
||||
|
||||
// remapOAuthToolNames renames third-party tool names to Claude Code equivalents
|
||||
// and removes tools without an official counterpart. This prevents Anthropic from
|
||||
// fingerprinting the request as a third-party client via tool naming patterns.
|
||||
|
||||
Reference in New Issue
Block a user