mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-09-03 14:39:26 +08:00
refactor(claude): deduplicate CLI identity application and tidy helpers
- Extract the credential-identity block shared by the streaming and non-streaming Claude paths into applyClaudeCLIIdentity, so the identity seed choice (API key versus stable Kimi auth identity) cannot drift between the two paths - Move stripDefaultKimiClaudeCodeAttribution next to the other attribution and CCH helpers in claude_signing.go; it is only called from the Claude executor paths and never from the Kimi executor itself - Reattach the addConfigHeadersToAttrs doc comment to its function in the watcher synthesizer helpers
This commit is contained in:
@@ -171,22 +171,9 @@ func (e *ClaudeExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, r
|
||||
}
|
||||
bodyForUpstream = sanitizeClaudeMessagesForClaudeUpstreamWithDebug(ctx, bodyForUpstream, baseModel, helps.APIKeyModelIsCompat(req))
|
||||
if fp.ApplyCLIIdentity {
|
||||
// ApplyCLIIdentity and ProfileClaudeCodeCLI are the same predicate, so
|
||||
// claudeSessionID was already resolved above by ClaudeAgentSessionUUIDForRequest,
|
||||
// which always returns a UUID. Do not add a second session source here: a
|
||||
// per-apiKey cached ID would silently break agent-conversation continuity.
|
||||
identitySeed := apiKey
|
||||
if isKimiMessagesUpstream(auth, url) {
|
||||
identitySeed = helps.ClaudeCLIAuthIdentitySeed(auth)
|
||||
}
|
||||
var identityAuth *cliproxyauth.Auth
|
||||
identityAuth, err = helps.PrepareClaudeCLIFingerprintAuth(auth, identitySeed, fp.SynthesizeIdentity)
|
||||
bodyForUpstream, err = applyClaudeCLIIdentity(bodyForUpstream, auth, apiKey, url, claudeSessionID, fp.SynthesizeIdentity)
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("ensure Claude CLI fingerprint identity: %w", err)
|
||||
}
|
||||
bodyForUpstream, _, err = helps.ApplyClaudeCredentialMetadata(bodyForUpstream, identityAuth, claudeSessionID)
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("apply Claude credential metadata: %w", err)
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
cchBilling := ""
|
||||
|
||||
@@ -163,22 +163,9 @@ func (e *ClaudeExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A
|
||||
}
|
||||
bodyForUpstream = sanitizeClaudeMessagesForClaudeUpstreamWithDebug(ctx, bodyForUpstream, baseModel, helps.APIKeyModelIsCompat(req))
|
||||
if fp.ApplyCLIIdentity {
|
||||
// ApplyCLIIdentity and ProfileClaudeCodeCLI are the same predicate, so
|
||||
// claudeSessionID was already resolved above by ClaudeAgentSessionUUIDForRequest,
|
||||
// which always returns a UUID. Do not add a second session source here: a
|
||||
// per-apiKey cached ID would silently break agent-conversation continuity.
|
||||
identitySeed := apiKey
|
||||
if isKimiMessagesUpstream(auth, url) {
|
||||
identitySeed = helps.ClaudeCLIAuthIdentitySeed(auth)
|
||||
}
|
||||
var identityAuth *cliproxyauth.Auth
|
||||
identityAuth, err = helps.PrepareClaudeCLIFingerprintAuth(auth, identitySeed, fp.SynthesizeIdentity)
|
||||
bodyForUpstream, err = applyClaudeCLIIdentity(bodyForUpstream, auth, apiKey, url, claudeSessionID, fp.SynthesizeIdentity)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ensure Claude CLI fingerprint identity: %w", err)
|
||||
}
|
||||
bodyForUpstream, _, err = helps.ApplyClaudeCredentialMetadata(bodyForUpstream, identityAuth, claudeSessionID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("apply Claude credential metadata: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
cchBilling := ""
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package executor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
claudeauth "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/claude"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/executor/helps"
|
||||
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -109,3 +111,31 @@ func resolveClaudeFingerprintPolicy(cfg *config.Config, auth *cliproxyauth.Auth,
|
||||
OAuthCancellation: authIsOAuth,
|
||||
}
|
||||
}
|
||||
|
||||
// applyClaudeCLIIdentity applies the Claude Code CLI credential identity to the
|
||||
// upstream Messages body. It is the single implementation behind both the
|
||||
// streaming and the non-streaming request paths; keep it that way.
|
||||
//
|
||||
// ApplyCLIIdentity and ProfileClaudeCodeCLI are the same predicate, so
|
||||
// sessionID has already been resolved by ClaudeAgentSessionUUIDForRequest,
|
||||
// which always returns a UUID. Do not add a second session source here: a
|
||||
// per-apiKey cached ID would silently break agent-conversation continuity.
|
||||
//
|
||||
// API keys seed the synthesized identity from the key itself; delegated
|
||||
// providers such as Kimi seed from the stable auth identity, so an access-token
|
||||
// rotation does not rotate the device fingerprint.
|
||||
func applyClaudeCLIIdentity(body []byte, auth *cliproxyauth.Auth, apiKey, upstreamURL, sessionID string, synthesize bool) ([]byte, error) {
|
||||
identitySeed := apiKey
|
||||
if isKimiMessagesUpstream(auth, upstreamURL) {
|
||||
identitySeed = helps.ClaudeCLIAuthIdentitySeed(auth)
|
||||
}
|
||||
identityAuth, errIdentity := helps.PrepareClaudeCLIFingerprintAuth(auth, identitySeed, synthesize)
|
||||
if errIdentity != nil {
|
||||
return nil, fmt.Errorf("ensure Claude CLI fingerprint identity: %w", errIdentity)
|
||||
}
|
||||
updated, _, errApply := helps.ApplyClaudeCredentialMetadata(body, identityAuth, sessionID)
|
||||
if errApply != nil {
|
||||
return nil, fmt.Errorf("apply Claude credential metadata: %w", errApply)
|
||||
}
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
xxHash64 "github.com/pierrec/xxHash/xxHash64"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/util"
|
||||
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/sjson"
|
||||
@@ -160,6 +161,18 @@ func isKimiMessagesUpstream(auth *cliproxyauth.Auth, endpoint string) bool {
|
||||
return isKimiAPIEndpoint(endpoint)
|
||||
}
|
||||
|
||||
// stripDefaultKimiClaudeCodeAttribution removes the Claude Code billing/CCH
|
||||
// attribution block from a Kimi Messages body when the caller did not opt into
|
||||
// the full CLI profile. Kimi treats the block as prompt text, so forwarding it
|
||||
// unchanged would leak CPA's attribution into the model's context. Other system
|
||||
// content is preserved.
|
||||
func stripDefaultKimiClaudeCodeAttribution(auth *cliproxyauth.Auth, endpoint string, cliFingerprint bool, body []byte) []byte {
|
||||
if cliFingerprint || !isKimiMessagesUpstream(auth, endpoint) {
|
||||
return body
|
||||
}
|
||||
return util.StripClaudeCodeAttributionSystem(body)
|
||||
}
|
||||
|
||||
// claudeCCHSigningEnabled applies CPA's CCH policy.
|
||||
//
|
||||
// Native gate, identical in Claude Code 2.1.220 through 2.1.234:
|
||||
|
||||
@@ -50,13 +50,6 @@ func NewKimiExecutor(cfg *config.Config) *KimiExecutor {
|
||||
// Identifier returns the executor identifier.
|
||||
func (e *KimiExecutor) Identifier() string { return "kimi" }
|
||||
|
||||
func stripDefaultKimiClaudeCodeAttribution(auth *cliproxyauth.Auth, endpoint string, cliFingerprint bool, body []byte) []byte {
|
||||
if cliFingerprint || !isKimiMessagesUpstream(auth, endpoint) {
|
||||
return body
|
||||
}
|
||||
return util.StripClaudeCodeAttributionSystem(body)
|
||||
}
|
||||
|
||||
// RequestToFormat reports the upstream request format used after auth selection.
|
||||
func (e *KimiExecutor) RequestToFormat(_ cliproxyexecutor.Request, opts cliproxyexecutor.Options) sdktranslator.Format {
|
||||
if opts.SourceFormat == sdktranslator.FormatClaude {
|
||||
|
||||
@@ -120,8 +120,6 @@ func addRequestScopedErrorsToMetadata(rules []config.RequestScopedErrorRule, met
|
||||
metadata["request_scoped_errors"] = rules
|
||||
}
|
||||
|
||||
// addConfigHeadersToAttrs adds header configuration to auth attributes.
|
||||
// Headers are prefixed with "header:" in the attributes map.
|
||||
func fingerprintProfileFromMetadata(metadata map[string]any) string {
|
||||
if metadata == nil {
|
||||
return ""
|
||||
@@ -152,6 +150,8 @@ func applyFingerprintProfileAttribute(auth *coreauth.Auth, metadata map[string]a
|
||||
auth.Attributes["fingerprint_profile"] = profile
|
||||
}
|
||||
|
||||
// addConfigHeadersToAttrs adds header configuration to auth attributes.
|
||||
// Headers are prefixed with "header:" in the attributes map.
|
||||
func addConfigHeadersToAttrs(headers map[string]string, attrs map[string]string) {
|
||||
if len(headers) == 0 || attrs == nil {
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user