- Centralize Claude CLI fingerprint constants (UA, x-stainless-*) in pkg/claude with BuildCLI/CodeUserAgent helpers - Reuse constants in DefaultHeaders, identity_service defaults, and antigravity identity defaults to keep all callers in sync - Extend ClaudeCodeValidator to accept both claude-cli/ and claude-code/ UA prefixes (transport/helper requests use the latter) - Update related tests to cover the new UA prefix and version
223 lines
7.9 KiB
Go
223 lines
7.9 KiB
Go
// Package claude provides constants and helpers for Claude API integration.
|
||
package claude
|
||
|
||
import "strings"
|
||
|
||
// Claude Code 客户端相关常量
|
||
|
||
const (
|
||
DefaultCLIProductVersion = "2.1.88"
|
||
DefaultUserType = "external"
|
||
DefaultEntrypoint = "cli"
|
||
DefaultStainlessLang = "js"
|
||
DefaultStainlessPackageVersion = "0.74.0"
|
||
DefaultStainlessOS = "MacOS"
|
||
DefaultStainlessArch = "arm64"
|
||
DefaultStainlessRuntime = "node"
|
||
DefaultStainlessRuntimeVersion = "v24.3.0"
|
||
DefaultCLIUserAgent = "claude-cli/" + DefaultCLIProductVersion + " (" + DefaultUserType + ", " + DefaultEntrypoint + ")"
|
||
DefaultCodeUserAgent = "claude-code/" + DefaultCLIProductVersion
|
||
)
|
||
|
||
// Beta header 常量
|
||
const (
|
||
BetaOAuth = "oauth-2025-04-20"
|
||
BetaClaudeCode = "claude-code-20250219"
|
||
BetaInterleavedThinking = "interleaved-thinking-2025-05-14"
|
||
BetaFineGrainedToolStreaming = "fine-grained-tool-streaming-2025-05-14"
|
||
BetaTokenCounting = "token-counting-2024-11-01"
|
||
BetaContext1M = "context-1m-2025-08-07"
|
||
BetaFastMode = "fast-mode-2026-02-01"
|
||
BetaRedactThinking = "redact-thinking-2026-02-12"
|
||
BetaContextManagement = "context-management-2025-06-27"
|
||
BetaPromptCachingScope = "prompt-caching-scope-2026-01-05"
|
||
BetaEffort = "effort-2025-11-24"
|
||
)
|
||
|
||
// DroppedBetas 是转发时需要从 anthropic-beta header 中移除的 beta token 列表。
|
||
// 这些 token 是客户端特有的,不应透传给上游 API。
|
||
var DroppedBetas = []string{}
|
||
|
||
// DefaultBetaHeader Claude Code 客户端默认的 anthropic-beta header
|
||
const DefaultBetaHeader = BetaClaudeCode + "," + BetaOAuth + "," + BetaInterleavedThinking + "," + BetaFineGrainedToolStreaming
|
||
|
||
// MessageBetaHeaderNoTools /v1/messages 在无工具时的 beta header
|
||
//
|
||
// 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
|
||
|
||
// MessageBetaHeaderWithTools /v1/messages 在有工具时的 beta header
|
||
const MessageBetaHeaderWithTools = BetaClaudeCode + "," + BetaOAuth + "," + BetaInterleavedThinking
|
||
|
||
// CountTokensBetaHeader count_tokens 请求使用的 anthropic-beta header
|
||
const CountTokensBetaHeader = BetaClaudeCode + "," + BetaOAuth + "," + BetaInterleavedThinking + "," + BetaTokenCounting
|
||
|
||
// HaikuBetaHeader Haiku 模型使用的 anthropic-beta header(不需要 claude-code beta)
|
||
const HaikuBetaHeader = BetaOAuth + "," + BetaInterleavedThinking
|
||
|
||
// APIKeyBetaHeader API-key 账号建议使用的 anthropic-beta header(不包含 oauth)
|
||
const APIKeyBetaHeader = BetaClaudeCode + "," + BetaInterleavedThinking + "," + BetaFineGrainedToolStreaming
|
||
|
||
// APIKeyHaikuBetaHeader Haiku 模型在 API-key 账号下使用的 anthropic-beta header(不包含 oauth / claude-code)
|
||
const APIKeyHaikuBetaHeader = BetaInterleavedThinking
|
||
|
||
// DefaultHeaders 是 Claude Code 客户端默认请求头。
|
||
var DefaultHeaders = 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": DefaultCLIUserAgent,
|
||
"X-Stainless-Lang": DefaultStainlessLang,
|
||
"X-Stainless-Package-Version": DefaultStainlessPackageVersion,
|
||
"X-Stainless-OS": DefaultStainlessOS,
|
||
"X-Stainless-Arch": DefaultStainlessArch,
|
||
"X-Stainless-Runtime": DefaultStainlessRuntime,
|
||
"X-Stainless-Runtime-Version": DefaultStainlessRuntimeVersion,
|
||
"X-Stainless-Retry-Count": "0",
|
||
"X-Stainless-Timeout": "600",
|
||
"X-App": "cli",
|
||
"Anthropic-Dangerous-Direct-Browser-Access": "true",
|
||
}
|
||
|
||
// BuildCLIUserAgent returns the current Claude Code API client user-agent.
|
||
func BuildCLIUserAgent(version, userType, entrypoint string) string {
|
||
version = strings.TrimSpace(version)
|
||
if version == "" {
|
||
version = DefaultCLIProductVersion
|
||
}
|
||
userType = strings.TrimSpace(userType)
|
||
if userType == "" {
|
||
userType = DefaultUserType
|
||
}
|
||
entrypoint = strings.TrimSpace(entrypoint)
|
||
if entrypoint == "" {
|
||
entrypoint = DefaultEntrypoint
|
||
}
|
||
return "claude-cli/" + version + " (" + userType + ", " + entrypoint + ")"
|
||
}
|
||
|
||
// BuildCodeUserAgent returns the current Claude Code transport/helper user-agent.
|
||
func BuildCodeUserAgent(version string) string {
|
||
version = strings.TrimSpace(version)
|
||
if version == "" {
|
||
version = DefaultCLIProductVersion
|
||
}
|
||
return "claude-code/" + version
|
||
}
|
||
|
||
// ApplyFingerprintOverrides 用配置覆盖默认指纹值(每个实例可设不同值)
|
||
// cliVersion: Claude CLI 版本(如 "2.1.81")
|
||
// pkgVersion: SDK 版本(如 "0.80.0")
|
||
// runtimeVersion: Node.js 版本(如 "v24.13.0")
|
||
// os_: 操作系统(如 "Linux")
|
||
// arch: 架构(如 "arm64")
|
||
func ApplyFingerprintOverrides(cliVersion, pkgVersion, runtimeVersion, os_, arch string) {
|
||
if cliVersion != "" {
|
||
DefaultHeaders["User-Agent"] = BuildCLIUserAgent(cliVersion, "", "")
|
||
}
|
||
if pkgVersion != "" {
|
||
DefaultHeaders["X-Stainless-Package-Version"] = pkgVersion
|
||
}
|
||
if runtimeVersion != "" {
|
||
DefaultHeaders["X-Stainless-Runtime-Version"] = runtimeVersion
|
||
}
|
||
if os_ != "" {
|
||
DefaultHeaders["X-Stainless-OS"] = os_
|
||
}
|
||
if arch != "" {
|
||
DefaultHeaders["X-Stainless-Arch"] = arch
|
||
}
|
||
}
|
||
|
||
// 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-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
|
||
}
|