// Package claude provides constants and helpers for Claude API integration. package claude import ( "fmt" "os" "strings" ) // Claude Code 客户端相关常量 // // 反编译 @anthropic-ai/claude-code 2.1.145(Bun 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-05,2.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 header(OAuth 账号,不含 context-1m) // 使用 GetOAuthBetaHeader(modelID) 获取含 context-1m 的 model-aware 版本。 // 已移除 GA 的 BetaFineGrainedToolStreaming。 const DefaultBetaHeader = BetaClaudeCode + "," + BetaOAuth + "," + BetaInterleavedThinking + "," + BetaRedactThinking + "," + BetaContextManagement + "," + BetaPromptCachingScope + "," + BetaEffort // MessageBetaHeaderNoTools /v1/messages 在无工具时的 beta header(OAuth,不含 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 header(OAuth,不含 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 header(OAuth,不含 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 账号 + haiku:Anthropic 对 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-opus-4-8", Type: "model", DisplayName: "Claude Opus 4.8", CreatedAt: "2026-05-29T00: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()) }