mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-16 15:43:13 +08:00
出站伪装用的 Claude Code 版本号此前硬编码在 claude.CLICurrentVersion, 只能靠环境变量 SUB2API_CLAUDE_CLI_VERSION 覆盖且需重启;Anthropic 抬高 版本下限时(如 Opus 5.5 要求 >= 2.1.280)必须改代码发版才能跟上。 改为每小时从官方 GitHub Releases 同步最新稳定版,无需重启也无需发版。 - 新增 ClaudeCodeVersionSyncService:1 小时间隔,数据源 anthropics/claude-code 的 releases/latest,失败回退最近 30 条列表;过滤 draft/prerelease; 只向前推进,拉取失败保留上次值不降级;面板可关闭 - 新增三个设置项:claude_code_client_version(管理员手动固定)、 claude_code_client_version_synced(同步服务独占写)、 claude_code_version_auto_sync_enabled(开关,默认开) - 取值优先级:面板手动值 -> 同步值 -> SUB2API_CLAUDE_CLI_VERSION -> 内置基线, 每层经 IsSupportedCLIVersion 校验,含 60s 缓存与 singleflight - 版本号运行期化:claude.DefaultHeaders 由包级 var 改为函数,新增 EffectiveCLIVersion/DefaultUserAgent 与 SetCLIVersionResolver(未注入时 与 CLIVersion() 等价);identity 的 defaultFingerprint 不再在包 init 固化, 指纹下限与主版本超前校验改用运行期值 - 守住一致性铁律:buildUpstreamRequest 与 count_tokens 各取一次 UA 字符串, 同时喂给出站 User-Agent 头与请求体 billing 的 cc_version,避免缓存翻转 瞬间头体版本不一致被判非正版客户端 - 管理后台设置页新增配置区块,含中英文案与「当前同步到」只读展示
74 lines
2.5 KiB
Go
74 lines
2.5 KiB
Go
package claude
|
|
|
|
import "testing"
|
|
|
|
func TestIsSupportedCLIVersion(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
version string
|
|
want bool
|
|
}{
|
|
{"内置基线本身", CLICurrentVersion, true},
|
|
{"高于基线的补丁位", "2.1.259", true},
|
|
{"高于基线的次版本", "2.2.0", true},
|
|
{"高于基线的主版本", "3.0.0", true},
|
|
{"低于基线", "2.1.219", false},
|
|
{"远低于基线", "1.0.0", false},
|
|
{"空值", "", false},
|
|
{"只有两段", "2.2", false},
|
|
{"带 v 前缀", "v2.2.0", false},
|
|
{"预发布后缀", "2.2.0-local", false},
|
|
{"构建元数据", "2.2.0+build1", false},
|
|
{"哨兵版本带后缀", "999.0.0-local", false},
|
|
{"纯数字哨兵仍然合法(形态没问题,运维自负)", "999.0.0", true},
|
|
{"非数字", "abc", false},
|
|
{"多余的段", "2.2.0.1", false},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if got := IsSupportedCLIVersion(tc.version); got != tc.want {
|
|
t.Fatalf("IsSupportedCLIVersion(%q) = %v, want %v", tc.version, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestResolveCLIVersion(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
raw string
|
|
want string
|
|
}{
|
|
{"未配置时用内置基线", "", CLICurrentVersion},
|
|
{"只有空白等同未配置", " ", CLICurrentVersion},
|
|
{"合法覆盖生效", "2.1.259", "2.1.259"},
|
|
{"两侧空白被裁掉", " 2.1.259 ", "2.1.259"},
|
|
{"非法值回落基线", "not-a-version", CLICurrentVersion},
|
|
{"向下覆盖被拒", "2.0.0", CLICurrentVersion},
|
|
{"预发布后缀被拒(会毒化账号指纹)", "2.2.0-local", CLICurrentVersion},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if got := resolveCLIVersion(tc.raw); got != tc.want {
|
|
t.Fatalf("resolveCLIVersion(%q) = %q, want %q", tc.raw, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// 伪装身份的自洽性:User-Agent 头里的版本号必须与 CLIVersion() 一致。
|
|
// 两者由不同代码路径写入同一个请求,不一致会被上游判为非正版客户端。
|
|
func TestDefaultHeadersUserAgentMatchesCLIVersion(t *testing.T) {
|
|
want := "claude-cli/" + CLIVersion() + " (external, cli)"
|
|
if got := DefaultHeaders()["User-Agent"]; got != want {
|
|
t.Fatalf("DefaultHeaders[User-Agent] = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
// 没有配置覆盖时,CLIVersion() 必须等于内置基线——保证本 PR 对既有部署零行为变化。
|
|
func TestCLIVersionDefaultsToBuiltinPin(t *testing.T) {
|
|
if got := CLIVersion(); got != CLICurrentVersion {
|
|
t.Fatalf("CLIVersion() = %q, want built-in pin %q (测试进程未设置 %s)", got, CLICurrentVersion, CLIVersionEnv)
|
|
}
|
|
}
|