Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions src/adapters/google.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ const GOOGLE_BREVITY_INSTRUCTION = [

const ANTIGRAVITY_REJECTED_CLAUDE_SDK_PARAGRAPH =
"You are a Claude agent, built on Anthropic's Claude Agent SDK.";

/**
* CCA Flash generations that reject the Claude-Agent identity paragraph.
*
Expand Down Expand Up @@ -102,6 +101,20 @@ function stripAntigravityRejectedClaudeSdkParagraph(systemText: string): string
.join("\n\n");
}

/**
* Strips Claude Code CLI's internal billing header (`x-anthropic-billing-header: ...`)
* at the start of the system prompt, because Cloud Code Assist / Google Antigravity inspects
* `systemInstruction` and rejects requests containing Anthropic billing metadata with
* HTTP 429 RESOURCE_EXHAUSTED.
*
* Matching is restricted to the prompt start (`^` without the `/m` multiline flag) so that
* user prompts discussing billing headers in intermediate lines are never modified, and
* prompts without a billing header preserve their leading whitespace untouched.
*/
function stripAntigravityBillingHeader(systemText: string): string {
return systemText.replace(/^x-anthropic-billing-header:[^\n]*\n*/, "");
}

/**
* Documented output ceiling for a Google-surface model, or `undefined` when the id is not
* recognized.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Expand Down Expand Up @@ -276,6 +289,7 @@ function messagesToGeminiFormat(
parsed: OcxParsedRequest,
identityModelId: string,
stripRejectedClaudeSdkParagraph = false,
isCloudCodeAssist = false,
): { systemInstruction?: unknown; contents: unknown[]; replayedCallIds: string[] } {
// Neutralize Codex's GPT-5 identity line (Gemini/Antigravity share this path) so a routed model
// never misreports as GPT-5/OpenAI, and never leaks the proxy identity upstream.
Expand All @@ -285,9 +299,12 @@ function messagesToGeminiFormat(
...(toolCatalogNudge ? [toolCatalogNudge] : []),
GOOGLE_BREVITY_INSTRUCTION,
].join("\n\n"), identityModelId);
const systemText = stripRejectedClaudeSdkParagraph
? stripAntigravityRejectedClaudeSdkParagraph(identifiedSystemText)
let systemText = isCloudCodeAssist
? stripAntigravityBillingHeader(identifiedSystemText)
: identifiedSystemText;
if (stripRejectedClaudeSdkParagraph) {
systemText = stripAntigravityRejectedClaudeSdkParagraph(systemText);
}
const systemInstruction = { parts: [{ text: systemText }] };

const contents: unknown[] = [];
Expand Down Expand Up @@ -833,12 +850,14 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
&& /^gemini-/.test(routedModelId) && !isImageCapableModel(parsed.modelId);
// AI Studio's `-tiered` spelling is wire-only; CCA aliases may migrate to another generation.
const identityModelId = provider.googleMode === "cloud-code-assist" ? routedModelId : parsed.modelId;
const stripRejectedClaudeSdkParagraph = provider.googleMode === "cloud-code-assist"
const isCloudCodeAssist = provider.googleMode === "cloud-code-assist";
const stripRejectedClaudeSdkParagraph = isCloudCodeAssist
&& rejectsClaudeSdkParagraph(parsed.modelId, routedModelId);
const { systemInstruction, contents, replayedCallIds } = messagesToGeminiFormat(
parsed,
identityModelId,
stripRejectedClaudeSdkParagraph,
isCloudCodeAssist,
);
lastInjectedCallIds = [...replayedCallIds];
lastReasoningReplayScope = parsed._reasoningReplayScope;
Expand Down
58 changes: 58 additions & 0 deletions tests/adapters/google/google-adapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,64 @@ describe("google adapter — Antigravity system prompt compatibility", () => {

expect(systemInstructionText(body)).toContain(REJECTED_CLAUDE_SDK_PARAGRAPH);
});

test("removes x-anthropic-billing-header for Cloud Code Assist models", async () => {
const parsed: OcxParsedRequest = {
...systemPromptParsed("gemini-3.8-flash"),
context: {
systemPrompt: [
"x-anthropic-billing-header: cc_version=2.1.236.b88; cc_entrypoint=sdk-cli;",
"You are Claude Code.",
],
messages: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
tools: [],
},
};
const envelope = JSON.parse((await createGoogleAdapter(ccaProvider).buildRequest(parsed)).body) as {
request: Record<string, unknown>;
};

expect(systemInstructionText(envelope.request)).not.toContain("x-anthropic-billing-header");
expect(systemInstructionText(envelope.request)).toContain("You are Claude Code.");
});

test("preserves x-anthropic-billing-header outside Cloud Code Assist", async () => {
const parsed: OcxParsedRequest = {
...systemPromptParsed("gemini-3.8-flash"),
context: {
systemPrompt: [
"x-anthropic-billing-header: cc_version=2.1.236.b88; cc_entrypoint=sdk-cli;",
"You are Claude Code.",
],
messages: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
tools: [],
},
};
const body = await geminiBody(parsed);

expect(systemInstructionText(body)).toContain("x-anthropic-billing-header");
expect(systemInstructionText(body)).toContain("You are Claude Code.");
});

test("preserves non-leading x-anthropic-billing-header and leading whitespace in Cloud Code Assist", async () => {
const parsed: OcxParsedRequest = {
...systemPromptParsed("gemini-3.8-flash"),
context: {
systemPrompt: [
" leading indentation",
"x-anthropic-billing-header: cc_version=2.1.236.b88; cc_entrypoint=sdk-cli;",
],
messages: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
tools: [],
},
};
const envelope = JSON.parse((await createGoogleAdapter(ccaProvider).buildRequest(parsed)).body) as {
request: Record<string, unknown>;
};

expect(systemInstructionText(envelope.request)).toContain(" leading indentation");
expect(systemInstructionText(envelope.request)).toContain("x-anthropic-billing-header");
});
});

describe("google adapter — tool_choice on the wire", () => {
Expand Down
Loading