Created
September 24, 2026 21:44
-
-
Save aiexz/601948148d3b6420b73aaac3a5eeb84a to your computer and use it in GitHub Desktop.
Bypass gemini antigravity restrictions in omp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * gemini-antigravity-obfuscator — oh-my-pi extension port of | |
| * minhgv/oh-my-pi@8146af1 ("fix(antigravity): obfuscate sensitive words in | |
| * systemInstruction and drop requestType agent"). | |
| * | |
| * Cloud Code Assist inspects `systemInstruction` on agent-mode payloads and | |
| * answers matched ones with a bare `429 RESOURCE_EXHAUSTED` (no | |
| * ErrorInfo/RetryInfo, identical on every retry). Two mitigations, applied | |
| * here at the `before_provider_request` layer — the same layer the core | |
| * patch touches (`buildRequest` in google-gemini-cli.ts passes its wire | |
| * body through this hook, so the extension sees the exact envelope): | |
| * | |
| * 1. Configured literal phrases (default: "RFC 2119") are split with a | |
| * U+200B zero-width space inside `systemInstruction.parts[].text`. | |
| * Visually and semantically intact for the model; clears the | |
| * server-side literal match. Same mitigation CLIProxyAPI ships as | |
| * `antigravity.sensitive-words`. | |
| * 2. `requestType: "agent"` is dropped — it addresses a constrained bucket | |
| * that returns a detail-free 429 (upstream #11689); official | |
| * Antigravity omits it on consumer Cloud Code. | |
| * | |
| * The hook only fires for Gemini traffic: the request model id must contain | |
| * "gemini" (case-insensitive). Everything else passes through untouched. | |
| * | |
| * Install: copy this file to `~/.omp/agent/extensions/` (or | |
| * `<cwd>/.omp/extensions/`), or pass `--extension <path>`. | |
| * Configure: `OMP_ANTIGRAVITY_SENSITIVE_WORDS="RFC 2119,MUST"` (comma | |
| * separated; default `"RFC 2119"`). Empty value disables obfuscation | |
| * (requestType stripping still applies); phrases shorter than 2 chars after | |
| * trimming are ignored, matching the core implementation. | |
| */ | |
| import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent"; | |
| /** Zero-width space: invisible when rendered, breaks literal matching. */ | |
| const ZERO_WIDTH_SPACE = "\u200b"; | |
| /** Fallback when the env var is unset. Mirrors the core default. */ | |
| const DEFAULT_SENSITIVE_WORDS = ["RFC 2119"]; | |
| /** Insert a zero-width space after the first character of the phrase. */ | |
| function splitPhrase(phrase: string): string { | |
| const chars = [...phrase]; | |
| return chars[0] + ZERO_WIDTH_SPACE + chars.slice(1).join(""); | |
| } | |
| /** | |
| * Insert a zero-width space after the first character of every literal | |
| * occurrence of each phrase. Matching is literal and case-sensitive; | |
| * empty/whitespace-only phrases and phrases shorter than 2 chars are | |
| * ignored. Returns the input unchanged when nothing matches. | |
| */ | |
| export function obfuscateSensitiveWords(text: string, phrases: readonly string[]): string { | |
| let result = text; | |
| for (const phrase of phrases) { | |
| const trimmed = phrase.trim(); | |
| if ([...trimmed].length < 2) continue; | |
| result = result.split(trimmed).join(splitPhrase(trimmed)); | |
| } | |
| return result; | |
| } | |
| /** Resolve the active phrase list from the environment. */ | |
| export function resolveSensitiveWords(env: Record<string, string | undefined> = process.env): string[] { | |
| const raw = env.OMP_ANTIGRAVITY_SENSITIVE_WORDS; | |
| if (raw === undefined) return [...DEFAULT_SENSITIVE_WORDS]; | |
| return raw | |
| .split(",") | |
| .map(part => part.trim()) | |
| .filter(part => part.length > 0); | |
| } | |
| interface SystemInstructionPart { | |
| text?: unknown; | |
| [key: string]: unknown; | |
| } | |
| interface AntigravityPayload { | |
| systemInstruction?: { | |
| parts?: unknown; | |
| [key: string]: unknown; | |
| } | null; | |
| requestType?: unknown; | |
| [key: string]: unknown; | |
| } | |
| export interface TransformResult { | |
| payload: AntigravityPayload; | |
| changed: boolean; | |
| } | |
| /** | |
| * Apply both mitigations to a provider wire payload. Pure function over | |
| * plain data so it is unit-testable without the extension runtime. | |
| */ | |
| export function transformAntigravityPayload( | |
| payload: unknown, | |
| sensitiveWords: readonly string[], | |
| ): TransformResult { | |
| if (typeof payload !== "object" || payload === null || Array.isArray(payload)) { | |
| return { payload: payload as AntigravityPayload, changed: false }; | |
| } | |
| const body = payload as AntigravityPayload; | |
| let changed = false; | |
| let systemInstruction = body.systemInstruction; | |
| let parts: SystemInstructionPart[] | undefined; | |
| if ( | |
| typeof systemInstruction === "object" && | |
| systemInstruction !== null && | |
| Array.isArray(systemInstruction.parts) | |
| ) { | |
| parts = systemInstruction.parts as SystemInstructionPart[]; | |
| } | |
| if (parts && sensitiveWords.length > 0) { | |
| const nextParts = parts.map(part => { | |
| if (typeof part !== "object" || part === null || typeof part.text !== "string") return part; | |
| const text = obfuscateSensitiveWords(part.text, sensitiveWords); | |
| if (text === part.text) return part; | |
| changed = true; | |
| return { ...part, text }; | |
| }); | |
| if (changed) { | |
| systemInstruction = { ...systemInstruction, parts: nextParts }; | |
| } | |
| } | |
| let requestType = body.requestType; | |
| if (body.requestType === "agent") { | |
| requestType = undefined; | |
| changed = true; | |
| } | |
| if (!changed) return { payload: body, changed: false }; | |
| const next: AntigravityPayload = { ...body }; | |
| if (systemInstruction !== body.systemInstruction) next.systemInstruction = systemInstruction; | |
| if (requestType === undefined && "requestType" in body) delete next.requestType; | |
| return { payload: next, changed: true }; | |
| } | |
| /** True when the request model is Gemini traffic. */ | |
| export function isGeminiModel(modelId: unknown): boolean { | |
| return typeof modelId === "string" && modelId.toLowerCase().includes("gemini"); | |
| } | |
| export default function geminiAntigravityObfuscator(pi: ExtensionAPI) { | |
| pi.on("before_provider_request", async (event, ctx) => { | |
| const modelId = ctx.model?.id; | |
| if (!isGeminiModel(modelId)) return undefined; | |
| const { payload, changed } = transformAntigravityPayload( | |
| event.payload, | |
| resolveSensitiveWords(), | |
| ); | |
| return changed ? payload : undefined; | |
| }); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment