mirror of
https://github.com/router-for-me/CLIProxyAPIPlus.git
synced 2026-04-26 20:55:47 +00:00
fix(claude): restore legacy runtime OS arch fallback
This commit is contained in:
@@ -170,7 +170,9 @@ nonstream-keepalive-interval: 0
|
|||||||
# cache-user-id: true # optional: default is false; set true to reuse cached user_id per API key instead of generating a random one each request
|
# cache-user-id: true # optional: default is false; set true to reuse cached user_id per API key instead of generating a random one each request
|
||||||
|
|
||||||
# Default headers for Claude API requests. Update when Claude Code releases new versions.
|
# Default headers for Claude API requests. Update when Claude Code releases new versions.
|
||||||
# These are used as fallbacks when the client does not send its own headers.
|
# In legacy mode, user-agent/package-version/runtime-version/timeout are used as fallbacks
|
||||||
|
# when the client omits them, while OS/arch remain runtime-derived. When
|
||||||
|
# stabilize-device-profile is enabled, all values below seed the pinned baseline fingerprint.
|
||||||
# claude-header-defaults:
|
# claude-header-defaults:
|
||||||
# user-agent: "claude-cli/2.1.44 (external, sdk-cli)"
|
# user-agent: "claude-cli/2.1.44 (external, sdk-cli)"
|
||||||
# package-version: "0.74.0"
|
# package-version: "0.74.0"
|
||||||
|
|||||||
@@ -128,8 +128,10 @@ type Config struct {
|
|||||||
legacyMigrationPending bool `yaml:"-" json:"-"`
|
legacyMigrationPending bool `yaml:"-" json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClaudeHeaderDefaults configures default header values injected into Claude API requests
|
// ClaudeHeaderDefaults configures default header values injected into Claude API requests.
|
||||||
// when the client does not send them. Update these when Claude Code releases a new version.
|
// In legacy mode, UserAgent/PackageVersion/RuntimeVersion/Timeout act as fallbacks when
|
||||||
|
// the client omits them, while OS/Arch remain runtime-derived. When stabilized device
|
||||||
|
// profiles are enabled, all of these values seed the baseline pinned fingerprint.
|
||||||
type ClaudeHeaderDefaults struct {
|
type ClaudeHeaderDefaults struct {
|
||||||
UserAgent string `yaml:"user-agent" json:"user-agent"`
|
UserAgent string `yaml:"user-agent" json:"user-agent"`
|
||||||
PackageVersion string `yaml:"package-version" json:"package-version"`
|
PackageVersion string `yaml:"package-version" json:"package-version"`
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -107,6 +108,36 @@ func defaultClaudeDeviceProfile(cfg *config.Config) claudeDeviceProfile {
|
|||||||
return profile
|
return profile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mapStainlessOS maps runtime.GOOS to Stainless SDK OS names.
|
||||||
|
func mapStainlessOS() string {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "darwin":
|
||||||
|
return "MacOS"
|
||||||
|
case "windows":
|
||||||
|
return "Windows"
|
||||||
|
case "linux":
|
||||||
|
return "Linux"
|
||||||
|
case "freebsd":
|
||||||
|
return "FreeBSD"
|
||||||
|
default:
|
||||||
|
return "Other::" + runtime.GOOS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mapStainlessArch maps runtime.GOARCH to Stainless SDK architecture names.
|
||||||
|
func mapStainlessArch() string {
|
||||||
|
switch runtime.GOARCH {
|
||||||
|
case "amd64":
|
||||||
|
return "x64"
|
||||||
|
case "arm64":
|
||||||
|
return "arm64"
|
||||||
|
case "386":
|
||||||
|
return "x86"
|
||||||
|
default:
|
||||||
|
return "other::" + runtime.GOARCH
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func parseClaudeCLIVersion(userAgent string) (claudeCLIVersion, bool) {
|
func parseClaudeCLIVersion(userAgent string) (claudeCLIVersion, bool) {
|
||||||
matches := claudeCLIVersionPattern.FindStringSubmatch(strings.TrimSpace(userAgent))
|
matches := claudeCLIVersionPattern.FindStringSubmatch(strings.TrimSpace(userAgent))
|
||||||
if len(matches) != 4 {
|
if len(matches) != 4 {
|
||||||
@@ -274,8 +305,8 @@ func applyClaudeLegacyDeviceHeaders(r *http.Request, ginHeaders http.Header, cfg
|
|||||||
|
|
||||||
miscEnsure("X-Stainless-Runtime-Version", profile.RuntimeVersion)
|
miscEnsure("X-Stainless-Runtime-Version", profile.RuntimeVersion)
|
||||||
miscEnsure("X-Stainless-Package-Version", profile.PackageVersion)
|
miscEnsure("X-Stainless-Package-Version", profile.PackageVersion)
|
||||||
miscEnsure("X-Stainless-Os", profile.OS)
|
miscEnsure("X-Stainless-Os", mapStainlessOS())
|
||||||
miscEnsure("X-Stainless-Arch", profile.Arch)
|
miscEnsure("X-Stainless-Arch", mapStainlessArch())
|
||||||
|
|
||||||
clientUA := ""
|
clientUA := ""
|
||||||
if ginHeaders != nil {
|
if ginHeaders != nil {
|
||||||
|
|||||||
@@ -855,8 +855,8 @@ func applyClaudeHeaders(r *http.Request, auth *cliproxyauth.Auth, apiKey string,
|
|||||||
r.Header.Set("Accept", "application/json")
|
r.Header.Set("Accept", "application/json")
|
||||||
r.Header.Set("Accept-Encoding", "gzip, deflate, br, zstd")
|
r.Header.Set("Accept-Encoding", "gzip, deflate, br, zstd")
|
||||||
}
|
}
|
||||||
// Keep OS/Arch mapping dynamic (not configurable).
|
// Legacy mode keeps OS/Arch runtime-derived; stabilized mode may pin
|
||||||
// They intentionally continue to derive from runtime.GOOS/runtime.GOARCH.
|
// the full device profile from the cached or configured baseline.
|
||||||
var attrs map[string]string
|
var attrs map[string]string
|
||||||
if auth != nil {
|
if auth != nil {
|
||||||
attrs = auth.Attributes
|
attrs = auth.Attributes
|
||||||
|
|||||||
@@ -216,6 +216,62 @@ func TestApplyClaudeHeaders_DisableDeviceProfileStabilization(t *testing.T) {
|
|||||||
assertClaudeFingerprint(t, lowerReq.Header, "claude-cli/2.1.61 (external, cli)", "0.73.0", "v24.2.0", "Windows", "x64")
|
assertClaudeFingerprint(t, lowerReq.Header, "claude-cli/2.1.61 (external, cli)", "0.73.0", "v24.2.0", "Windows", "x64")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestApplyClaudeHeaders_LegacyModeFallsBackToRuntimeOSArchWhenMissing(t *testing.T) {
|
||||||
|
resetClaudeDeviceProfileCache()
|
||||||
|
|
||||||
|
stabilize := false
|
||||||
|
cfg := &config.Config{
|
||||||
|
ClaudeHeaderDefaults: config.ClaudeHeaderDefaults{
|
||||||
|
UserAgent: "claude-cli/2.1.60 (external, cli)",
|
||||||
|
PackageVersion: "0.70.0",
|
||||||
|
RuntimeVersion: "v22.0.0",
|
||||||
|
OS: "MacOS",
|
||||||
|
Arch: "arm64",
|
||||||
|
StabilizeDeviceProfile: &stabilize,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
auth := &cliproxyauth.Auth{
|
||||||
|
ID: "auth-legacy-runtime-os-arch",
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"api_key": "key-legacy-runtime-os-arch",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
req := newClaudeHeaderTestRequest(t, http.Header{
|
||||||
|
"User-Agent": []string{"curl/8.7.1"},
|
||||||
|
})
|
||||||
|
applyClaudeHeaders(req, auth, "key-legacy-runtime-os-arch", false, nil, cfg)
|
||||||
|
|
||||||
|
assertClaudeFingerprint(t, req.Header, "claude-cli/2.1.60 (external, cli)", "0.70.0", "v22.0.0", mapStainlessOS(), mapStainlessArch())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApplyClaudeHeaders_UnsetStabilizationAlsoUsesLegacyRuntimeOSArchFallback(t *testing.T) {
|
||||||
|
resetClaudeDeviceProfileCache()
|
||||||
|
|
||||||
|
cfg := &config.Config{
|
||||||
|
ClaudeHeaderDefaults: config.ClaudeHeaderDefaults{
|
||||||
|
UserAgent: "claude-cli/2.1.60 (external, cli)",
|
||||||
|
PackageVersion: "0.70.0",
|
||||||
|
RuntimeVersion: "v22.0.0",
|
||||||
|
OS: "MacOS",
|
||||||
|
Arch: "arm64",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
auth := &cliproxyauth.Auth{
|
||||||
|
ID: "auth-unset-runtime-os-arch",
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"api_key": "key-unset-runtime-os-arch",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
req := newClaudeHeaderTestRequest(t, http.Header{
|
||||||
|
"User-Agent": []string{"curl/8.7.1"},
|
||||||
|
})
|
||||||
|
applyClaudeHeaders(req, auth, "key-unset-runtime-os-arch", false, nil, cfg)
|
||||||
|
|
||||||
|
assertClaudeFingerprint(t, req.Header, "claude-cli/2.1.60 (external, cli)", "0.70.0", "v22.0.0", mapStainlessOS(), mapStainlessArch())
|
||||||
|
}
|
||||||
|
|
||||||
func TestClaudeDeviceProfileStabilizationEnabled_DefaultFalse(t *testing.T) {
|
func TestClaudeDeviceProfileStabilizationEnabled_DefaultFalse(t *testing.T) {
|
||||||
if claudeDeviceProfileStabilizationEnabled(nil) {
|
if claudeDeviceProfileStabilizationEnabled(nil) {
|
||||||
t.Fatal("expected nil config to default to disabled stabilization")
|
t.Fatal("expected nil config to default to disabled stabilization")
|
||||||
|
|||||||
Reference in New Issue
Block a user