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
24 changes: 22 additions & 2 deletions src/adapters/google.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,16 @@ const GOOGLE_BREVITY_INSTRUCTION = [
"- This applies only to intermediate progress text. Your final answer after the work is done is exempt: write it in full and at whatever length the task requires.",
].join("\n");

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

function stripAntigravityRejectedClaudeSdkParagraph(systemText: string): string {
return systemText
.split("\n\n")
.filter(paragraph => paragraph !== ANTIGRAVITY_REJECTED_CLAUDE_SDK_PARAGRAPH)
.join("\n\n");
}

/**
* Documented output ceiling for a Google-surface model, or `undefined` when the id is not
* recognized.
Expand Down Expand Up @@ -227,15 +237,19 @@ function geminiOrphanToolResultParts(msg: OcxToolResultMessage): unknown[] {
function messagesToGeminiFormat(
parsed: OcxParsedRequest,
identityModelId: string,
stripRejectedClaudeSdkParagraph = 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.
const toolCatalogNudge = buildNonOpenAIToolCatalogNudgeForTools(parsed.context.tools, parsed.options.toolChoice);
const systemText = identifyRoutedModel([
const identifiedSystemText = identifyRoutedModel([
...(parsed.context.systemPrompt ?? []),
...(toolCatalogNudge ? [toolCatalogNudge] : []),
GOOGLE_BREVITY_INSTRUCTION,
].join("\n\n"), identityModelId);
const systemText = stripRejectedClaudeSdkParagraph
? stripAntigravityRejectedClaudeSdkParagraph(identifiedSystemText)
: identifiedSystemText;
const systemInstruction = { parts: [{ text: systemText }] };

const contents: unknown[] = [];
Expand Down Expand Up @@ -733,7 +747,13 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
: resolveDirectGeminiWireModelId(parsed.modelId, provider.directGeminiWireRenames !== false);
// 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 { systemInstruction, contents, replayedCallIds } = messagesToGeminiFormat(parsed, identityModelId);
const stripRejectedClaudeSdkParagraph = provider.googleMode === "cloud-code-assist"
&& parsed.modelId === "gemini-3.7-flash";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Apply stripping to every route targeting Gemini 3.7 Flash

When a caller uses a supported retired selection such as gemini-3.6-flash or one of its effort aliases, resolveAntigravityEffortWireModel routes the request to gemini-3.7-flash-tiered (src/providers/antigravity-models.ts:612-620), but this predicate checks the original picker ID and therefore leaves the rejected paragraph intact. Those compatibility routes still receive the same false 429 this change is intended to fix; determine stripping from routedModelId (or otherwise include every alias resolving to that wire model) instead.

AGENTS.md reference: src/AGENTS.md:L10-L10

Useful? React with 👍 / 👎.

const { systemInstruction, contents, replayedCallIds } = messagesToGeminiFormat(
parsed,
identityModelId,
stripRejectedClaudeSdkParagraph,
);
lastInjectedCallIds = [...replayedCallIds];
lastReasoningReplayScope = parsed._reasoningReplayScope;
const tools = toolsToGeminiFormat(parsed);
Expand Down
59 changes: 59 additions & 0 deletions tests/google-adapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ async function geminiBody(parsed: OcxParsedRequest): Promise<Record<string, unkn
return JSON.parse(body);
}

function systemInstructionText(body: Record<string, unknown>): string {
const instruction = body.systemInstruction as { parts?: Array<{ text?: string }> } | undefined;
return instruction?.parts?.[0]?.text ?? "";
}

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

describe("google adapter — tool result images", () => {
test("tool-result screenshots ride along as inline_data beside the functionResponse", async () => {
const contents = await geminiContents(parsedWith([
Expand Down Expand Up @@ -239,6 +247,57 @@ describe("google adapter — tool-call ids on the wire", () => {
});
});

describe("google adapter — Antigravity system prompt compatibility", () => {
const ccaProvider = {
...provider,
googleMode: "cloud-code-assist",
baseUrl: "https://daily-cloudcode-pa.googleapis.com",
project: "proj-123",
} as const;

function systemPromptParsed(modelId: string): OcxParsedRequest {
return {
modelId,
stream: false,
options: {},
context: {
systemPrompt: [`prefix\n\n${REJECTED_CLAUDE_SDK_PARAGRAPH}\n\nsuffix`],
messages: [{ role: "user", content: "hi" }],
tools: [],
},
} as unknown as OcxParsedRequest;
}

test("removes only the rejected standalone paragraph for CCA Gemini 3.7 Flash", async () => {
const parsed = systemPromptParsed("gemini-3.7-flash");
const ccaEnvelope = JSON.parse((await createGoogleAdapter(ccaProvider).buildRequest(parsed)).body) as {
request: Record<string, unknown>;
};
const directBody = await geminiBody(parsed);
const directText = systemInstructionText(directBody);

expect(systemInstructionText(ccaEnvelope.request)).toBe(
directText.replace(`${REJECTED_CLAUDE_SDK_PARAGRAPH}\n\n`, ""),
);
expect(systemInstructionText(ccaEnvelope.request)).not.toContain(REJECTED_CLAUDE_SDK_PARAGRAPH);
});

test("preserves the paragraph for another Cloud Code Assist model", async () => {
const parsed = systemPromptParsed("gemini-3.6-flash");
const envelope = JSON.parse((await createGoogleAdapter(ccaProvider).buildRequest(parsed)).body) as {
request: Record<string, unknown>;
};

expect(systemInstructionText(envelope.request)).toContain(REJECTED_CLAUDE_SDK_PARAGRAPH);
});

test("preserves the paragraph outside Cloud Code Assist", async () => {
const body = await geminiBody(systemPromptParsed("gemini-3.7-flash"));

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

describe("google adapter — tool_choice on the wire", () => {
const TOOLS = [
{ name: "get_weather", parameters: { type: "object", properties: {} } },
Expand Down
Loading