package executor import ( "bytes" "context" "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "net/http" "strings" "time" claudeauth "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/claude" "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" "github.com/router-for-me/CLIProxyAPI/v7/internal/util" 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") func resolveIncomingClaudeHeaders(ctx context.Context, incoming http.Header) http.Header { resolved := make(http.Header) if ginCtx, ok := ctx.Value("gin").(*gin.Context); ok && ginCtx != nil && ginCtx.Request != nil { resolved = ginCtx.Request.Header.Clone() } for key, values := range incoming { resolved[key] = append([]string(nil), values...) } return resolved} func detectIncomingClaudeCodeRequest(ctx context.Context, incoming http.Header, payload []byte, countTokens bool, cfg *config.Config) (http.Header, helps.ClaudeCodeRequestDetection) { resolved := resolveIncomingClaudeHeaders(ctx, incoming) return resolved, helps.DetectClaudeCodeRequest(resolved, payload, countTokens, cfg)} // 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 value := claudeauth.ReadMetadataString(&auth.Metadata, key); value != "" { 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) } sessionID, errSessionID := helps.CachedSessionIDRequired(ctx, apiKey) if errSessionID != nil { return "", errSessionID } return helps.GenerateFakeUserIDWithSessionID(sessionID), 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// Claude Code prepends to its system prompt. cch is present only on signed paths.func generateBillingHeader(cchSigning bool, version, messageText, entrypoint, workload string, isSubagent bool, prevReq, promptID string) string { if entrypoint == "" { entrypoint = "cli" } buildHash := computeFingerprint(messageText, version) var b strings.Builder b.WriteString("x-anthropic-billing-header: cc_version=") b.WriteString(version) b.WriteByte('.') b.WriteString(buildHash) b.WriteString("; cc_entrypoint=") b.WriteString(entrypoint) b.WriteByte(';') if cchSigning { b.WriteString(" cch=00000;") } if workload != "" { b.WriteString(" cc_workload=") b.WriteString(workload) b.WriteByte(';') } if isSubagent { b.WriteString(" cc_is_subagent=true;") } if cchSigning { if prevReq != "" { b.WriteString(" cc_prev_req=") b.WriteString(prevReq) b.WriteByte(';') } if promptID != "" { b.WriteString(" cc_prompt_id=") b.WriteString(promptID) b.WriteByte(';') } } return b.String()} func resolveClaudeContinuityTags( ctx context.Context, auth *cliproxyauth.Auth, incomingHeaders http.Header, payload []byte, confirmedClaudeCode bool, existingPrevReq, existingPromptID string,) (prevReq, promptID string, cCtx helps.ClaudeContinuityContext, ok bool) { hasExecutionMetadata := helps.ClaudeExecutionMetadataFromContext(ctx) sessionID := helps.ClaudeSessionIDFromContext(ctx) if sessionID == "" && auth != nil { sessionID = helps.ClaudeAgentSessionUUIDForRequest(incomingHeaders, payload, payload, confirmedClaudeCode) } if sessionID == "" || auth == nil { return "", "", helps.ClaudeContinuityContext{}, false } credIdentity := claudeDiagnosticsCredentialIdentity(auth) isNewTurn := helps.IsClaudeNewPromptTurn(payload) continuityKey, seq, prevMsgID, storedPrevReq, storedPromptID := helps.BeginClaudeContinuity(credIdentity, sessionID, isNewTurn, existingPromptID) if existingPromptID != "" { promptID = existingPromptID } else if prevMsgID != "" && storedPromptID != "" && (hasExecutionMetadata || !isNewTurn) { promptID = storedPromptID } else if !hasExecutionMetadata { promptID = helps.ClaudeDeterministicPromptID("cpa:prompt:" + claudeBillingFingerprintMessageText(payload)) } else { promptID = storedPromptID } if (hasExecutionMetadata || existingPrevReq != "") && storedPrevReq != "" { prevReq = storedPrevReq } else { prevReq = existingPrevReq } cCtx = helps.ClaudeContinuityContext{ Key: continuityKey, Sequence: seq, PromptID: promptID, Initialized: true, } if hasExecutionMetadata || existingPrevReq != "" { cCtx.PreviousMessageID = prevMsgID cCtx.PreviousRequestID = storedPrevReq } else { cCtx.PreviousMessageID = "" cCtx.PreviousRequestID = "" } return prevReq, promptID, cCtx, true} func claudeBillingFingerprintMessageText(payload []byte) string { idx := firstClaudeUserMessageIndex(payload) if idx < 0 { return "" } content := gjson.GetBytes(payload, fmt.Sprintf("messages.%d.content", idx)) if content.Type == gjson.String { return content.String() } if content.IsArray() { messageText := "" content.ForEach(func(_, part gjson.Result) bool { if part.Get("type").String() == "text" { text := part.Get("text").String() if !isClaudeCodeCurrentDateReminder(text) && !isClaudeCodeContextReminder(text) { messageText = text } } return true }) return messageText } return ""} func claudeCCHFallbackBillingHeader(ctx context.Context, cfg *config.Config, payload []byte, entrypoint string) string { isProbeOrHelper := helps.IsClaudeProbeOrHelperRequest(payload) prevReq, promptID := helps.ExtractClaudeBillingTags(payload) if !isProbeOrHelper { continuityCtx := helps.ClaudeContinuityContextFromContext(ctx) if prevReq == "" && continuityCtx != nil { prevReq = continuityCtx.PreviousRequestID } if promptID == "" && continuityCtx != nil { promptID = continuityCtx.PromptID } } incomingHeaders := resolveIncomingClaudeHeaders(ctx, helps.IncomingHeadersFromContext(ctx)) isSubagent := helps.IsClaudeSubagentRequest(incomingHeaders, payload) return generateBillingHeader( true, helps.DefaultClaudeVersion(cfg), claudeBillingFingerprintMessageText(payload), entrypoint, getWorkloadFromContext(ctx), isSubagent, prevReq, promptID, )} const claudeCodeCLIIdentity = "You are Claude Code, Anthropic's official CLI for Claude." const claudeCodeFableReportingOutcomes = `# Reporting outcomes Report what actually happened, not what you intended. When you say something is done, sent, saved, fixed, or verified, that claim must rest on a result you observed in this session — tool output, the file as it now reads, the page as it now loads — not on what the step should have produced. If you did not check, say you did not check. If any step failed, was skipped, or came back different from what you expected, say so in the first sentence of your report, before anything else, even when the rest of the work succeeded. Never quietly work around a failure in a way that makes it look resolved; a problem the user can see is recoverable, one your summary hides is not. When you stop before the task is complete, your first line says so plainly and names what is left. Do not describe partial work as done, and do not let a summary read as more certain than the evidence behind it.` func checkSystemInstructionsWithMode(payload []byte, strictMode bool) []byte { return checkSystemInstructionsWithSigningMode(payload, strictMode, false, "2.1.258", "cli", "")} // checkSystemInstructionsWithSigningMode keeps the top-level system in Claude// Code's minimal CLI shape. Each caller system block is preserved as a separate// mid-conversation system message after the first user turn, where supported// Claude models give it operator-level authority without changing the cached// top-level prefix.func checkSystemInstructionsWithSigningMode(payload []byte, strictMode bool, cchSigning bool, version, entrypoint, workload string) []byte { return checkSystemInstructionsWithSigningModeAt(payload, strictMode, cchSigning, version, entrypoint, workload, time.Now(), false, "", "")} // isClaudeFable51Model reports whether the model is specifically Fable 5.1 / Mythos 5.1,// matching native Claude Code 2.1.258 family/major/minor checks (AFo = {major:5, minor:1}).func isClaudeFable51Model(model string) bool { m := strings.ToLower(strings.TrimSpace(model)) for _, target := range []string{"fable-5-1", "fable-5.1", "mythos-5-1", "mythos-5.1"} { idx := strings.Index(m, target) if idx != -1 { nextIdx := idx + len(target) if nextIdx >= len(m) || m[nextIdx] < '0' || m[nextIdx] > '9' { return true } } } return false} func checkSystemInstructionsWithSigningModeAt( payload []byte, strictMode bool, cchSigning bool, version, entrypoint, workload string, now time.Time, isSubagent bool, prevReq, promptID string,) []byte { system := gjson.GetBytes(payload, "system") messageText := claudeBillingFingerprintMessageText(payload) billingText := generateBillingHeader(cchSigning, version, messageText, entrypoint, workload, isSubagent, prevReq, promptID) billingBlock := buildTextBlock(billingText, nil) agentBlock := buildTextBlock(claudeCodeCLIIdentity, &claudeCodeCacheControl) systemBlocks := []string{billingBlock, agentBlock} model := strings.ToLower(strings.TrimSpace(gjson.GetBytes(payload, "model").String())) if isClaudeFable51Model(model) && !helps.IsClaudeProbeOrHelperRequest(payload) { systemBlocks = append(systemBlocks, buildTextBlock(claudeCodeFableReportingOutcomes, nil)) } payload, _ = sjson.SetRawBytes(payload, "system", []byte("["+strings.Join(systemBlocks, ",")+"]")) if strictMode { return injectClaudeCodeCurrentDate(payload, now) } forwardedSystemBlocks := collectForwardedClaudeSystemPromptBlocks(system) if len(forwardedSystemBlocks) == 0 { return injectClaudeCodeCurrentDate(payload, now) } if claudeHistoryHasAdvisorCallOrResult(payload) { for _, block := range forwardedSystemBlocks { systemBlocks = append(systemBlocks, buildTextBlock(block, nil)) } payload, _ = sjson.SetRawBytes(payload, "system", []byte("["+strings.Join(systemBlocks, ",")+"]")) return injectClaudeCodeCurrentDate(payload, now) } if claudeUsesLegacySystemReminder(payload) { payload = prependClaudeSystemRemindersToFirstUserMessage(payload, forwardedSystemBlocks) } else { // Unknown and future model IDs optimistically use the authoritative // mid-conversation system role. Only empirically unsupported legacy IDs // stay on the user-reminder compatibility path. payload = insertClaudeMidConversationSystemMessages(payload, forwardedSystemBlocks) } return injectClaudeCodeCurrentDate(payload, now)} // relocateClaudeSystemPromptForCountTokens keeps a cloaked count_tokens request// in Claude Code's measured shape, which carries only model, messages and tools.// The Claude Code system blocks are therefore not installed here, but each caller// system block still has to be accounted for, so it is relocated into messages// using the same positional mapping as the Messages path. That keeps the counted// tokens aligned with the request the caller is about to send while preventing a// third-party system prompt from reaching Anthropic in the system slot.func relocateClaudeSystemPromptForCountTokens(payload []byte, strictMode bool) []byte { system := gjson.GetBytes(payload, "system") if !system.Exists() { return payload } // Strict mode drops caller prompts on the Messages path, so it must not // reintroduce them here either. var forwardedSystemBlocks []string if !strictMode { forwardedSystemBlocks = collectForwardedClaudeSystemPromptBlocks(system) } if len(forwardedSystemBlocks) == 0 { updated, _ := sjson.DeleteBytes(payload, "system") return updated } if claudeHistoryHasAdvisorCallOrResult(payload) { // When an advisor call or result is present, the Messages path preserves // caller system blocks in top-level system to prevent shifting message // positions. Keep them in top-level system here as well so token counting // remains aligned with the Messages path without splicing messages[]. blocks := make([]string, 0, len(forwardedSystemBlocks)) for _, block := range forwardedSystemBlocks { blocks = append(blocks, buildTextBlock(block, nil)) } updated, _ := sjson.SetRawBytes(payload, "system", []byte("["+strings.Join(blocks, ",")+"]")) return updated } updated, errDelete := sjson.DeleteBytes(payload, "system") if errDelete != nil { return payload } payload = updated if claudeUsesLegacySystemReminder(payload) { return prependClaudeSystemRemindersToFirstUserMessage(payload, forwardedSystemBlocks) } return insertClaudeMidConversationSystemMessages(payload, forwardedSystemBlocks)} // claudeLegacySystemReminderModels lists the official Anthropic model IDs and// aliases that reject a mid-conversation role=system message. Entries mirror the// "claude" provider in internal/registry/models/models.json plus Anthropic's own// bare and "-latest" aliases. Other providers' synthetic IDs do not belong here.var claudeLegacySystemReminderModels = map[string]struct{}{ "claude-3-5-haiku-20241022": {}, "claude-3-5-haiku-latest": {}, "claude-3-7-sonnet-20250219": {}, "claude-3-7-sonnet-latest": {}, "claude-haiku-4-5": {}, "claude-haiku-4-5-20251001": {}, "claude-opus-4": {}, "claude-opus-4-20250514": {}, "claude-opus-4-1": {}, "claude-opus-4-1-20250805": {}, "claude-opus-4-5": {}, "claude-opus-4-5-20251101": {}, "claude-opus-4-6": {}, "claude-opus-4-7": {}, "claude-sonnet-4": {}, "claude-sonnet-4-20250514": {}, "claude-sonnet-4-5": {}, "claude-sonnet-4-5-20250929": {}, "claude-sonnet-4-6": {},} func claudeUsesLegacySystemReminder(payload []byte) bool { model := strings.ToLower(strings.TrimSpace(gjson.GetBytes(payload, "model").String())) if slash := strings.LastIndexByte(model, '/'); slash >= 0 { model = model[slash+1:] } _, legacy := claudeLegacySystemReminderModels[model] return legacy} // claudeCallerSystemBlockError reports a caller system block that Claude cannot// carry in any system slot. It is request-scoped: no other credential or upstream// model can accept the same body, so the request must not be retried.type claudeCallerSystemBlockError struct { statusErr} func (claudeCallerSystemBlockError) IsRequestScoped() bool { return true} func newClaudeCallerSystemBlockError(index int, blockType string) error { if blockType == "" { blockType = "unknown" } return claudeCallerSystemBlockError{statusErr{ code: http.StatusBadRequest, msg: fmt.Sprintf("invalid_request_error: system.%d.type: Input should be 'text'. "+ "System instructions support text only, but this block has type %q. "+ "Move non-text content into a user message.", index, blockType), }}} // claudeMidSystemMessageModelError reports a mid-conversation// {"role":"system"} turn addressed to a first-party model that cannot carry// it. It is request-scoped for the same reason as claudeCallerSystemBlockError:// the body is incompatible with the model rather than evidence of unhealthy// credentials, so no credential should be cooled or retried.type claudeMidSystemMessageModelError struct { statusErr} func (claudeMidSystemMessageModelError) IsRequestScoped() bool { return true} // The turn is not always the caller's. CPA normally reconciles a cloaked turn// when a payload rule changes the model to legacy, but it deliberately gives up// if the rule also rewrites the tracked messages and their provenance is no// longer exact. The wording therefore states the model's requirement instead of// assuming the caller created the turn.func newClaudeMidSystemMessageModelError(model string) error { if model == "" {Lines 1–500 of 2190. Load more
package executor import ( "bytes" "context" "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "net/http" "strings" "time" claudeauth "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/claude" "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" "github.com/router-for-me/CLIProxyAPI/v7/internal/util" 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" ) func resolveIncomingClaudeHeaders(ctx context.Context, incoming http.Header) http.Header { resolved := make(http.Header) if ginCtx, ok := ctx.Value("gin").(*gin.Context); ok && ginCtx != nil && ginCtx.Request != nil { resolved = ginCtx.Request.Header.Clone() } for key, values := range incoming { resolved[key] = append([]string(nil), values...) } return resolved } func detectIncomingClaudeCodeRequest(ctx context.Context, incoming http.Header, payload []byte, countTokens bool, cfg *config.Config) (http.Header, helps.ClaudeCodeRequestDetection) { resolved := resolveIncomingClaudeHeaders(ctx, incoming) return resolved, helps.DetectClaudeCodeRequest(resolved, payload, countTokens, cfg) } // 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 value := claudeauth.ReadMetadataString(&auth.Metadata, key); value != "" { 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) } sessionID, errSessionID := helps.CachedSessionIDRequired(ctx, apiKey) if errSessionID != nil { return "", errSessionID } return helps.GenerateFakeUserIDWithSessionID(sessionID), 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 // Claude Code prepends to its system prompt. cch is present only on signed paths. func generateBillingHeader(cchSigning bool, version, messageText, entrypoint, workload string, isSubagent bool, prevReq, promptID string) string { if entrypoint == "" { entrypoint = "cli" } buildHash := computeFingerprint(messageText, version) var b strings.Builder b.WriteString("x-anthropic-billing-header: cc_version=") b.WriteString(version) b.WriteByte('.') b.WriteString(buildHash) b.WriteString("; cc_entrypoint=") b.WriteString(entrypoint) b.WriteByte(';') if cchSigning { b.WriteString(" cch=00000;") } if workload != "" { b.WriteString(" cc_workload=") b.WriteString(workload) b.WriteByte(';') } if isSubagent { b.WriteString(" cc_is_subagent=true;") } if cchSigning { if prevReq != "" { b.WriteString(" cc_prev_req=") b.WriteString(prevReq) b.WriteByte(';') } if promptID != "" { b.WriteString(" cc_prompt_id=") b.WriteString(promptID) b.WriteByte(';') } } return b.String() } func resolveClaudeContinuityTags( ctx context.Context, auth *cliproxyauth.Auth, incomingHeaders http.Header, payload []byte, confirmedClaudeCode bool, existingPrevReq, existingPromptID string, ) (prevReq, promptID string, cCtx helps.ClaudeContinuityContext, ok bool) { hasExecutionMetadata := helps.ClaudeExecutionMetadataFromContext(ctx) sessionID := helps.ClaudeSessionIDFromContext(ctx) if sessionID == "" && auth != nil { sessionID = helps.ClaudeAgentSessionUUIDForRequest(incomingHeaders, payload, payload, confirmedClaudeCode) } if sessionID == "" || auth == nil { return "", "", helps.ClaudeContinuityContext{}, false } credIdentity := claudeDiagnosticsCredentialIdentity(auth) isNewTurn := helps.IsClaudeNewPromptTurn(payload) continuityKey, seq, prevMsgID, storedPrevReq, storedPromptID := helps.BeginClaudeContinuity(credIdentity, sessionID, isNewTurn, existingPromptID) if existingPromptID != "" { promptID = existingPromptID } else if prevMsgID != "" && storedPromptID != "" && (hasExecutionMetadata || !isNewTurn) { promptID = storedPromptID } else if !hasExecutionMetadata { promptID = helps.ClaudeDeterministicPromptID("cpa:prompt:" + claudeBillingFingerprintMessageText(payload)) } else { promptID = storedPromptID } if (hasExecutionMetadata || existingPrevReq != "") && storedPrevReq != "" { prevReq = storedPrevReq } else { prevReq = existingPrevReq } cCtx = helps.ClaudeContinuityContext{ Key: continuityKey, Sequence: seq, PromptID: promptID, Initialized: true, } if hasExecutionMetadata || existingPrevReq != "" { cCtx.PreviousMessageID = prevMsgID cCtx.PreviousRequestID = storedPrevReq } else { cCtx.PreviousMessageID = "" cCtx.PreviousRequestID = "" } return prevReq, promptID, cCtx, true } func claudeBillingFingerprintMessageText(payload []byte) string { idx := firstClaudeUserMessageIndex(payload) if idx < 0 { return "" } content := gjson.GetBytes(payload, fmt.Sprintf("messages.%d.content", idx)) if content.Type == gjson.String { return content.String() } if content.IsArray() { messageText := "" content.ForEach(func(_, part gjson.Result) bool { if part.Get("type").String() == "text" { text := part.Get("text").String() if !isClaudeCodeCurrentDateReminder(text) && !isClaudeCodeContextReminder(text) { messageText = text } } return true }) return messageText } return "" } func claudeCCHFallbackBillingHeader(ctx context.Context, cfg *config.Config, payload []byte, entrypoint string) string { isProbeOrHelper := helps.IsClaudeProbeOrHelperRequest(payload) prevReq, promptID := helps.ExtractClaudeBillingTags(payload) if !isProbeOrHelper { continuityCtx := helps.ClaudeContinuityContextFromContext(ctx) if prevReq == "" && continuityCtx != nil { prevReq = continuityCtx.PreviousRequestID } if promptID == "" && continuityCtx != nil { promptID = continuityCtx.PromptID } } incomingHeaders := resolveIncomingClaudeHeaders(ctx, helps.IncomingHeadersFromContext(ctx)) isSubagent := helps.IsClaudeSubagentRequest(incomingHeaders, payload) return generateBillingHeader( true, helps.DefaultClaudeVersion(cfg), claudeBillingFingerprintMessageText(payload), entrypoint, getWorkloadFromContext(ctx), isSubagent, prevReq, promptID, ) } const claudeCodeCLIIdentity = "You are Claude Code, Anthropic's official CLI for Claude." const claudeCodeFableReportingOutcomes = `# Reporting outcomes Report what actually happened, not what you intended. When you say something is done, sent, saved, fixed, or verified, that claim must rest on a result you observed in this session — tool output, the file as it now reads, the page as it now loads — not on what the step should have produced. If you did not check, say you did not check. If any step failed, was skipped, or came back different from what you expected, say so in the first sentence of your report, before anything else, even when the rest of the work succeeded. Never quietly work around a failure in a way that makes it look resolved; a problem the user can see is recoverable, one your summary hides is not. When you stop before the task is complete, your first line says so plainly and names what is left. Do not describe partial work as done, and do not let a summary read as more certain than the evidence behind it.` func checkSystemInstructionsWithMode(payload []byte, strictMode bool) []byte { return checkSystemInstructionsWithSigningMode(payload, strictMode, false, "2.1.258", "cli", "") } // checkSystemInstructionsWithSigningMode keeps the top-level system in Claude // Code's minimal CLI shape. Each caller system block is preserved as a separate // mid-conversation system message after the first user turn, where supported // Claude models give it operator-level authority without changing the cached // top-level prefix. func checkSystemInstructionsWithSigningMode(payload []byte, strictMode bool, cchSigning bool, version, entrypoint, workload string) []byte { return checkSystemInstructionsWithSigningModeAt(payload, strictMode, cchSigning, version, entrypoint, workload, time.Now(), false, "", "") } // isClaudeFable51Model reports whether the model is specifically Fable 5.1 / Mythos 5.1, // matching native Claude Code 2.1.258 family/major/minor checks (AFo = {major:5, minor:1}). func isClaudeFable51Model(model string) bool { m := strings.ToLower(strings.TrimSpace(model)) for _, target := range []string{"fable-5-1", "fable-5.1", "mythos-5-1", "mythos-5.1"} { idx := strings.Index(m, target) if idx != -1 { nextIdx := idx + len(target) if nextIdx >= len(m) || m[nextIdx] < '0' || m[nextIdx] > '9' { return true } } } return false } func checkSystemInstructionsWithSigningModeAt( payload []byte, strictMode bool, cchSigning bool, version, entrypoint, workload string, now time.Time, isSubagent bool, prevReq, promptID string, ) []byte { system := gjson.GetBytes(payload, "system") messageText := claudeBillingFingerprintMessageText(payload) billingText := generateBillingHeader(cchSigning, version, messageText, entrypoint, workload, isSubagent, prevReq, promptID) billingBlock := buildTextBlock(billingText, nil) agentBlock := buildTextBlock(claudeCodeCLIIdentity, &claudeCodeCacheControl) systemBlocks := []string{billingBlock, agentBlock} model := strings.ToLower(strings.TrimSpace(gjson.GetBytes(payload, "model").String())) if isClaudeFable51Model(model) && !helps.IsClaudeProbeOrHelperRequest(payload) { systemBlocks = append(systemBlocks, buildTextBlock(claudeCodeFableReportingOutcomes, nil)) } payload, _ = sjson.SetRawBytes(payload, "system", []byte("["+strings.Join(systemBlocks, ",")+"]")) if strictMode { return injectClaudeCodeCurrentDate(payload, now) } forwardedSystemBlocks := collectForwardedClaudeSystemPromptBlocks(system) if len(forwardedSystemBlocks) == 0 { return injectClaudeCodeCurrentDate(payload, now) } if claudeHistoryHasAdvisorCallOrResult(payload) { for _, block := range forwardedSystemBlocks { systemBlocks = append(systemBlocks, buildTextBlock(block, nil)) } payload, _ = sjson.SetRawBytes(payload, "system", []byte("["+strings.Join(systemBlocks, ",")+"]")) return injectClaudeCodeCurrentDate(payload, now) } if claudeUsesLegacySystemReminder(payload) { payload = prependClaudeSystemRemindersToFirstUserMessage(payload, forwardedSystemBlocks) } else { // Unknown and future model IDs optimistically use the authoritative // mid-conversation system role. Only empirically unsupported legacy IDs // stay on the user-reminder compatibility path. payload = insertClaudeMidConversationSystemMessages(payload, forwardedSystemBlocks) } return injectClaudeCodeCurrentDate(payload, now) } // relocateClaudeSystemPromptForCountTokens keeps a cloaked count_tokens request // in Claude Code's measured shape, which carries only model, messages and tools. // The Claude Code system blocks are therefore not installed here, but each caller // system block still has to be accounted for, so it is relocated into messages // using the same positional mapping as the Messages path. That keeps the counted // tokens aligned with the request the caller is about to send while preventing a // third-party system prompt from reaching Anthropic in the system slot. func relocateClaudeSystemPromptForCountTokens(payload []byte, strictMode bool) []byte { system := gjson.GetBytes(payload, "system") if !system.Exists() { return payload } // Strict mode drops caller prompts on the Messages path, so it must not // reintroduce them here either. var forwardedSystemBlocks []string if !strictMode { forwardedSystemBlocks = collectForwardedClaudeSystemPromptBlocks(system) } if len(forwardedSystemBlocks) == 0 { updated, _ := sjson.DeleteBytes(payload, "system") return updated } if claudeHistoryHasAdvisorCallOrResult(payload) { // When an advisor call or result is present, the Messages path preserves // caller system blocks in top-level system to prevent shifting message // positions. Keep them in top-level system here as well so token counting // remains aligned with the Messages path without splicing messages[]. blocks := make([]string, 0, len(forwardedSystemBlocks)) for _, block := range forwardedSystemBlocks { blocks = append(blocks, buildTextBlock(block, nil)) } updated, _ := sjson.SetRawBytes(payload, "system", []byte("["+strings.Join(blocks, ",")+"]")) return updated } updated, errDelete := sjson.DeleteBytes(payload, "system") if errDelete != nil { return payload } payload = updated if claudeUsesLegacySystemReminder(payload) { return prependClaudeSystemRemindersToFirstUserMessage(payload, forwardedSystemBlocks) } return insertClaudeMidConversationSystemMessages(payload, forwardedSystemBlocks) } // claudeLegacySystemReminderModels lists the official Anthropic model IDs and // aliases that reject a mid-conversation role=system message. Entries mirror the // "claude" provider in internal/registry/models/models.json plus Anthropic's own // bare and "-latest" aliases. Other providers' synthetic IDs do not belong here. var claudeLegacySystemReminderModels = map[string]struct{}{ "claude-3-5-haiku-20241022": {}, "claude-3-5-haiku-latest": {}, "claude-3-7-sonnet-20250219": {}, "claude-3-7-sonnet-latest": {}, "claude-haiku-4-5": {}, "claude-haiku-4-5-20251001": {}, "claude-opus-4": {}, "claude-opus-4-20250514": {}, "claude-opus-4-1": {}, "claude-opus-4-1-20250805": {}, "claude-opus-4-5": {}, "claude-opus-4-5-20251101": {}, "claude-opus-4-6": {}, "claude-opus-4-7": {}, "claude-sonnet-4": {}, "claude-sonnet-4-20250514": {}, "claude-sonnet-4-5": {}, "claude-sonnet-4-5-20250929": {}, "claude-sonnet-4-6": {}, } func claudeUsesLegacySystemReminder(payload []byte) bool { model := strings.ToLower(strings.TrimSpace(gjson.GetBytes(payload, "model").String())) if slash := strings.LastIndexByte(model, '/'); slash >= 0 { model = model[slash+1:] } _, legacy := claudeLegacySystemReminderModels[model] return legacy } // claudeCallerSystemBlockError reports a caller system block that Claude cannot // carry in any system slot. It is request-scoped: no other credential or upstream // model can accept the same body, so the request must not be retried. type claudeCallerSystemBlockError struct { statusErr } func (claudeCallerSystemBlockError) IsRequestScoped() bool { return true } func newClaudeCallerSystemBlockError(index int, blockType string) error { if blockType == "" { blockType = "unknown" } return claudeCallerSystemBlockError{statusErr{ code: http.StatusBadRequest, msg: fmt.Sprintf("invalid_request_error: system.%d.type: Input should be 'text'. "+ "System instructions support text only, but this block has type %q. "+ "Move non-text content into a user message.", index, blockType), }} } // claudeMidSystemMessageModelError reports a mid-conversation // {"role":"system"} turn addressed to a first-party model that cannot carry // it. It is request-scoped for the same reason as claudeCallerSystemBlockError: // the body is incompatible with the model rather than evidence of unhealthy // credentials, so no credential should be cooled or retried. type claudeMidSystemMessageModelError struct { statusErr } func (claudeMidSystemMessageModelError) IsRequestScoped() bool { return true } // The turn is not always the caller's. CPA normally reconciles a cloaked turn // when a payload rule changes the model to legacy, but it deliberately gives up // if the rule also rewrites the tracked messages and their provenance is no // longer exact. The wording therefore states the model's requirement instead of // assuming the caller created the turn. func newClaudeMidSystemMessageModelError(model string) error { if model == "" {