package executor import ( "context" "crypto/sha256" "encoding/hex" "fmt" "strings" "github.com/router-for-me/CLIProxyAPI/v7/internal/config" "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" "github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/executor/helps" cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" "github.com/tidwall/sjson" "github.com/gin-gonic/gin") // getClientUserAgent extracts the client User-Agent from the gin context.func getClientUserAgent(ctx context.Context) string { if ginCtx, ok := ctx.Value("gin").(*gin.Context); ok && ginCtx != nil && ginCtx.Request != nil { return ginCtx.GetHeader("User-Agent") } return ""} // parseEntrypointFromUA extracts the entrypoint from a Claude Code User-Agent.// Format: "claude-cli/x.y.z (external, cli)" → "cli"// Format: "claude-cli/x.y.z (external, vscode)" → "vscode"// Returns "cli" if parsing fails or UA is not Claude Code.func parseEntrypointFromUA(userAgent string) string { // Find content inside parentheses start := strings.Index(userAgent, "(") end := strings.LastIndex(userAgent, ")") if start < 0 || end <= start { return "cli" } inner := userAgent[start+1 : end] // Split by comma, take the second part (entrypoint is at index 1, after USER_TYPE) // Format: "(USER_TYPE, ENTRYPOINT[, extra...])" parts := strings.Split(inner, ",") if len(parts) >= 2 { ep := strings.TrimSpace(parts[1]) if ep != "" { return ep } } return "cli"} // getWorkloadFromContext extracts workload identifier from the gin request headers.func getWorkloadFromContext(ctx context.Context) string { if ginCtx, ok := ctx.Value("gin").(*gin.Context); ok && ginCtx != nil && ginCtx.Request != nil { return strings.TrimSpace(ginCtx.GetHeader("X-CPA-Claude-Workload")) } return ""} // getCloakConfigFromAuth extracts cloak configuration from the auth's attributes,// falling back to its stored metadata (the raw OAuth/token JSON). Returns// (cloakMode, strictMode, sensitiveWords, cacheUserID); an empty cloakMode means// the credential did not explicitly configure a mode.func getCloakConfigFromAuth(auth *cliproxyauth.Auth) (cloakMode string, strictMode bool, sensitiveWords []string, cacheUserID bool) { if auth == nil { return "", false, nil, false } // lookupCloakAttr prefers the executor-facing Attributes, then falls back to the // raw metadata blob (e.g. the OAuth/token JSON) so file-based credentials can // carry cloak settings without a matching claude-api-key config entry. lookupCloakAttr := func(key string) string { if auth.Attributes != nil { if value := strings.TrimSpace(auth.Attributes[key]); value != "" { return value } } if auth.Metadata != nil { if value, ok := auth.Metadata[key].(string); ok { return strings.TrimSpace(value) } } return "" } // An empty cloakMode means this credential did not explicitly configure a mode, // allowing the caller to fall back to the global/default behavior. cloakMode = lookupCloakAttr("cloak_mode") strictMode = strings.EqualFold(lookupCloakAttr("cloak_strict_mode"), "true") if wordsStr := lookupCloakAttr("cloak_sensitive_words"); wordsStr != "" { sensitiveWords = strings.Split(wordsStr, ",") for i := range sensitiveWords { sensitiveWords[i] = strings.TrimSpace(sensitiveWords[i]) } } cacheUserID = strings.EqualFold(lookupCloakAttr("cloak_cache_user_id"), "true") return cloakMode, strictMode, sensitiveWords, cacheUserID} // injectFakeUserID generates and injects a fake user ID into the request metadata.// When useCache is false, a new user ID is generated for every call.func injectFakeUserID(ctx context.Context, payload []byte, apiKey string, useCache bool) ([]byte, error) { generateID := func() (string, error) { if useCache { return helps.CachedUserIDRequired(ctx, apiKey) } return helps.GenerateFakeUserID(), nil } metadata := gjson.GetBytes(payload, "metadata") if !metadata.Exists() { userID, errUserID := generateID() if errUserID != nil { return nil, errUserID } payload, _ = sjson.SetBytes(payload, "metadata.user_id", userID) return payload, nil } existingUserID := gjson.GetBytes(payload, "metadata.user_id").String() if existingUserID == "" || !helps.IsValidUserID(existingUserID) { userID, errUserID := generateID() if errUserID != nil { return nil, errUserID } payload, _ = sjson.SetBytes(payload, "metadata.user_id", userID) } return payload, nil} // fingerprintSalt is the salt used by Claude Code to compute the 3-char build fingerprint.const fingerprintSalt = "59cf53e54c78" // computeFingerprint computes the 3-char build fingerprint that Claude Code embeds in cc_version.// Algorithm: SHA256(salt + messageText[4] + messageText[7] + messageText[20] + version)[:3]func computeFingerprint(messageText, version string) string { indices := [3]int{4, 7, 20} runes := []rune(messageText) var sb strings.Builder for _, idx := range indices { if idx < len(runes) { sb.WriteRune(runes[idx]) } else { sb.WriteRune('0') } } input := fingerprintSalt + sb.String() + version h := sha256.Sum256([]byte(input)) return hex.EncodeToString(h[:])[:3]} // generateBillingHeader creates the x-anthropic-billing-header text block that// real Claude Code prepends to every system prompt array.// Format: x-anthropic-billing-header: cc_version=<ver>.<build>; cc_entrypoint=<ep>; cch=<hash>; [cc_workload=<wl>;]func generateBillingHeader(payload []byte, experimentalCCHSigning bool, version, messageText, entrypoint, workload string) string { if entrypoint == "" { entrypoint = "cli" } buildHash := computeFingerprint(messageText, version) workloadPart := "" if workload != "" { workloadPart = fmt.Sprintf(" cc_workload=%s;", workload) } if experimentalCCHSigning { return fmt.Sprintf("x-anthropic-billing-header: cc_version=%s.%s; cc_entrypoint=%s; cch=00000;%s", version, buildHash, entrypoint, workloadPart) } // Generate a deterministic cch hash from the payload content (system + messages + tools). h := sha256.Sum256(payload) cch := hex.EncodeToString(h[:])[:5] return fmt.Sprintf("x-anthropic-billing-header: cc_version=%s.%s; cc_entrypoint=%s; cch=%s;%s", version, buildHash, entrypoint, cch, workloadPart)} func checkSystemInstructionsWithMode(payload []byte, strictMode bool) []byte { return checkSystemInstructionsWithSigningMode(payload, strictMode, false, false, "2.1.63", "", "")} // checkSystemInstructionsWithSigningMode injects Claude Code-style system blocks://// system[0]: billing header (no cache_control)// system[1]: agent identifier (cache_control ephemeral, scope=org)// system[2]: core intro prompt (cache_control ephemeral, scope=global)// system[3]: system instructions (no cache_control)// system[4]: doing tasks (no cache_control)// system[5]: user system messages moved to first user messagefunc checkSystemInstructionsWithSigningMode(payload []byte, strictMode bool, experimentalCCHSigning bool, oauthMode bool, version, entrypoint, workload string) []byte { system := gjson.GetBytes(payload, "system") // Extract original message text for fingerprint computation (before billing injection). // Use the first system text block's content as the fingerprint source. messageText := "" if system.IsArray() { system.ForEach(func(_, part gjson.Result) bool { if part.Get("type").String() == "text" { messageText = part.Get("text").String() return false } return true }) } else if system.Type == gjson.String { messageText = system.String() } // Skip if already injected firstText := gjson.GetBytes(payload, "system.0.text").String() if strings.HasPrefix(firstText, "x-anthropic-billing-header:") { return payload } billingText := generateBillingHeader(payload, experimentalCCHSigning, version, messageText, entrypoint, workload) billingBlock := buildTextBlock(billingText, nil) // Build system blocks matching real Claude Code structure. // Important: Claude Code's internal cacheScope='org' does NOT serialize to // scope='org' in the API request. Only scope='global' is sent explicitly. // The system prompt prefix block is sent without cache_control. agentBlock := buildTextBlock("You are Claude Code, Anthropic's official CLI for Claude.", nil) staticPrompt := strings.Join([]string{ helps.ClaudeCodeIntro, helps.ClaudeCodeSystem, helps.ClaudeCodeDoingTasks, helps.ClaudeCodeToneAndStyle, helps.ClaudeCodeOutputEfficiency, }, "\n\n") staticBlock := buildTextBlock(staticPrompt, nil) systemResult := "[" + billingBlock + "," + agentBlock + "," + staticBlock + "]" payload, _ = sjson.SetRawBytes(payload, "system", []byte(systemResult)) // Collect user system instructions and prepend to first user message if !strictMode { var userSystemParts []string if system.IsArray() { system.ForEach(func(_, part gjson.Result) bool { if part.Get("type").String() == "text" { txt := strings.TrimSpace(part.Get("text").String()) if txt != "" { userSystemParts = append(userSystemParts, txt) } } return true }) } else if system.Type == gjson.String && strings.TrimSpace(system.String()) != "" { userSystemParts = append(userSystemParts, strings.TrimSpace(system.String())) } if len(userSystemParts) > 0 { combined := strings.Join(userSystemParts, "\n\n") if oauthMode { combined = sanitizeForwardedSystemPrompt(combined) } if strings.TrimSpace(combined) != "" { payload = prependToFirstUserMessage(payload, combined) } } } return payload} // sanitizeForwardedSystemPrompt reduces forwarded third-party system context to a// tiny neutral reminder for Claude OAuth cloaking. The goal is to preserve only// the minimum tool/task guidance while removing virtually all client-specific// prompt structure that Anthropic may classify as third-party agent traffic.func sanitizeForwardedSystemPrompt(text string) string { if strings.TrimSpace(text) == "" { return "" } return strings.TrimSpace(`Use the available tools when needed to help with software engineering tasks.Keep responses concise and focused on the user's request.Prefer acting on the user's task over describing product-specific workflows.`)} // buildTextBlock constructs a JSON text block object with proper escaping.// Uses sjson.SetBytes to handle multi-line text, quotes, and control characters.// cacheControl is optional; pass nil to omit cache_control.func buildTextBlock(text string, cacheControl map[string]string) string { block := []byte(`{"type":"text"}`) block, _ = sjson.SetBytes(block, "text", text) if cacheControl != nil && len(cacheControl) > 0 { // Build cache_control JSON manually to avoid sjson map marshaling issues. // sjson.SetBytes with map[string]string may not produce expected structure. cc := `{"type":"ephemeral"` if t, ok := cacheControl["ttl"]; ok { cc += fmt.Sprintf(`,"ttl":"%s"`, t) } cc += "}" block, _ = sjson.SetRawBytes(block, "cache_control", []byte(cc)) } return string(block)} // prependToFirstUserMessage injects text content into the first user message.// This avoids putting non-Claude-Code system instructions in system[] which// triggers Anthropic's extra usage billing for OAuth-proxied requests.func prependToFirstUserMessage(payload []byte, text string) []byte { messages := gjson.GetBytes(payload, "messages") if !messages.Exists() || !messages.IsArray() { return payload } // Find the first user message index firstUserIdx := -1 messages.ForEach(func(idx, msg gjson.Result) bool { if msg.Get("role").String() == "user" { firstUserIdx = int(idx.Int()) return false } return true }) if firstUserIdx < 0 { return payload } prefixBlock := fmt.Sprintf(`<system-reminder>As you answer the user's questions, you can use the following context from the system:%s IMPORTANT: this context may or may not be relevant to your tasks. You should not respond to this context unless it is highly relevant to your task.</system-reminder>`, text) contentPath := fmt.Sprintf("messages.%d.content", firstUserIdx) content := gjson.GetBytes(payload, contentPath) if content.IsArray() { newBlock := fmt.Sprintf(`{"type":"text","text":%q}`, prefixBlock) var newArray string switch { case content.Raw == "[]" || content.Raw == "": newArray = "[" + newBlock + "]" case leadsWithToolResult(content): // Anthropic requires the user message that immediately follows an // assistant tool_use turn to lead with its tool_result blocks. // Append the reminder so those blocks stay at the head. if trimmed := strings.TrimRight(content.Raw, " \t\r\n"); strings.HasSuffix(trimmed, "]") { newArray = trimmed[:len(trimmed)-1] + "," + newBlock + "]" } else { newArray = "[" + newBlock + "," + content.Raw[1:] } default: newArray = "[" + newBlock + "," + content.Raw[1:] } payload, _ = sjson.SetRawBytes(payload, contentPath, []byte(newArray)) } else if content.Type == gjson.String { newText := prefixBlock + content.String() payload, _ = sjson.SetBytes(payload, contentPath, newText) } return payload} // leadsWithToolResult reports whether a message content array starts with a// tool_result block. Such a message answers a preceding assistant tool_use turn,// and Anthropic requires its tool_result blocks to remain first.func leadsWithToolResult(content gjson.Result) bool { first := content.Get("0") return first.Exists() && first.Get("type").String() == "tool_result"} // applyCloaking applies cloaking transformations to the payload based on config and client.// Cloaking includes: system prompt injection, fake user ID, and sensitive word obfuscation.func applyCloaking(ctx context.Context, cfg *config.Config, auth *cliproxyauth.Auth, payload []byte, model string, apiKey string) ([]byte, error) { clientUserAgent := getClientUserAgent(ctx) // Enable cch signing for OAuth tokens by default (not just experimental flag). oauthToken := isClaudeOAuthToken(apiKey) useCCHSigning := oauthToken || experimentalCCHSigningEnabled(cfg, auth) // Get cloak config from ClaudeKey configuration cloakCfg := resolveClaudeKeyCloakConfig(cfg, auth) attrMode, attrStrict, attrWords, attrCache := getCloakConfigFromAuth(auth) // Determine cloak settings. Precedence (low -> high): // built-in "auto" default // -> global disable-claude-cloak-mode switch (forces "never") // -> per-credential settings from auth attributes/metadata // -> per claude-api-key cloak config cloakMode := "auto" if cfg != nil && cfg.DisableClaudeCloakMode { cloakMode = "never" } strictMode := attrStrict sensitiveWords := attrWords cacheUserID := attrCache if attrMode != "" { cloakMode = attrMode } if cloakCfg != nil { if mode := strings.TrimSpace(cloakCfg.Mode); mode != "" { cloakMode = mode } if cloakCfg.StrictMode { strictMode = true } if len(cloakCfg.SensitiveWords) > 0 { sensitiveWords = cloakCfg.SensitiveWords } if cloakCfg.CacheUserID != nil { cacheUserID = *cloakCfg.CacheUserID } } // Determine if cloaking should be applied if !helps.ShouldCloak(cloakMode, clientUserAgent) { return payload, nil } // Skip system instructions for claude-3-5-haiku models if !strings.HasPrefix(model, "claude-3-5-haiku") { billingVersion := helps.DefaultClaudeVersion(cfg) entrypoint := parseEntrypointFromUA(clientUserAgent) workload := getWorkloadFromContext(ctx) payload = checkSystemInstructionsWithSigningMode(payload, strictMode, useCCHSigning, oauthToken, billingVersion, entrypoint, workload) } // Inject fake user ID var errFakeUserID error payload, errFakeUserID = injectFakeUserID(ctx, payload, apiKey, cacheUserID) if errFakeUserID != nil { return nil, errFakeUserID } // Apply sensitive word obfuscation if len(sensitiveWords) > 0 { matcher := helps.BuildSensitiveWordMatcher(sensitiveWords) payload = helps.ObfuscateSensitiveWords(payload, matcher) } return payload, nil} // ensureCacheControl injects cache_control breakpoints into the payload for optimal prompt caching.// According to Anthropic's documentation, cache prefixes are created in order: tools -> system -> messages.// This function adds cache_control to:// 1. The LAST non-deferred tool in the tools array (caches all preceding tool definitions)// 2. The LAST system prompt element// 3. The SECOND-TO-LAST user turn (caches conversation history for multi-turn)//// Up to 4 cache breakpoints are allowed per request. Tools, System, and Messages are INDEPENDENT breakpoints.// This enables up to 90% cost reduction on cached tokens (cache read = 0.1x base price).// See: https://docs.anthropic.com/en/docs/build-with-claude/prompt-cachingfunc ensureCacheControl(payload []byte) []byte { // 1. Inject cache_control into the LAST non-deferred tool // Tools are cached first in the hierarchy, so this is the most important breakpoint. payload = injectToolsCacheControl(payload) // 2. Inject cache_control into the LAST system prompt element // System is the second level in the cache hierarchy. payload = injectSystemCacheControl(payload) // 3. Inject cache_control into messages for multi-turn conversation caching // This caches the conversation history up to the second-to-last user turn. payload = injectMessagesCacheControl(payload) return payload} func countCacheControls(payload []byte) int { count := 0 // Check system system := gjson.GetBytes(payload, "system") if system.IsArray() { system.ForEach(func(_, item gjson.Result) bool { if item.Get("cache_control").Exists() { count++ } return true }) } // Check tools tools := gjson.GetBytes(payload, "tools") if tools.IsArray() { tools.ForEach(func(_, item gjson.Result) bool { if item.Get("cache_control").Exists() { count++ } return true }) } // Check messages messages := gjson.GetBytes(payload, "messages") if messages.IsArray() { messages.ForEach(func(_, msg gjson.Result) bool { content := msg.Get("content") if content.IsArray() { content.ForEach(func(_, item gjson.Result) bool { if item.Get("cache_control").Exists() { count++Lines 1–500 of 978. Load more
package executor import ( "context" "crypto/sha256" "encoding/hex" "fmt" "strings" "github.com/router-for-me/CLIProxyAPI/v7/internal/config" "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" "github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/executor/helps" cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" "github.com/tidwall/sjson" "github.com/gin-gonic/gin" ) // getClientUserAgent extracts the client User-Agent from the gin context. func getClientUserAgent(ctx context.Context) string { if ginCtx, ok := ctx.Value("gin").(*gin.Context); ok && ginCtx != nil && ginCtx.Request != nil { return ginCtx.GetHeader("User-Agent") } return "" } // parseEntrypointFromUA extracts the entrypoint from a Claude Code User-Agent. // Format: "claude-cli/x.y.z (external, cli)" → "cli" // Format: "claude-cli/x.y.z (external, vscode)" → "vscode" // Returns "cli" if parsing fails or UA is not Claude Code. func parseEntrypointFromUA(userAgent string) string { // Find content inside parentheses start := strings.Index(userAgent, "(") end := strings.LastIndex(userAgent, ")") if start < 0 || end <= start { return "cli" } inner := userAgent[start+1 : end] // Split by comma, take the second part (entrypoint is at index 1, after USER_TYPE) // Format: "(USER_TYPE, ENTRYPOINT[, extra...])" parts := strings.Split(inner, ",") if len(parts) >= 2 { ep := strings.TrimSpace(parts[1]) if ep != "" { return ep } } return "cli" } // getWorkloadFromContext extracts workload identifier from the gin request headers. func getWorkloadFromContext(ctx context.Context) string { if ginCtx, ok := ctx.Value("gin").(*gin.Context); ok && ginCtx != nil && ginCtx.Request != nil { return strings.TrimSpace(ginCtx.GetHeader("X-CPA-Claude-Workload")) } return "" } // getCloakConfigFromAuth extracts cloak configuration from the auth's attributes, // falling back to its stored metadata (the raw OAuth/token JSON). Returns // (cloakMode, strictMode, sensitiveWords, cacheUserID); an empty cloakMode means // the credential did not explicitly configure a mode. func getCloakConfigFromAuth(auth *cliproxyauth.Auth) (cloakMode string, strictMode bool, sensitiveWords []string, cacheUserID bool) { if auth == nil { return "", false, nil, false } // lookupCloakAttr prefers the executor-facing Attributes, then falls back to the // raw metadata blob (e.g. the OAuth/token JSON) so file-based credentials can // carry cloak settings without a matching claude-api-key config entry. lookupCloakAttr := func(key string) string { if auth.Attributes != nil { if value := strings.TrimSpace(auth.Attributes[key]); value != "" { return value } } if auth.Metadata != nil { if value, ok := auth.Metadata[key].(string); ok { return strings.TrimSpace(value) } } return "" } // An empty cloakMode means this credential did not explicitly configure a mode, // allowing the caller to fall back to the global/default behavior. cloakMode = lookupCloakAttr("cloak_mode") strictMode = strings.EqualFold(lookupCloakAttr("cloak_strict_mode"), "true") if wordsStr := lookupCloakAttr("cloak_sensitive_words"); wordsStr != "" { sensitiveWords = strings.Split(wordsStr, ",") for i := range sensitiveWords { sensitiveWords[i] = strings.TrimSpace(sensitiveWords[i]) } } cacheUserID = strings.EqualFold(lookupCloakAttr("cloak_cache_user_id"), "true") return cloakMode, strictMode, sensitiveWords, cacheUserID } // injectFakeUserID generates and injects a fake user ID into the request metadata. // When useCache is false, a new user ID is generated for every call. func injectFakeUserID(ctx context.Context, payload []byte, apiKey string, useCache bool) ([]byte, error) { generateID := func() (string, error) { if useCache { return helps.CachedUserIDRequired(ctx, apiKey) } return helps.GenerateFakeUserID(), nil } metadata := gjson.GetBytes(payload, "metadata") if !metadata.Exists() { userID, errUserID := generateID() if errUserID != nil { return nil, errUserID } payload, _ = sjson.SetBytes(payload, "metadata.user_id", userID) return payload, nil } existingUserID := gjson.GetBytes(payload, "metadata.user_id").String() if existingUserID == "" || !helps.IsValidUserID(existingUserID) { userID, errUserID := generateID() if errUserID != nil { return nil, errUserID } payload, _ = sjson.SetBytes(payload, "metadata.user_id", userID) } return payload, nil } // fingerprintSalt is the salt used by Claude Code to compute the 3-char build fingerprint. const fingerprintSalt = "59cf53e54c78" // computeFingerprint computes the 3-char build fingerprint that Claude Code embeds in cc_version. // Algorithm: SHA256(salt + messageText[4] + messageText[7] + messageText[20] + version)[:3] func computeFingerprint(messageText, version string) string { indices := [3]int{4, 7, 20} runes := []rune(messageText) var sb strings.Builder for _, idx := range indices { if idx < len(runes) { sb.WriteRune(runes[idx]) } else { sb.WriteRune('0') } } input := fingerprintSalt + sb.String() + version h := sha256.Sum256([]byte(input)) return hex.EncodeToString(h[:])[:3] } // generateBillingHeader creates the x-anthropic-billing-header text block that // real Claude Code prepends to every system prompt array. // Format: x-anthropic-billing-header: cc_version=<ver>.<build>; cc_entrypoint=<ep>; cch=<hash>; [cc_workload=<wl>;] func generateBillingHeader(payload []byte, experimentalCCHSigning bool, version, messageText, entrypoint, workload string) string { if entrypoint == "" { entrypoint = "cli" } buildHash := computeFingerprint(messageText, version) workloadPart := "" if workload != "" { workloadPart = fmt.Sprintf(" cc_workload=%s;", workload) } if experimentalCCHSigning { return fmt.Sprintf("x-anthropic-billing-header: cc_version=%s.%s; cc_entrypoint=%s; cch=00000;%s", version, buildHash, entrypoint, workloadPart) } // Generate a deterministic cch hash from the payload content (system + messages + tools). h := sha256.Sum256(payload) cch := hex.EncodeToString(h[:])[:5] return fmt.Sprintf("x-anthropic-billing-header: cc_version=%s.%s; cc_entrypoint=%s; cch=%s;%s", version, buildHash, entrypoint, cch, workloadPart) } func checkSystemInstructionsWithMode(payload []byte, strictMode bool) []byte { return checkSystemInstructionsWithSigningMode(payload, strictMode, false, false, "2.1.63", "", "") } // checkSystemInstructionsWithSigningMode injects Claude Code-style system blocks: // // system[0]: billing header (no cache_control) // system[1]: agent identifier (cache_control ephemeral, scope=org) // system[2]: core intro prompt (cache_control ephemeral, scope=global) // system[3]: system instructions (no cache_control) // system[4]: doing tasks (no cache_control) // system[5]: user system messages moved to first user message func checkSystemInstructionsWithSigningMode(payload []byte, strictMode bool, experimentalCCHSigning bool, oauthMode bool, version, entrypoint, workload string) []byte { system := gjson.GetBytes(payload, "system") // Extract original message text for fingerprint computation (before billing injection). // Use the first system text block's content as the fingerprint source. messageText := "" if system.IsArray() { system.ForEach(func(_, part gjson.Result) bool { if part.Get("type").String() == "text" { messageText = part.Get("text").String() return false } return true }) } else if system.Type == gjson.String { messageText = system.String() } // Skip if already injected firstText := gjson.GetBytes(payload, "system.0.text").String() if strings.HasPrefix(firstText, "x-anthropic-billing-header:") { return payload } billingText := generateBillingHeader(payload, experimentalCCHSigning, version, messageText, entrypoint, workload) billingBlock := buildTextBlock(billingText, nil) // Build system blocks matching real Claude Code structure. // Important: Claude Code's internal cacheScope='org' does NOT serialize to // scope='org' in the API request. Only scope='global' is sent explicitly. // The system prompt prefix block is sent without cache_control. agentBlock := buildTextBlock("You are Claude Code, Anthropic's official CLI for Claude.", nil) staticPrompt := strings.Join([]string{ helps.ClaudeCodeIntro, helps.ClaudeCodeSystem, helps.ClaudeCodeDoingTasks, helps.ClaudeCodeToneAndStyle, helps.ClaudeCodeOutputEfficiency, }, "\n\n") staticBlock := buildTextBlock(staticPrompt, nil) systemResult := "[" + billingBlock + "," + agentBlock + "," + staticBlock + "]" payload, _ = sjson.SetRawBytes(payload, "system", []byte(systemResult)) // Collect user system instructions and prepend to first user message if !strictMode { var userSystemParts []string if system.IsArray() { system.ForEach(func(_, part gjson.Result) bool { if part.Get("type").String() == "text" { txt := strings.TrimSpace(part.Get("text").String()) if txt != "" { userSystemParts = append(userSystemParts, txt) } } return true }) } else if system.Type == gjson.String && strings.TrimSpace(system.String()) != "" { userSystemParts = append(userSystemParts, strings.TrimSpace(system.String())) } if len(userSystemParts) > 0 { combined := strings.Join(userSystemParts, "\n\n") if oauthMode { combined = sanitizeForwardedSystemPrompt(combined) } if strings.TrimSpace(combined) != "" { payload = prependToFirstUserMessage(payload, combined) } } } return payload } // sanitizeForwardedSystemPrompt reduces forwarded third-party system context to a // tiny neutral reminder for Claude OAuth cloaking. The goal is to preserve only // the minimum tool/task guidance while removing virtually all client-specific // prompt structure that Anthropic may classify as third-party agent traffic. func sanitizeForwardedSystemPrompt(text string) string { if strings.TrimSpace(text) == "" { return "" } return strings.TrimSpace(`Use the available tools when needed to help with software engineering tasks. Keep responses concise and focused on the user's request. Prefer acting on the user's task over describing product-specific workflows.`) } // buildTextBlock constructs a JSON text block object with proper escaping. // Uses sjson.SetBytes to handle multi-line text, quotes, and control characters. // cacheControl is optional; pass nil to omit cache_control. func buildTextBlock(text string, cacheControl map[string]string) string { block := []byte(`{"type":"text"}`) block, _ = sjson.SetBytes(block, "text", text) if cacheControl != nil && len(cacheControl) > 0 { // Build cache_control JSON manually to avoid sjson map marshaling issues. // sjson.SetBytes with map[string]string may not produce expected structure. cc := `{"type":"ephemeral"` if t, ok := cacheControl["ttl"]; ok { cc += fmt.Sprintf(`,"ttl":"%s"`, t) } cc += "}" block, _ = sjson.SetRawBytes(block, "cache_control", []byte(cc)) } return string(block) } // prependToFirstUserMessage injects text content into the first user message. // This avoids putting non-Claude-Code system instructions in system[] which // triggers Anthropic's extra usage billing for OAuth-proxied requests. func prependToFirstUserMessage(payload []byte, text string) []byte { messages := gjson.GetBytes(payload, "messages") if !messages.Exists() || !messages.IsArray() { return payload } // Find the first user message index firstUserIdx := -1 messages.ForEach(func(idx, msg gjson.Result) bool { if msg.Get("role").String() == "user" { firstUserIdx = int(idx.Int()) return false } return true }) if firstUserIdx < 0 { return payload } prefixBlock := fmt.Sprintf(`<system-reminder> As you answer the user's questions, you can use the following context from the system: %s IMPORTANT: this context may or may not be relevant to your tasks. You should not respond to this context unless it is highly relevant to your task. </system-reminder> `, text) contentPath := fmt.Sprintf("messages.%d.content", firstUserIdx) content := gjson.GetBytes(payload, contentPath) if content.IsArray() { newBlock := fmt.Sprintf(`{"type":"text","text":%q}`, prefixBlock) var newArray string switch { case content.Raw == "[]" || content.Raw == "": newArray = "[" + newBlock + "]" case leadsWithToolResult(content): // Anthropic requires the user message that immediately follows an // assistant tool_use turn to lead with its tool_result blocks. // Append the reminder so those blocks stay at the head. if trimmed := strings.TrimRight(content.Raw, " \t\r\n"); strings.HasSuffix(trimmed, "]") { newArray = trimmed[:len(trimmed)-1] + "," + newBlock + "]" } else { newArray = "[" + newBlock + "," + content.Raw[1:] } default: newArray = "[" + newBlock + "," + content.Raw[1:] } payload, _ = sjson.SetRawBytes(payload, contentPath, []byte(newArray)) } else if content.Type == gjson.String { newText := prefixBlock + content.String() payload, _ = sjson.SetBytes(payload, contentPath, newText) } return payload } // leadsWithToolResult reports whether a message content array starts with a // tool_result block. Such a message answers a preceding assistant tool_use turn, // and Anthropic requires its tool_result blocks to remain first. func leadsWithToolResult(content gjson.Result) bool { first := content.Get("0") return first.Exists() && first.Get("type").String() == "tool_result" } // applyCloaking applies cloaking transformations to the payload based on config and client. // Cloaking includes: system prompt injection, fake user ID, and sensitive word obfuscation. func applyCloaking(ctx context.Context, cfg *config.Config, auth *cliproxyauth.Auth, payload []byte, model string, apiKey string) ([]byte, error) { clientUserAgent := getClientUserAgent(ctx) // Enable cch signing for OAuth tokens by default (not just experimental flag). oauthToken := isClaudeOAuthToken(apiKey) useCCHSigning := oauthToken || experimentalCCHSigningEnabled(cfg, auth) // Get cloak config from ClaudeKey configuration cloakCfg := resolveClaudeKeyCloakConfig(cfg, auth) attrMode, attrStrict, attrWords, attrCache := getCloakConfigFromAuth(auth) // Determine cloak settings. Precedence (low -> high): // built-in "auto" default // -> global disable-claude-cloak-mode switch (forces "never") // -> per-credential settings from auth attributes/metadata // -> per claude-api-key cloak config cloakMode := "auto" if cfg != nil && cfg.DisableClaudeCloakMode { cloakMode = "never" } strictMode := attrStrict sensitiveWords := attrWords cacheUserID := attrCache if attrMode != "" { cloakMode = attrMode } if cloakCfg != nil { if mode := strings.TrimSpace(cloakCfg.Mode); mode != "" { cloakMode = mode } if cloakCfg.StrictMode { strictMode = true } if len(cloakCfg.SensitiveWords) > 0 { sensitiveWords = cloakCfg.SensitiveWords } if cloakCfg.CacheUserID != nil { cacheUserID = *cloakCfg.CacheUserID } } // Determine if cloaking should be applied if !helps.ShouldCloak(cloakMode, clientUserAgent) { return payload, nil } // Skip system instructions for claude-3-5-haiku models if !strings.HasPrefix(model, "claude-3-5-haiku") { billingVersion := helps.DefaultClaudeVersion(cfg) entrypoint := parseEntrypointFromUA(clientUserAgent) workload := getWorkloadFromContext(ctx) payload = checkSystemInstructionsWithSigningMode(payload, strictMode, useCCHSigning, oauthToken, billingVersion, entrypoint, workload) } // Inject fake user ID var errFakeUserID error payload, errFakeUserID = injectFakeUserID(ctx, payload, apiKey, cacheUserID) if errFakeUserID != nil { return nil, errFakeUserID } // Apply sensitive word obfuscation if len(sensitiveWords) > 0 { matcher := helps.BuildSensitiveWordMatcher(sensitiveWords) payload = helps.ObfuscateSensitiveWords(payload, matcher) } return payload, nil } // ensureCacheControl injects cache_control breakpoints into the payload for optimal prompt caching. // According to Anthropic's documentation, cache prefixes are created in order: tools -> system -> messages. // This function adds cache_control to: // 1. The LAST non-deferred tool in the tools array (caches all preceding tool definitions) // 2. The LAST system prompt element // 3. The SECOND-TO-LAST user turn (caches conversation history for multi-turn) // // Up to 4 cache breakpoints are allowed per request. Tools, System, and Messages are INDEPENDENT breakpoints. // This enables up to 90% cost reduction on cached tokens (cache read = 0.1x base price). // See: https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching func ensureCacheControl(payload []byte) []byte { // 1. Inject cache_control into the LAST non-deferred tool // Tools are cached first in the hierarchy, so this is the most important breakpoint. payload = injectToolsCacheControl(payload) // 2. Inject cache_control into the LAST system prompt element // System is the second level in the cache hierarchy. payload = injectSystemCacheControl(payload) // 3. Inject cache_control into messages for multi-turn conversation caching // This caches the conversation history up to the second-to-last user turn. payload = injectMessagesCacheControl(payload) return payload } func countCacheControls(payload []byte) int { count := 0 // Check system system := gjson.GetBytes(payload, "system") if system.IsArray() { system.ForEach(func(_, item gjson.Result) bool { if item.Get("cache_control").Exists() { count++ } return true }) } // Check tools tools := gjson.GetBytes(payload, "tools") if tools.IsArray() { tools.ForEach(func(_, item gjson.Result) bool { if item.Get("cache_control").Exists() { count++ } return true }) } // Check messages messages := gjson.GetBytes(payload, "messages") if messages.IsArray() { messages.ForEach(func(_, msg gjson.Result) bool { content := msg.Get("content") if content.IsArray() { content.ForEach(func(_, item gjson.Result) bool { if item.Get("cache_control").Exists() { count++