mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-11-02 08:31:35 +00:00
fix(antigravity): neutralize Claude Agent SDK identity in system prompts
Stripping the x-anthropic-billing-header line is not enough. Claude Code's
Agent SDK prompts (session title generation, and other SDK-driven calls) open
a separate system block with
You are a Claude agent, built on Anthropic's Claude Agent SDK.
and the Google upstream still rejects those requests with 429 RESOURCE_EXHAUSTED.
Measured on Sub2API 0.2.7, which already contains the attribution fix, using an isolated single-account group, waiting for the per-model cooldown
to expire before every request so that a previous failure could not turn later
results into 503:
original request replayed 429 (8/8)
same request, generic system prompt 200 (8/8)
attribution also removed client-side 429 (3/3)
attribution removed + identity neutralized 200 (3/3)
only 'Claude' replaced in system 200 (2/2)
only 'Anthropic' removed from system 200 (2/2)
output_config / max_tokens / non-stream no effect
The rewrite is anchored at the start of a system block, so user instructions that
merely mention Claude or Anthropic, and model ids such as claude-sonnet-4-6, are
left untouched. Scoped to the Antigravity transformer, as with the existing
attribution handling.
This commit is contained in:
@@ -86,3 +86,54 @@ func TestTransformClaudeToGemini_AttributionDoesNotHideIdentity(t *testing.T) {
|
||||
require.Equal(t, "You are Antigravity. Keep this identity.", got.Parts[0].Text)
|
||||
require.False(t, strings.Contains(got.Parts[0].Text, "cc_version="))
|
||||
}
|
||||
|
||||
func TestNeutralizeClaudeIdentity(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
text string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "agent sdk opener",
|
||||
text: "You are a Claude agent, built on Anthropic's Claude Agent SDK. You are naming a coding session.",
|
||||
want: "You are an AI agent. You are naming a coding session.",
|
||||
},
|
||||
{
|
||||
name: "cli opener",
|
||||
text: "You are Claude Code, Anthropic's official CLI for Claude. Help with engineering tasks.",
|
||||
want: "You are an AI agent. Help with engineering tasks.",
|
||||
},
|
||||
{
|
||||
name: "leading whitespace",
|
||||
text: "\n You are a Claude agent, built on Anthropic's Claude Agent SDK.\nRest.",
|
||||
want: "You are an AI agent.\nRest.",
|
||||
},
|
||||
// User instructions that merely mention the vendor must stay untouched:
|
||||
// they are content, not a client identity declaration.
|
||||
{
|
||||
name: "mention inside instructions",
|
||||
text: "Follow CLAUDE.md. Prefer claude-sonnet-4-6 for long tasks.",
|
||||
want: "Follow CLAUDE.md. Prefer claude-sonnet-4-6 for long tasks.",
|
||||
},
|
||||
{
|
||||
name: "opener not at start",
|
||||
text: "Example: You are a Claude agent, built on Anthropic's Claude Agent SDK.",
|
||||
want: "Example: You are a Claude agent, built on Anthropic's Claude Agent SDK.",
|
||||
},
|
||||
{name: "ordinary text", text: "Keep these instructions.", want: "Keep these instructions."},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require.Equal(t, tt.want, neutralizeClaudeIdentity(tt.text))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// The attribution line and the identity sentence arrive as separate system
|
||||
// blocks in real Claude Code traffic; both have to be handled.
|
||||
func TestAttributionAndIdentityTogether(t *testing.T) {
|
||||
attribution := "x-anthropic-billing-header: cc_version=2.1.278.0a9; cc_entrypoint=claude-vscode;"
|
||||
identity := "You are a Claude agent, built on Anthropic's Claude Agent SDK."
|
||||
require.Equal(t, "", stripClaudeAttribution(attribution))
|
||||
require.Equal(t, "You are an AI agent.", neutralizeClaudeIdentity(identity))
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -297,6 +298,31 @@ func filterOpenCodePrompt(text string) string {
|
||||
// from Antigravity system text. This is prompt metadata, not an HTTP header; it
|
||||
// can trigger RESOURCE_EXHAUSTED on the Google upstream. Keep this scoped to the
|
||||
// Antigravity transformer: native Anthropic OAuth may require the attribution.
|
||||
// claudeIdentityOpeners are the Claude Agent SDK identity sentences that some
|
||||
// Anthropic clients put at the start of a system block. Stripping the
|
||||
// x-anthropic-billing-header line alone is not enough: a request whose system
|
||||
// text still opens with this sentence is still rejected by the Google upstream
|
||||
// with 429 RESOURCE_EXHAUSTED (see docs/ANTIGRAVITY_ATTRIBUTION_429.md).
|
||||
//
|
||||
// The patterns are anchored at the start of a system block on purpose, so user
|
||||
// instructions that merely mention Claude or Anthropic are left untouched.
|
||||
var claudeIdentityOpeners = []*regexp.Regexp{
|
||||
regexp.MustCompile(`(?i)^[ \t\r\n]*You are a Claude agent, built on Anthropic'?s Claude Agent SDK\.?`),
|
||||
regexp.MustCompile(`(?i)^[ \t\r\n]*You are Claude Code, Anthropic'?s official CLI for Claude\.?`),
|
||||
}
|
||||
|
||||
// neutralizeClaudeIdentity rewrites a leading vendor identity sentence into a
|
||||
// vendor-neutral one. It keeps the rest of the block, including any user
|
||||
// instructions that follow.
|
||||
func neutralizeClaudeIdentity(text string) string {
|
||||
for _, re := range claudeIdentityOpeners {
|
||||
if loc := re.FindStringIndex(text); loc != nil {
|
||||
return "You are an AI agent." + text[loc[1]:]
|
||||
}
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
func stripClaudeAttribution(text string) string {
|
||||
trimmed := strings.TrimLeft(text, " \t\r\n")
|
||||
if !strings.HasPrefix(trimmed, "x-anthropic-billing-header:") {
|
||||
@@ -325,7 +351,7 @@ func buildSystemInstruction(system json.RawMessage, modelName string, opts Trans
|
||||
// 尝试解析为字符串
|
||||
var sysStr string
|
||||
if err := json.Unmarshal(system, &sysStr); err == nil {
|
||||
sysStr = stripClaudeAttribution(sysStr)
|
||||
sysStr = neutralizeClaudeIdentity(stripClaudeAttribution(sysStr))
|
||||
if strings.TrimSpace(sysStr) != "" {
|
||||
if strings.Contains(sysStr, "You are Antigravity") {
|
||||
userHasAntigravityIdentity = true
|
||||
@@ -341,7 +367,7 @@ func buildSystemInstruction(system json.RawMessage, modelName string, opts Trans
|
||||
var sysBlocks []SystemBlock
|
||||
if err := json.Unmarshal(system, &sysBlocks); err == nil {
|
||||
for _, block := range sysBlocks {
|
||||
block.Text = stripClaudeAttribution(block.Text)
|
||||
block.Text = neutralizeClaudeIdentity(stripClaudeAttribution(block.Text))
|
||||
if block.Type == "text" && strings.TrimSpace(block.Text) != "" {
|
||||
if strings.Contains(block.Text, "You are Antigravity") {
|
||||
userHasAntigravityIdentity = true
|
||||
|
||||
Reference in New Issue
Block a user