diff --git a/src/adapters/google.ts b/src/adapters/google.ts index d2f40a96e7..01189a5938 100644 --- a/src/adapters/google.ts +++ b/src/adapters/google.ts @@ -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. @@ -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[] = []; @@ -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"; + const { systemInstruction, contents, replayedCallIds } = messagesToGeminiFormat( + parsed, + identityModelId, + stripRejectedClaudeSdkParagraph, + ); lastInjectedCallIds = [...replayedCallIds]; lastReasoningReplayScope = parsed._reasoningReplayScope; const tools = toolsToGeminiFormat(parsed); diff --git a/tests/google-adapter.test.ts b/tests/google-adapter.test.ts index d067af9589..e7311eae62 100644 --- a/tests/google-adapter.test.ts +++ b/tests/google-adapter.test.ts @@ -21,6 +21,14 @@ async function geminiBody(parsed: OcxParsedRequest): Promise): 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([ @@ -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; + }; + 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; + }; + + 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: {} } },