win 0fefedf9cd feat(claude-mimic): upgrade Claude Code mimicry to 2.1.145 via bundle abstraction
反编译本地 Claude Code 2.1.145 二进制 (Bun 1.3.14 打包,
@anthropic-ai/sdk@0.94.0 嵌入) 提取真实指纹,系统性升级 mimicry。

核心改动:
- 新增 ClaudeCodeBundle struct 作为单一事实源,DefaultBundle 描述当前
  伪装目标的完整快照 (CLIVersion/SDKVersion/RuntimeVersion/OS/Arch)
- DefaultCLIVersion/DefaultStainlessPackageVersion/CLICurrentVersion/
  DefaultHeaders 全部派生自 DefaultBundle,消除三处 (2.1.92, 2.1.104,
  0.70.0, 0.81.0) 版本分裂
- CLI 版本 2.1.92/2.1.104 -> 2.1.145
- SDK 版本 0.70.0/0.81.0 -> 0.94.0
- 新增 12 个 2.1.145 反编译确认的 anthropic-beta token:
  advanced-tool-use, tool-search-tool, mcp-servers, mcp-client,
  mid-conversation-system, afk-mode, cache-diagnosis, context-hint,
  environments, managed-agents, skills, compact
- FullClaudeCodeMimicryBetas() 从 7 个 token 升级到 21 个 ordered list
- 修正 BetaTokenEfficientTools 错日期 (2026-03-28 -> 2025-02-19)
- 从默认 beta header 移除已 GA 的 BetaFineGrainedToolStreaming /
  BetaTokenEfficientTools (常量保留供客户端显式 merge)
- claudemask.RequiredNodeHeaders 加 X-Claude-Code-Session-Id 强制

新增 ensureClaudeCodeSessionID helper (claude_code_session_id.go):
- 真实 CLI 在 SDK 内强制 X-Claude-Code-Session-Id:y_(),缺失被判第三方
- OAuth mimic 路径: metadata.user_id 派生 -> canonical UUID 写入
  -> 兜底 uuid.NewString()
- API key passthrough 路径: 不从 body 派生,保护客户端原始语义
- 所有路径均对客户端传入的非法 UUID 执行删除 (避免恶意值上游透传)
- 所有写入 header 的 session-id 都通过 uuid.Parse 校验

测试:
- 新增 14 个 ensureClaudeCodeSessionID 单元测试,含恶意 UUID 注入拒绝
  + API key 路径隔离 + canonical 形式校验
- 新增 3 个 bundle 派生一致性测试
- mask_test 加 session-id 缺失校验 case
- 老 UA 断言 2.1.104 -> 2.1.145

不在范围:
- TLS 指纹 (utls 已处理)
- Bun.hash vs xxHash64 算法验证 (需 golden vectors,独立项目)

References:
- VERSION:2.1.145 BUILD_TIME:2026-05-19T01:36:35Z
  GIT_SHA:daa4c3755d45ab0cf97bb41db8c03bd2dfd2ff5f
2026-05-20 17:18:47 +08:00

550 lines
21 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package claude provides constants and helpers for Claude API integration.
package claude
import (
"fmt"
"os"
"strings"
)
// Claude Code 客户端相关常量
//
// 反编译 @anthropic-ai/claude-code 2.1.145Bun 1.3.14 打包)确认的真实指纹:
// VERSION = 2.1.145
// SDK 内嵌 = @anthropic-ai/sdk@0.94.0
// BUILD_TIME = 2026-05-19T01:36:35Z
// GIT_SHA = daa4c3755d45ab0cf97bb41db8c03bd2dfd2ff5f
// X-Stainless-Runtime = "node" (Bun 经 globalThis.process 检测为 node)
// X-Stainless-Runtime-Version = globalThis.process.version (Bun Node-compat 字符串)
// ClaudeCodeBundle 描述当前 sub2api 对外伪装的 Claude Code CLI 构建快照。
// 每次升级目标 CLI 版本时,更新 DefaultBundle 即可。
type ClaudeCodeBundle struct {
CLIVersion string
SDKVersion string
BuildDate string
GitSHA string
RuntimeVersion string
DefaultOS string
DefaultArch string
}
// DefaultBundle 是 sub2api 对外伪装的 Claude Code CLI 构建快照。
// 当 Claude Code 发新版且抓包确认指纹变化时,更新此处常量并同步测试断言。
var DefaultBundle = ClaudeCodeBundle{
CLIVersion: "2.1.145",
SDKVersion: "0.94.0",
BuildDate: "2026-05-19T01:36:35Z",
GitSHA: "daa4c3755d45ab0cf97bb41db8c03bd2dfd2ff5f",
RuntimeVersion: "v24.3.0",
DefaultOS: "MacOS",
DefaultArch: "arm64",
}
// 所有 Default* 变量从 DefaultBundle 派生。请勿在此包外直接修改它们,
// 改用 ApplyFingerprintOverrides 保持 bundle 与派生变量一致。
var (
DefaultCLIVersion = DefaultBundle.CLIVersion
DefaultStainlessLang = "js"
DefaultStainlessPackageVersion = DefaultBundle.SDKVersion
DefaultStainlessOS = DefaultBundle.DefaultOS
DefaultStainlessArch = DefaultBundle.DefaultArch
DefaultStainlessRuntime = "node"
DefaultStainlessRuntimeVersion = DefaultBundle.RuntimeVersion
DefaultStainlessRetryCount = "0"
DefaultStainlessTimeout = "600"
DefaultXApp = "cli"
DefaultAnthropicVersion = "2023-06-01"
)
// DeviceProfile 表示一组 Claude Code 客户端设备画像默认值。
type DeviceProfile struct {
UserAgent string
StainlessLang string
StainlessPackageVersion string
StainlessOS string
StainlessArch string
StainlessRuntime string
StainlessRuntimeVersion string
StainlessRetryCount string
StainlessTimeout string
XApp string
AnthropicVersion string
}
func trimEnv(key string) string {
return strings.TrimSpace(os.Getenv(key))
}
func envTruthy(key string) bool {
switch strings.ToLower(trimEnv(key)) {
case "1", "true", "yes", "on":
return true
default:
return false
}
}
func envExplicitFalse(key string) bool {
switch strings.ToLower(trimEnv(key)) {
case "0", "false", "no", "off":
return true
default:
return false
}
}
// CurrentEntrypoint returns the Claude Code entrypoint label used by the real CLI.
// The local bundle defaults to "cli" when CLAUDE_CODE_ENTRYPOINT is not set.
func CurrentEntrypoint() string {
if entrypoint := trimEnv("CLAUDE_CODE_ENTRYPOINT"); entrypoint != "" {
return entrypoint
}
return "cli"
}
func currentAgentSDKVersion() string {
return trimEnv("CLAUDE_AGENT_SDK_VERSION")
}
func currentClientApp() string {
return trimEnv("CLAUDE_AGENT_SDK_CLIENT_APP")
}
// CurrentWorkload returns the process-scoped workload tag used for cc_workload attribution.
// Local Claude Code keeps this in-process; sub2api exposes an env fallback so the server can
// mirror cron/daemon callers when configured.
func CurrentWorkload() string {
return trimEnv("CLAUDE_CODE_WORKLOAD")
}
func currentCLIUserAgentDescriptors() []string {
parts := []string{CurrentEntrypoint()}
if sdkVersion := currentAgentSDKVersion(); sdkVersion != "" {
parts = append(parts, "agent-sdk/"+sdkVersion)
}
if clientApp := currentClientApp(); clientApp != "" {
parts = append(parts, "client-app/"+clientApp)
}
if workload := CurrentWorkload(); workload != "" {
parts = append(parts, "workload/"+workload)
}
return parts
}
func currentCodeUserAgentDescriptors() []string {
parts := make([]string, 0, 3)
if entrypoint := trimEnv("CLAUDE_CODE_ENTRYPOINT"); entrypoint != "" {
parts = append(parts, entrypoint)
}
if sdkVersion := currentAgentSDKVersion(); sdkVersion != "" {
parts = append(parts, "agent-sdk/"+sdkVersion)
}
if clientApp := currentClientApp(); clientApp != "" {
parts = append(parts, "client-app/"+clientApp)
}
return parts
}
func formatUserAgent(cliVersion string) string {
version := strings.TrimSpace(cliVersion)
if version == "" {
version = DefaultCLIVersion
}
return fmt.Sprintf("claude-cli/%s (external, %s)", version, strings.Join(currentCLIUserAgentDescriptors(), ", "))
}
func DefaultUserAgent() string {
return formatUserAgent(DefaultCLIVersion)
}
func DefaultCodeUserAgent() string {
return "claude-code/" + strings.TrimSpace(DefaultCLIVersion)
}
// DetailedCodeUserAgent mirrors the local JqH() builder, which appends entrypoint / agent-sdk /
// client-app descriptors when they are present in the process environment.
func DetailedCodeUserAgent() string {
version := strings.TrimSpace(DefaultCLIVersion)
if version == "" {
version = "unknown"
}
parts := currentCodeUserAgentDescriptors()
if len(parts) == 0 {
return "claude-code/" + version
}
return fmt.Sprintf("claude-code/%s (%s)", version, strings.Join(parts, ", "))
}
// DefaultDeviceProfile 返回当前默认 Claude Code 设备画像。
func DefaultDeviceProfile() DeviceProfile {
return DeviceProfile{
UserAgent: DefaultUserAgent(),
StainlessLang: DefaultStainlessLang,
StainlessPackageVersion: DefaultStainlessPackageVersion,
StainlessOS: DefaultStainlessOS,
StainlessArch: DefaultStainlessArch,
StainlessRuntime: DefaultStainlessRuntime,
StainlessRuntimeVersion: DefaultStainlessRuntimeVersion,
StainlessRetryCount: DefaultStainlessRetryCount,
StainlessTimeout: DefaultStainlessTimeout,
XApp: DefaultXApp,
AnthropicVersion: DefaultAnthropicVersion,
}
}
func buildDefaultHeaders(profile DeviceProfile) map[string]string {
return map[string]string{
// Keep these in sync with recent Claude CLI traffic to reduce the chance
// that Claude Code-scoped OAuth credentials are rejected as "non-CLI" usage.
"User-Agent": profile.UserAgent,
"X-Stainless-Lang": profile.StainlessLang,
"X-Stainless-Package-Version": profile.StainlessPackageVersion,
"X-Stainless-OS": profile.StainlessOS,
"X-Stainless-Arch": profile.StainlessArch,
"X-Stainless-Runtime": profile.StainlessRuntime,
"X-Stainless-Runtime-Version": profile.StainlessRuntimeVersion,
"X-Stainless-Retry-Count": profile.StainlessRetryCount,
"X-Stainless-Timeout": profile.StainlessTimeout,
"X-App": profile.XApp,
"anthropic-version": profile.AnthropicVersion,
"anthropic-dangerous-direct-browser-access": "true",
}
}
// DefaultHeadersSnapshot returns a fresh copy of the default Claude Code header skeleton.
// It re-evaluates env-backed runtime values like CLAUDE_CODE_ENTRYPOINT on each call.
func DefaultHeadersSnapshot() map[string]string {
return buildDefaultHeaders(DefaultDeviceProfile())
}
// OptionalAPIHeaders returns the local Claude Code env-driven optional headers that are only
// attached in remote / SDK / protected modes.
func OptionalAPIHeaders() map[string]string {
headers := map[string]string{}
if containerID := trimEnv("CLAUDE_CODE_CONTAINER_ID"); containerID != "" {
headers["x-claude-remote-container-id"] = containerID
}
if remoteSessionID := trimEnv("CLAUDE_CODE_REMOTE_SESSION_ID"); remoteSessionID != "" {
headers["x-claude-remote-session-id"] = remoteSessionID
}
if clientApp := currentClientApp(); clientApp != "" {
headers["x-client-app"] = clientApp
}
if envTruthy("CLAUDE_CODE_ADDITIONAL_PROTECTION") {
headers["x-anthropic-additional-protection"] = "true"
}
return headers
}
// AttributionHeaderDisabled mirrors the official CLAUDE_CODE_ATTRIBUTION_HEADER=false toggle.
func AttributionHeaderDisabled() bool {
return envExplicitFalse("CLAUDE_CODE_ATTRIBUTION_HEADER")
}
// Beta header 常量
//
// 这里的常量对齐真实 Claude Code CLI 的最新流量(截至 2026-052.1.145 抓包)。
// 选型来源:反编译 2.1.145 binary 的内部 capability 注册表JM("name", "beta-X-Y-Z"))。
// 原因Anthropic 上游会基于 anthropic-beta 的完整集合判定请求来源;
// 缺少任何"官方 Claude Code 请求才会带"的 beta都会被降级到第三方额度
// 对应报错:`Third-party apps now draw from your extra usage, not your plan limits.`
const (
BetaOAuth = "oauth-2025-04-20"
BetaClaudeCode = "claude-code-20250219"
BetaInterleavedThinking = "interleaved-thinking-2025-05-14"
BetaTokenCounting = "token-counting-2024-11-01"
BetaContext1M = "context-1m-2025-08-07"
BetaFastMode = "fast-mode-2026-02-01"
BetaPromptCachingScope = "prompt-caching-scope-2026-01-05"
BetaEffort = "effort-2025-11-24"
BetaRedactThinking = "redact-thinking-2026-02-12"
BetaContextManagement = "context-management-2025-06-27"
BetaExtendedCacheTTL = "extended-cache-ttl-2025-04-11"
BetaTaskBudgets = "task-budgets-2026-03-13"
BetaStructuredOutputs = "structured-outputs-2025-12-15"
BetaAdvisor = "advisor-tool-2026-03-01"
BetaWebSearch = "web-search-2025-03-05"
// 2026-05 已 GA 的 beta保留常量供 client 显式传入时 merge 使用,
// 但 sub2api 默认不再注入到 OAuth/API-key mimic header 中。
BetaFineGrainedToolStreaming = "fine-grained-tool-streaming-2025-05-14"
BetaTokenEfficientTools = "token-efficient-tools-2025-02-19" // 修正原 2026-03-28 错日期
// 2.1.145 反编译新增(对齐官方 CLI 2.1.1xx 以来的流量)
BetaAdvancedToolUse = "advanced-tool-use-2025-11-20"
BetaToolSearchTool = "tool-search-tool-2025-10-19"
BetaMCPServers = "mcp-servers-2025-12-04"
BetaMCPClient = "mcp-client-2025-11-20"
BetaMidConversationSystem = "mid-conversation-system-2026-04-07"
BetaAFKMode = "afk-mode-2026-01-31"
BetaCacheDiagnosis = "cache-diagnosis-2026-04-07"
BetaContextHint = "context-hint-2026-04-09"
BetaEnvironments = "environments-2025-11-01"
BetaManagedAgents = "managed-agents-2026-04-01"
BetaSkills = "skills-2025-10-02"
BetaCompact = "compact-2026-01-12"
)
// DroppedBetas 是转发时需要从 anthropic-beta header 中移除的 beta token 列表。
// 这些 token 是客户端特有的,不应透传给上游 API。
var DroppedBetas = []string{}
// DefaultBetaHeader Claude Code 客户端默认的 anthropic-beta headerOAuth 账号,不含 context-1m
// 使用 GetOAuthBetaHeader(modelID) 获取含 context-1m 的 model-aware 版本。
// 已移除 GA 的 BetaFineGrainedToolStreaming。
const DefaultBetaHeader = BetaClaudeCode + "," + BetaOAuth + "," + BetaInterleavedThinking + "," + BetaRedactThinking + "," + BetaContextManagement + "," + BetaPromptCachingScope + "," + BetaEffort
// MessageBetaHeaderNoTools /v1/messages 在无工具时的 beta headerOAuth不含 context-1m
//
// NOTE: Claude Code OAuth credentials are scoped to Claude Code. When we "mimic"
// Claude Code for non-Claude-Code clients, we must include the claude-code beta
// even if the request doesn't use tools, otherwise upstream may reject the
// request as a non-Claude-Code API request.
const MessageBetaHeaderNoTools = BetaClaudeCode + "," + BetaOAuth + "," + BetaInterleavedThinking + "," + BetaRedactThinking + "," + BetaContextManagement + "," + BetaPromptCachingScope + "," + BetaEffort
// MessageBetaHeaderWithTools /v1/messages 在有工具时的 beta headerOAuth不含 context-1m
const MessageBetaHeaderWithTools = BetaClaudeCode + "," + BetaOAuth + "," + BetaInterleavedThinking + "," + BetaRedactThinking + "," + BetaContextManagement + "," + BetaPromptCachingScope + "," + BetaEffort
// CountTokensBetaHeader count_tokens 请求使用的 anthropic-beta header
const CountTokensBetaHeader = BetaClaudeCode + "," + BetaOAuth + "," + BetaInterleavedThinking + "," + BetaTokenCounting + "," + BetaContextManagement
// HaikuBetaHeader Haiku 模型使用的 anthropic-beta headerOAuth不含 claude-code / context-1m
const HaikuBetaHeader = BetaOAuth + "," + BetaInterleavedThinking + "," + BetaEffort
// APIKeyBetaHeader API-key 账号使用的 anthropic-beta header不含 oauth / context-1m
// 使用 GetAPIKeyBetaHeader(modelID) 获取含 context-1m 的 model-aware 版本。
// 已移除 GA 的 BetaFineGrainedToolStreaming。
const APIKeyBetaHeader = BetaClaudeCode + "," + BetaInterleavedThinking + "," + BetaEffort + "," + BetaPromptCachingScope
// APIKeyHaikuBetaHeader Haiku 模型在 API-key 账号下使用的 anthropic-beta header不含 oauth / claude-code
const APIKeyHaikuBetaHeader = BetaInterleavedThinking + "," + BetaEffort
// ModelSupports1M 判断模型是否支持 1M context window。
// 与 Claude Code CLI bundle 中 modelSupports1M 逻辑保持一致(截至 2.1.145 反编译):
//
// claude-sonnet-4 系列 和 claude-opus-4-6 支持 1M context。
func ModelSupports1M(modelID string) bool {
lower := strings.ToLower(strings.TrimSpace(modelID))
return strings.Contains(lower, "claude-sonnet-4") || strings.Contains(lower, "opus-4-6")
}
// GetOAuthBetaHeader 返回 OAuth 账号的 beta header。
// 仅当模型支持 1M context 时才包含 context-1m-2025-08-07。
// 已移除 GA 的 BetaFineGrainedToolStreaming。
func GetOAuthBetaHeader(modelID string) string {
if ModelSupports1M(modelID) {
return BetaClaudeCode + "," + BetaOAuth + "," + BetaInterleavedThinking + "," + BetaContext1M + "," + BetaRedactThinking + "," + BetaContextManagement + "," + BetaPromptCachingScope + "," + BetaEffort
}
return DefaultBetaHeader
}
// GetAPIKeyBetaHeader 返回 API-key 账号的 beta header。
// 仅当模型支持 1M context 时才包含 context-1m-2025-08-07。
// 已移除 GA 的 BetaFineGrainedToolStreaming。
func GetAPIKeyBetaHeader(modelID string) string {
if strings.Contains(strings.ToLower(modelID), "haiku") {
return APIKeyHaikuBetaHeader
}
if ModelSupports1M(modelID) {
return BetaClaudeCode + "," + BetaInterleavedThinking + "," + BetaContext1M + "," + BetaEffort + "," + BetaPromptCachingScope
}
return APIKeyBetaHeader
}
// DefaultCacheControlTTL 是网关代理为自己生成的 cache_control 块默认使用的 ttl。
// 真实 Claude Code CLI 当前使用 "1h",但本仓策略是"客户端透传 ttl 优先;
// 客户端缺省时统一使用 5m",这样既不浪费 1h 缓存额度,也保留客户端自定义能力。
const DefaultCacheControlTTL = "5m"
// CLICurrentVersion 是 sub2api 当前对外伪装的 Claude Code CLI 版本号(三段 semver
// 用于 billing attribution block 中的 cc_version=X.Y.Z.{fp} 前缀以及 fingerprint 计算。
// 必须与 DefaultHeaders["User-Agent"] 中的版本号严格一致;不一致会被 Anthropic 判第三方。
//
// 从 var 改为派生自 DefaultBundle.CLIVersion避免硬编码漂移。
var CLICurrentVersion = DefaultBundle.CLIVersion
// FullClaudeCodeMimicryBetas 返回最"像"真实 Claude Code CLI 2.1.145 的完整 beta 列表,
// 用于 OAuth 账号伪装成 Claude Code 时使用。
// 顺序基于反编译 2.1.145 binary 中的 capability 注册顺序,与真实 CLI 抓包匹配。
//
// 使用建议:
// - OAuth 账号 + 非 haiku追加这整份列表再按需保留 client 带来的 beta。
// - OAuth 账号 + haikuAnthropic 对 haiku 不做 third-party 判定,使用 HaikuBetaHeader 即可。
// - API-key 账号:不要使用本函数,参见 APIKeyBetaHeader。
// - 不默认加入 redact-thinking避免上游抹除 thinking 内容;客户端显式传入时由合并逻辑保留。
// - 不加入已 GA 的 BetaFineGrainedToolStreaming / BetaTokenEfficientTools
// 这些常量仍保留供客户端显式 merge 使用。
func FullClaudeCodeMimicryBetas() []string {
return []string{
// 核心身份 token
BetaClaudeCode,
BetaOAuth,
// 思考与工具
BetaInterleavedThinking,
BetaAdvancedToolUse,
BetaToolSearchTool,
// MCP
BetaMCPClient,
BetaMCPServers,
// 上下文与缓存
BetaPromptCachingScope,
BetaExtendedCacheTTL,
BetaContextManagement,
BetaContextHint,
BetaCacheDiagnosis,
// 系统提示与会话
BetaMidConversationSystem,
BetaCompact,
// 工作流
BetaEffort,
BetaTaskBudgets,
BetaAFKMode,
// 高级特性
BetaSkills,
BetaManagedAgents,
BetaEnvironments,
BetaAdvisor,
}
}
// DefaultHeaders 是 Claude Code 客户端默认请求头。
//
// 由 init() 在包加载时从 DefaultBundle 派生构造,避免与 DefaultBundle 漂移。
// 注:通过 ApplyFingerprintOverrides 更新指纹后,本变量会被重新构造。
var DefaultHeaders map[string]string
func init() {
DefaultHeaders = buildDefaultHeaders(DefaultDeviceProfile())
}
// Model 表示一个 Claude 模型
type Model struct {
ID string `json:"id"`
Type string `json:"type"`
DisplayName string `json:"display_name"`
CreatedAt string `json:"created_at"`
}
// DefaultModels Claude Code 客户端支持的默认模型列表
var DefaultModels = []Model{
{
ID: "claude-opus-4-5-20251101",
Type: "model",
DisplayName: "Claude Opus 4.5",
CreatedAt: "2025-11-01T00:00:00Z",
},
{
ID: "claude-opus-4-6",
Type: "model",
DisplayName: "Claude Opus 4.6",
CreatedAt: "2026-02-06T00:00:00Z",
},
{
ID: "claude-opus-4-7",
Type: "model",
DisplayName: "Claude Opus 4.7",
CreatedAt: "2026-04-17T00:00:00Z",
},
{
ID: "claude-sonnet-4-6",
Type: "model",
DisplayName: "Claude Sonnet 4.6",
CreatedAt: "2026-02-18T00:00:00Z",
},
{
ID: "claude-sonnet-4-5-20250929",
Type: "model",
DisplayName: "Claude Sonnet 4.5",
CreatedAt: "2025-09-29T00:00:00Z",
},
{
ID: "claude-haiku-4-5-20251001",
Type: "model",
DisplayName: "Claude Haiku 4.5",
CreatedAt: "2025-10-01T00:00:00Z",
},
}
// DefaultModelIDs 返回默认模型的 ID 列表
func DefaultModelIDs() []string {
ids := make([]string, len(DefaultModels))
for i, m := range DefaultModels {
ids[i] = m.ID
}
return ids
}
// DefaultTestModel 测试时使用的默认模型
const DefaultTestModel = "claude-sonnet-4-5-20250929"
// ModelIDOverrides Claude OAuth 请求需要的模型 ID 映射
var ModelIDOverrides = map[string]string{
"claude-sonnet-4-5": "claude-sonnet-4-5-20250929",
"claude-opus-4-5": "claude-opus-4-5-20251101",
"claude-haiku-4-5": "claude-haiku-4-5-20251001",
}
// ModelIDReverseOverrides 用于将上游模型 ID 还原为短名
var ModelIDReverseOverrides = map[string]string{
"claude-sonnet-4-5-20250929": "claude-sonnet-4-5",
"claude-opus-4-5-20251101": "claude-opus-4-5",
"claude-haiku-4-5-20251001": "claude-haiku-4-5",
}
// NormalizeModelID 根据 Claude OAuth 规则映射模型
func NormalizeModelID(id string) string {
if id == "" {
return id
}
if mapped, ok := ModelIDOverrides[id]; ok {
return mapped
}
return id
}
// DenormalizeModelID 将上游模型 ID 转换为短名
func DenormalizeModelID(id string) string {
if id == "" {
return id
}
if mapped, ok := ModelIDReverseOverrides[id]; ok {
return mapped
}
return id
}
// ApplyFingerprintOverrides 用配置覆盖默认指纹值(每个实例可设不同值)。
//
// 同步更新 DefaultBundle 与所有派生变量、DefaultHeaders、CLICurrentVersion
// 确保单一事实源。
func ApplyFingerprintOverrides(cliVersion, pkgVersion, runtimeVersion, os_, arch string) {
if cliVersion != "" {
v := strings.TrimSpace(cliVersion)
DefaultBundle.CLIVersion = v
DefaultCLIVersion = v
CLICurrentVersion = v
}
if pkgVersion != "" {
v := strings.TrimSpace(pkgVersion)
DefaultBundle.SDKVersion = v
DefaultStainlessPackageVersion = v
}
if runtimeVersion != "" {
v := strings.TrimSpace(runtimeVersion)
DefaultBundle.RuntimeVersion = v
DefaultStainlessRuntimeVersion = v
}
if os_ != "" {
v := strings.TrimSpace(os_)
DefaultBundle.DefaultOS = v
DefaultStainlessOS = v
}
if arch != "" {
v := strings.TrimSpace(arch)
DefaultBundle.DefaultArch = v
DefaultStainlessArch = v
}
DefaultHeaders = buildDefaultHeaders(DefaultDeviceProfile())
}