fix(antigravity): strip any competitor system prompt, not one literal
Google answers a systemInstruction advertising a rival agent with 429 RESOURCE_EXHAUSTED - a content refusal shaped like a quota error, which is why it survived the endpoint and backoff fixes. Upstream PR #3223 stripped a single Zed literal, so Claude Code / Hermes wording still tripped it. Match the sentence shape instead. Check fails on the old one-literal code and passes here.
This commit is contained in:
parent
c06905aa44
commit
ab66269155
2 changed files with 74 additions and 7 deletions
57
open-sse/executors/antigravity-competitor-strip.check.mjs
Normal file
57
open-sse/executors/antigravity-competitor-strip.check.mjs
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
/**
|
||||||
|
* #3223-generalised: Antigravity answers a system prompt that advertises a rival
|
||||||
|
* agent with 429 RESOURCE_EXHAUSTED. Upstream stripped one Zed literal; every
|
||||||
|
* other client wording still tripped it. This pins the shapes we must strip.
|
||||||
|
*
|
||||||
|
* Run: node open-sse/executors/antigravity-competitor-strip.check.mjs
|
||||||
|
*/
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { readFileSync } from 'node:fs';
|
||||||
|
import { dirname, resolve } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import vm from 'node:vm';
|
||||||
|
|
||||||
|
const here = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const src = readFileSync(
|
||||||
|
process.env.AG_SOURCE || resolve(here, 'antigravity.js'),
|
||||||
|
'utf8'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Lift the strip block out of buildRequest and run it against a fake request.
|
||||||
|
const start = src.indexOf('if (requestWithoutTools.systemInstruction?.parts)');
|
||||||
|
assert.ok(start >= 0, 'competitor-strip block must exist');
|
||||||
|
const end = src.indexOf('\n }\n', src.indexOf('for (const part', start)) + 6;
|
||||||
|
const block = src.slice(start, end);
|
||||||
|
|
||||||
|
const strip = (text) => {
|
||||||
|
const requestWithoutTools = { systemInstruction: { parts: [{ text }] } };
|
||||||
|
vm.runInNewContext(block, { requestWithoutTools });
|
||||||
|
return requestWithoutTools.systemInstruction.parts[0].text;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The literal upstream already handled — must keep working.
|
||||||
|
assert.ok(
|
||||||
|
!strip("You are a Claude agent, built on Anthropic's Claude Agent SDK. Be terse.")
|
||||||
|
.includes('Claude'),
|
||||||
|
'Zed wording must still be stripped'
|
||||||
|
);
|
||||||
|
|
||||||
|
// The wordings upstream missed.
|
||||||
|
for (const prompt of [
|
||||||
|
"You are Claude Code, Anthropic's official CLI for Claude. Help the user.",
|
||||||
|
'You are Claude, made by Anthropic!',
|
||||||
|
'you are a claude agent. lowercase should not save it.',
|
||||||
|
]) {
|
||||||
|
const out = strip(prompt);
|
||||||
|
assert.ok(!/claude/i.test(out), `must strip competitor branding from: ${prompt}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must not eat an innocent prompt.
|
||||||
|
const benign = 'You are a helpful assistant. Answer in French.';
|
||||||
|
assert.equal(strip(benign), benign, 'benign prompts must pass through untouched');
|
||||||
|
|
||||||
|
// Non-string parts must not throw.
|
||||||
|
const req = { systemInstruction: { parts: [{ inlineData: {} }] } };
|
||||||
|
vm.runInNewContext(block, { requestWithoutTools: req });
|
||||||
|
|
||||||
|
console.log('antigravity-competitor-strip: all passed');
|
||||||
|
|
@ -246,17 +246,27 @@ export class AntigravityExecutor extends BaseExecutor {
|
||||||
const { tools: _originalTools, toolConfig: _originalToolConfig, ...requestWithoutTools } = body.request || {};
|
const { tools: _originalTools, toolConfig: _originalToolConfig, ...requestWithoutTools } = body.request || {};
|
||||||
stripBlacklisted(requestWithoutTools);
|
stripBlacklisted(requestWithoutTools);
|
||||||
|
|
||||||
// Rewrite competitive system prompts (e.g. Zed IDE's Claude prompt) to prevent Antigravity from
|
// Rewrite competitive system prompts before they reach Antigravity. Google
|
||||||
// flagging the request and immediately blocking it with a 429 Quota Exhausted response.
|
// inspects systemInstruction and answers a prompt that advertises a rival
|
||||||
|
// agent with 429 RESOURCE_EXHAUSTED — a content refusal wearing a quota
|
||||||
|
// error's clothes, which is why it survives every backoff and endpoint fix.
|
||||||
|
// Upstream PR #3223 stripped ONE literal (Zed's "You are a Claude agent,
|
||||||
|
// built on Anthropic's Claude Agent SDK."), so every other client — Claude
|
||||||
|
// Code, Hermes, Cline — still trips it. Match the shape instead.
|
||||||
|
// ponytail: regex over a prompt-classifier; widen the alternation if a new
|
||||||
|
// client wording slips through.
|
||||||
if (requestWithoutTools.systemInstruction?.parts) {
|
if (requestWithoutTools.systemInstruction?.parts) {
|
||||||
const oldText = "You are a Claude agent, built on Anthropic's Claude Agent SDK.";
|
|
||||||
for (const part of requestWithoutTools.systemInstruction.parts) {
|
for (const part of requestWithoutTools.systemInstruction.parts) {
|
||||||
if (typeof part.text === "string" && part.text.includes(oldText)) {
|
if (typeof part.text !== "string") continue;
|
||||||
part.text = part.text.split(oldText).join("");
|
part.text = part.text
|
||||||
}
|
// "You are Claude Code, Anthropic's official CLI for Claude." /
|
||||||
|
// "You are a Claude agent, built on Anthropic's Claude Agent SDK."
|
||||||
|
.replace(/You are (?:a |an )?Claude\b[^.!?]*[.!?]\s*/gi, "")
|
||||||
|
// Residual vendor branding in the same sentence position.
|
||||||
|
.replace(/\b(?:Anthropic's|Anthropic)\s+(?:official\s+)?(?:CLI|Claude Agent SDK)\b[^.!?]*[.!?]\s*/gi, "")
|
||||||
|
.trimStart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const generationConfig = { ...(requestWithoutTools.generationConfig || {}) };
|
const generationConfig = { ...(requestWithoutTools.generationConfig || {}) };
|
||||||
if (generationConfig.maxOutputTokens > MAX_ANTIGRAVITY_OUTPUT_TOKENS) {
|
if (generationConfig.maxOutputTokens > MAX_ANTIGRAVITY_OUTPUT_TOKENS) {
|
||||||
generationConfig.maxOutputTokens = MAX_ANTIGRAVITY_OUTPUT_TOKENS;
|
generationConfig.maxOutputTokens = MAX_ANTIGRAVITY_OUTPUT_TOKENS;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue