Skip to content
Open
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
77 changes: 75 additions & 2 deletions open-sse/config/constants.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,76 @@
import { platform, arch } from "os";

// === Antigravity Binary Alignment: Numeric Enums ===
// Reference: Antigravity binary analysis - google.internal.cloud.code.v1internal.ClientMetadata

// IDE Type enum (numeric values as expected by Cloud Code API)
export const IDE_TYPE = {
UNSPECIFIED: 0,
JETSKI: 10, // Internal codename for Gemini CLI
ANTIGRAVITY: 9,
PLUGINS: 7
};

// Platform enum (as specified in Antigravity binary)
export const PLATFORM = {
UNSPECIFIED: 0,
DARWIN_AMD64: 1,
DARWIN_ARM64: 2,
LINUX_AMD64: 3,
LINUX_ARM64: 4,
WINDOWS_AMD64: 5
};

// Plugin type enum (as specified in Antigravity binary)
export const PLUGIN_TYPE = {
UNSPECIFIED: 0,
CLOUD_CODE: 1,
GEMINI: 2
};

/**
* Get the platform enum value based on the current OS.
* @returns {number} Platform enum value
*/
export function getPlatformEnum() {
const os = platform();
const architecture = arch();

if (os === "darwin") {
return architecture === "arm64" ? PLATFORM.DARWIN_ARM64 : PLATFORM.DARWIN_AMD64;
} else if (os === "linux") {
return architecture === "arm64" ? PLATFORM.LINUX_ARM64 : PLATFORM.LINUX_AMD64;
} else if (os === "win32") {
return PLATFORM.WINDOWS_AMD64;
}
return PLATFORM.UNSPECIFIED;
}

/**
* Generate platform-specific User-Agent string.
* @returns {string} User-Agent in format "antigravity/version os/arch"
*/
export function getPlatformUserAgent() {
const os = platform();
const architecture = arch();
return `antigravity/1.16.5 ${os}/${architecture}`;
}

// Centralized client metadata (used in request bodies for loadCodeAssist, onboardUser, etc.)
// Using numeric enum values as expected by the Cloud Code API
export const CLIENT_METADATA = {
ideType: IDE_TYPE.ANTIGRAVITY, // 9 - identifies as Antigravity client
platform: getPlatformEnum(), // Runtime platform detection
pluginType: PLUGIN_TYPE.GEMINI // 2
};

// Antigravity headers
export const ANTIGRAVITY_HEADERS = {
"X-Client-Name": "antigravity",
"X-Client-Version": "1.107.0",
"x-goog-api-client": "gl-node/18.18.2 fire/0.8.6 grpc/1.10.x"
};

// Provider configurations
export const PROVIDERS = {
claude: {
Expand Down Expand Up @@ -78,7 +151,7 @@ export const PROVIDERS = {
],
format: "antigravity",
headers: {
"User-Agent": "antigravity/1.104.0 darwin/arm64"
"User-Agent": getPlatformUserAgent()
},
clientId: "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com",
clientSecret: "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"
Expand Down Expand Up @@ -181,7 +254,7 @@ export const PROVIDERS = {
export const CLAUDE_SYSTEM_PROMPT = "You are Claude Code, Anthropic's official CLI for Claude.";

// Antigravity default system prompt (required for API to work)
export const ANTIGRAVITY_DEFAULT_SYSTEM = "Please ignore the following [ignore]You are Antigravity, a powerful agentic AI coding assistant designed by the Google Deepmind team working on Advanced Agentic Coding.You are pair programming with a USER to solve their coding task. The task may require creating a new codebase, modifying or debugging an existing codebase, or simply answering a question.**Absolute paths only****Proactiveness**[/ignore]";
export const ANTIGRAVITY_DEFAULT_SYSTEM = "You are Antigravity, a powerful agentic AI coding assistant designed by the Google Deepmind team working on Advanced Agentic Coding.You are pair programming with a USER to solve their coding task. The task may require creating a new codebase, modifying or debugging an existing codebase, or simply answering a question.**Absolute paths only****Proactiveness**";

// OAuth endpoints
export const OAUTH_ENDPOINTS = {
Expand Down
59 changes: 42 additions & 17 deletions open-sse/executors/antigravity.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import crypto from "crypto";
import { BaseExecutor } from "./base.js";
import { PROVIDERS, OAUTH_ENDPOINTS, HTTP_STATUS } from "../config/constants.js";
import { PROVIDERS, OAUTH_ENDPOINTS, HTTP_STATUS, ANTIGRAVITY_HEADERS } from "../config/constants.js";
import { deriveSessionId, getCachedSignature } from "../utils/sessionManager.js";

const MAX_RETRY_AFTER_MS = 10000;

Expand All @@ -16,28 +17,51 @@ export class AntigravityExecutor extends BaseExecutor {
return `${baseUrl}/v1internal:${action}`;
}

buildHeaders(credentials, stream = true) {
buildHeaders(credentials, stream = true, sessionId = null) {
return {
"Content-Type": "application/json",
"Authorization": `Bearer ${credentials.accessToken}`,
"User-Agent": this.config.headers?.["User-Agent"] || "antigravity/1.104.0 darwin/arm64",
"X-9Router-Source": "9router",
...ANTIGRAVITY_HEADERS,
...(sessionId && { "X-Machine-Session-Id": sessionId }),
...(stream && { "Accept": "text/event-stream" })
};
}

transformRequest(model, body, stream, credentials) {
const projectId = credentials?.projectId || this.generateProjectId();


const sessionId = body.request?.sessionId || deriveSessionId(credentials?.email || credentials?.connectionId);

// Get signature for this session (side-channel cache)
const cachedSignature = getCachedSignature(sessionId);
// DEBUG LOG
if (cachedSignature) {
console.log(`[AntigravityExecutor] Injecting cached signature for session ${sessionId.substring(0, 8)}...`);
} else {
// console.log(`[AntigravityExecutor] No cached signature for session ${sessionId.substring(0, 8)}...`);
}

// Fix contents for Claude models via Antigravity
const contents = body.request?.contents?.map(c => {
let role = c.role;
// functionResponse must be role "user" for Claude models
if (c.parts?.some(p => p.functionResponse)) {
role = "user";
}
// Strip thought-only parts, keep thoughtSignature on functionCall parts (Gemini 3+ requires it)
const parts = c.parts?.filter(p => {

// Inject signature into functionCall parts
let parts = c.parts?.map(p => {
if (p.functionCall) {
// Use cached signature or random fallback to avoid static fingerprint
const signature = cachedSignature || crypto.randomUUID();
return { ...p, thoughtSignature: signature };
}
return p;
});

// Strip thought-only parts
parts = parts?.filter(p => {
if (p.thought && !p.functionCall) return false;
if (p.thoughtSignature && !p.functionCall && !p.text) return false;
return true;
Expand All @@ -51,13 +75,13 @@ export class AntigravityExecutor extends BaseExecutor {
const transformedRequest = {
...body.request,
...(contents && { contents }),
sessionId: body.request?.sessionId || this.generateSessionId(),
sessionId: sessionId,
safetySettings: undefined,
toolConfig: body.request?.tools?.length > 0
toolConfig: body.request?.tools?.length > 0
? { functionCallingConfig: { mode: "VALIDATED" } }
: body.request?.toolConfig
};

return {
...body,
project: projectId,
Expand Down Expand Up @@ -108,7 +132,7 @@ export class AntigravityExecutor extends BaseExecutor {
}

generateSessionId() {
return `-${Math.floor(Math.random() * 9_000_000_000_000_000_000)}`;
return crypto.randomUUID() + Date.now().toString();
}

parseRetryHeaders(headers) {
Expand All @@ -118,7 +142,7 @@ export class AntigravityExecutor extends BaseExecutor {
if (retryAfter) {
const seconds = parseInt(retryAfter, 10);
if (!isNaN(seconds) && seconds > 0) return seconds * 1000;

const date = new Date(retryAfter);
if (!isNaN(date.getTime())) {
const diff = date.getTime() - Date.now();
Expand Down Expand Up @@ -167,9 +191,10 @@ export class AntigravityExecutor extends BaseExecutor {

for (let urlIndex = 0; urlIndex < fallbackCount; urlIndex++) {
const url = this.buildUrl(model, stream, urlIndex);
const headers = this.buildHeaders(credentials, stream);
const transformedBody = this.transformRequest(model, body, stream, credentials);

const sessionId = transformedBody.request?.sessionId;
const headers = this.buildHeaders(credentials, stream, sessionId);

// Initialize retry counter for this URL
if (!retryAttemptsByUrl[urlIndex]) {
retryAttemptsByUrl[urlIndex] = 0;
Expand Down Expand Up @@ -200,7 +225,7 @@ export class AntigravityExecutor extends BaseExecutor {
}

if (retryMs && retryMs <= MAX_RETRY_AFTER_MS) {
log?.debug?.("RETRY", `${response.status} with Retry-After: ${Math.ceil(retryMs/1000)}s, waiting...`);
log?.debug?.("RETRY", `${response.status} with Retry-After: ${Math.ceil(retryMs / 1000)}s, waiting...`);
await new Promise(resolve => setTimeout(resolve, retryMs));
urlIndex--;
continue;
Expand All @@ -211,15 +236,15 @@ export class AntigravityExecutor extends BaseExecutor {
retryAttemptsByUrl[urlIndex]++;
// Exponential backoff: 2s, 4s, 8s...
const backoffMs = Math.min(1000 * (2 ** retryAttemptsByUrl[urlIndex]), MAX_RETRY_AFTER_MS);
log?.debug?.("RETRY", `429 auto retry ${retryAttemptsByUrl[urlIndex]}/${MAX_AUTO_RETRIES} after ${backoffMs/1000}s`);
log?.debug?.("RETRY", `429 auto retry ${retryAttemptsByUrl[urlIndex]}/${MAX_AUTO_RETRIES} after ${backoffMs / 1000}s`);
await new Promise(resolve => setTimeout(resolve, backoffMs));
urlIndex--;
continue;
}

log?.debug?.("RETRY", `${response.status}, Retry-After ${retryMs ? `too long (${Math.ceil(retryMs/1000)}s)` : 'missing'}, trying fallback`);
log?.debug?.("RETRY", `${response.status}, Retry-After ${retryMs ? `too long (${Math.ceil(retryMs / 1000)}s)` : 'missing'}, trying fallback`);
lastStatus = response.status;

if (urlIndex + 1 < fallbackCount) {
continue;
}
Expand Down
Loading