mirror of
https://github.com/Nezumi-2711/9router.git
synced 2026-09-22 13:38:31 +00:00
feat(antigravity): initial steps for Antigravity anti-ban alignment
Cherry-picked from decolua/9router#141 (author: LinearSakana / zx <me@char.moe>) - Implement client identity spoofing with numeric enums (ideType: 9, pluginType: 2) - Add runtime platform detection for User-Agent and metadata - Implement per-connection session ID caching (binary-compatible format) - Add ANTIGRAVITY_HEADERS (X-Client-Name, X-Client-Version, x-goog-api-client) - Add X-Machine-Session-Id header injection - Align metadata/mode parameters across all Antigravity API calls - Implement double injection for system prompt (raw + [ignore] wrapped) - Rename internal anti-loop header to x-request-source for anonymity Skipped: commit 6 (signature side-channel caching) — kept DEFAULT_THINKING_GEMINI_SIGNATURE Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
committed by
decolua
co-authored by
Cursor
parent
9fbd6e619d
commit
a229d79158
@@ -1,3 +1,79 @@
|
|||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
// Internal anti-loop header to identify requests originating from this proxy
|
||||||
|
export const INTERNAL_REQUEST_HEADER = { name: "x-request-source", value: "local" };
|
||||||
|
|
||||||
|
// 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
|
// Provider configurations
|
||||||
export const PROVIDERS = {
|
export const PROVIDERS = {
|
||||||
claude: {
|
claude: {
|
||||||
@@ -78,7 +154,7 @@ export const PROVIDERS = {
|
|||||||
],
|
],
|
||||||
format: "antigravity",
|
format: "antigravity",
|
||||||
headers: {
|
headers: {
|
||||||
"User-Agent": "antigravity/1.104.0 darwin/arm64"
|
"User-Agent": getPlatformUserAgent()
|
||||||
},
|
},
|
||||||
clientId: "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com",
|
clientId: "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com",
|
||||||
clientSecret: "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"
|
clientSecret: "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"
|
||||||
@@ -181,7 +257,7 @@ export const PROVIDERS = {
|
|||||||
export const CLAUDE_SYSTEM_PROMPT = "You are Claude Code, Anthropic's official CLI for Claude.";
|
export const CLAUDE_SYSTEM_PROMPT = "You are Claude Code, Anthropic's official CLI for Claude.";
|
||||||
|
|
||||||
// Antigravity default system prompt (required for API to work)
|
// 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
|
// OAuth endpoints
|
||||||
export const OAUTH_ENDPOINTS = {
|
export const OAUTH_ENDPOINTS = {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
import { BaseExecutor } from "./base.js";
|
import { BaseExecutor } from "./base.js";
|
||||||
import { PROVIDERS, OAUTH_ENDPOINTS, HTTP_STATUS } from "../config/constants.js";
|
import { PROVIDERS, OAUTH_ENDPOINTS, HTTP_STATUS, ANTIGRAVITY_HEADERS, INTERNAL_REQUEST_HEADER } from "../config/constants.js";
|
||||||
|
import { deriveSessionId } from "../utils/sessionManager.js";
|
||||||
|
|
||||||
const MAX_RETRY_AFTER_MS = 10000;
|
const MAX_RETRY_AFTER_MS = 10000;
|
||||||
|
|
||||||
@@ -16,19 +17,21 @@ export class AntigravityExecutor extends BaseExecutor {
|
|||||||
return `${baseUrl}/v1internal:${action}`;
|
return `${baseUrl}/v1internal:${action}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
buildHeaders(credentials, stream = true) {
|
buildHeaders(credentials, stream = true, sessionId = null) {
|
||||||
return {
|
return {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"Authorization": `Bearer ${credentials.accessToken}`,
|
"Authorization": `Bearer ${credentials.accessToken}`,
|
||||||
"User-Agent": this.config.headers?.["User-Agent"] || "antigravity/1.104.0 darwin/arm64",
|
"User-Agent": this.config.headers?.["User-Agent"] || "antigravity/1.104.0 darwin/arm64",
|
||||||
"X-9Router-Source": "9router",
|
[INTERNAL_REQUEST_HEADER.name]: INTERNAL_REQUEST_HEADER.value,
|
||||||
|
...ANTIGRAVITY_HEADERS,
|
||||||
|
...(sessionId && { "X-Machine-Session-Id": sessionId }),
|
||||||
...(stream && { "Accept": "text/event-stream" })
|
...(stream && { "Accept": "text/event-stream" })
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
transformRequest(model, body, stream, credentials) {
|
transformRequest(model, body, stream, credentials) {
|
||||||
const projectId = credentials?.projectId || this.generateProjectId();
|
const projectId = credentials?.projectId || this.generateProjectId();
|
||||||
|
|
||||||
// Fix contents for Claude models via Antigravity
|
// Fix contents for Claude models via Antigravity
|
||||||
const contents = body.request?.contents?.map(c => {
|
const contents = body.request?.contents?.map(c => {
|
||||||
let role = c.role;
|
let role = c.role;
|
||||||
@@ -51,13 +54,13 @@ export class AntigravityExecutor extends BaseExecutor {
|
|||||||
const transformedRequest = {
|
const transformedRequest = {
|
||||||
...body.request,
|
...body.request,
|
||||||
...(contents && { contents }),
|
...(contents && { contents }),
|
||||||
sessionId: body.request?.sessionId || this.generateSessionId(),
|
sessionId: body.request?.sessionId || deriveSessionId(credentials?.email || credentials?.connectionId),
|
||||||
safetySettings: undefined,
|
safetySettings: undefined,
|
||||||
toolConfig: body.request?.tools?.length > 0
|
toolConfig: body.request?.tools?.length > 0
|
||||||
? { functionCallingConfig: { mode: "VALIDATED" } }
|
? { functionCallingConfig: { mode: "VALIDATED" } }
|
||||||
: body.request?.toolConfig
|
: body.request?.toolConfig
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...body,
|
...body,
|
||||||
project: projectId,
|
project: projectId,
|
||||||
@@ -108,7 +111,7 @@ export class AntigravityExecutor extends BaseExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
generateSessionId() {
|
generateSessionId() {
|
||||||
return `-${Math.floor(Math.random() * 9_000_000_000_000_000_000)}`;
|
return crypto.randomUUID() + Date.now().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
parseRetryHeaders(headers) {
|
parseRetryHeaders(headers) {
|
||||||
@@ -118,7 +121,7 @@ export class AntigravityExecutor extends BaseExecutor {
|
|||||||
if (retryAfter) {
|
if (retryAfter) {
|
||||||
const seconds = parseInt(retryAfter, 10);
|
const seconds = parseInt(retryAfter, 10);
|
||||||
if (!isNaN(seconds) && seconds > 0) return seconds * 1000;
|
if (!isNaN(seconds) && seconds > 0) return seconds * 1000;
|
||||||
|
|
||||||
const date = new Date(retryAfter);
|
const date = new Date(retryAfter);
|
||||||
if (!isNaN(date.getTime())) {
|
if (!isNaN(date.getTime())) {
|
||||||
const diff = date.getTime() - Date.now();
|
const diff = date.getTime() - Date.now();
|
||||||
@@ -167,9 +170,10 @@ export class AntigravityExecutor extends BaseExecutor {
|
|||||||
|
|
||||||
for (let urlIndex = 0; urlIndex < fallbackCount; urlIndex++) {
|
for (let urlIndex = 0; urlIndex < fallbackCount; urlIndex++) {
|
||||||
const url = this.buildUrl(model, stream, urlIndex);
|
const url = this.buildUrl(model, stream, urlIndex);
|
||||||
const headers = this.buildHeaders(credentials, stream);
|
|
||||||
const transformedBody = this.transformRequest(model, body, stream, credentials);
|
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
|
// Initialize retry counter for this URL
|
||||||
if (!retryAttemptsByUrl[urlIndex]) {
|
if (!retryAttemptsByUrl[urlIndex]) {
|
||||||
retryAttemptsByUrl[urlIndex] = 0;
|
retryAttemptsByUrl[urlIndex] = 0;
|
||||||
@@ -200,7 +204,7 @@ export class AntigravityExecutor extends BaseExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (retryMs && retryMs <= MAX_RETRY_AFTER_MS) {
|
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));
|
await new Promise(resolve => setTimeout(resolve, retryMs));
|
||||||
urlIndex--;
|
urlIndex--;
|
||||||
continue;
|
continue;
|
||||||
@@ -211,15 +215,15 @@ export class AntigravityExecutor extends BaseExecutor {
|
|||||||
retryAttemptsByUrl[urlIndex]++;
|
retryAttemptsByUrl[urlIndex]++;
|
||||||
// Exponential backoff: 2s, 4s, 8s...
|
// Exponential backoff: 2s, 4s, 8s...
|
||||||
const backoffMs = Math.min(1000 * (2 ** retryAttemptsByUrl[urlIndex]), MAX_RETRY_AFTER_MS);
|
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));
|
await new Promise(resolve => setTimeout(resolve, backoffMs));
|
||||||
urlIndex--;
|
urlIndex--;
|
||||||
continue;
|
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;
|
lastStatus = response.status;
|
||||||
|
|
||||||
if (urlIndex + 1 < fallbackCount) {
|
if (urlIndex + 1 < fallbackCount) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-23
@@ -2,6 +2,8 @@
|
|||||||
* Usage Fetcher - Get usage data from provider APIs
|
* Usage Fetcher - Get usage data from provider APIs
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { CLIENT_METADATA, getPlatformUserAgent } from "../config/constants.js";
|
||||||
|
|
||||||
// GitHub API config
|
// GitHub API config
|
||||||
const GITHUB_CONFIG = {
|
const GITHUB_CONFIG = {
|
||||||
apiVersion: "2022-11-28",
|
apiVersion: "2022-11-28",
|
||||||
@@ -15,7 +17,7 @@ const ANTIGRAVITY_CONFIG = {
|
|||||||
tokenUrl: "https://oauth2.googleapis.com/token",
|
tokenUrl: "https://oauth2.googleapis.com/token",
|
||||||
clientId: "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com",
|
clientId: "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com",
|
||||||
clientSecret: "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",
|
clientSecret: "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",
|
||||||
userAgent: "antigravity/1.11.3 Darwin/arm64",
|
userAgent: getPlatformUserAgent(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Codex (OpenAI) API config
|
// Codex (OpenAI) API config
|
||||||
@@ -65,23 +67,23 @@ export async function getUsageForProvider(connection) {
|
|||||||
*/
|
*/
|
||||||
function parseResetTime(resetValue) {
|
function parseResetTime(resetValue) {
|
||||||
if (!resetValue) return null;
|
if (!resetValue) return null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// If it's already a Date object
|
// If it's already a Date object
|
||||||
if (resetValue instanceof Date) {
|
if (resetValue instanceof Date) {
|
||||||
return resetValue.toISOString();
|
return resetValue.toISOString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's a number (Unix timestamp in milliseconds)
|
// If it's a number (Unix timestamp in milliseconds)
|
||||||
if (typeof resetValue === 'number') {
|
if (typeof resetValue === 'number') {
|
||||||
return new Date(resetValue).toISOString();
|
return new Date(resetValue).toISOString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's a string (ISO date or any parseable date string)
|
// If it's a string (ISO date or any parseable date string)
|
||||||
if (typeof resetValue === 'string') {
|
if (typeof resetValue === 'string') {
|
||||||
return new Date(resetValue).toISOString();
|
return new Date(resetValue).toISOString();
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(`Failed to parse reset time: ${resetValue}`, error);
|
console.warn(`Failed to parse reset time: ${resetValue}`, error);
|
||||||
@@ -98,7 +100,7 @@ async function getGitHubUsage(accessToken, providerSpecificData) {
|
|||||||
if (!accessToken) {
|
if (!accessToken) {
|
||||||
throw new Error("No GitHub access token available. Please re-authorize the connection.");
|
throw new Error("No GitHub access token available. Please re-authorize the connection.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// copilot_internal/user API requires GitHub OAuth token, not copilotToken
|
// copilot_internal/user API requires GitHub OAuth token, not copilotToken
|
||||||
const response = await fetch("https://api.github.com/copilot_internal/user", {
|
const response = await fetch("https://api.github.com/copilot_internal/user", {
|
||||||
headers: {
|
headers: {
|
||||||
@@ -123,7 +125,7 @@ async function getGitHubUsage(accessToken, providerSpecificData) {
|
|||||||
// Paid plan format
|
// Paid plan format
|
||||||
const snapshots = data.quota_snapshots;
|
const snapshots = data.quota_snapshots;
|
||||||
const resetAt = parseResetTime(data.quota_reset_date);
|
const resetAt = parseResetTime(data.quota_reset_date);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
plan: data.copilot_plan,
|
plan: data.copilot_plan,
|
||||||
resetDate: data.quota_reset_date,
|
resetDate: data.quota_reset_date,
|
||||||
@@ -138,7 +140,7 @@ async function getGitHubUsage(accessToken, providerSpecificData) {
|
|||||||
const monthlyQuotas = data.monthly_quotas || {};
|
const monthlyQuotas = data.monthly_quotas || {};
|
||||||
const usedQuotas = data.limited_user_quotas || {};
|
const usedQuotas = data.limited_user_quotas || {};
|
||||||
const resetAt = parseResetTime(data.limited_user_reset_date);
|
const resetAt = parseResetTime(data.limited_user_reset_date);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
plan: data.copilot_plan || data.access_type_sku,
|
plan: data.copilot_plan || data.access_type_sku,
|
||||||
resetDate: data.limited_user_reset_date,
|
resetDate: data.limited_user_reset_date,
|
||||||
@@ -167,7 +169,7 @@ async function getGitHubUsage(accessToken, providerSpecificData) {
|
|||||||
|
|
||||||
function formatGitHubQuotaSnapshot(quota) {
|
function formatGitHubQuotaSnapshot(quota) {
|
||||||
if (!quota) return { used: 0, total: 0, unlimited: true };
|
if (!quota) return { used: 0, total: 0, unlimited: true };
|
||||||
|
|
||||||
return {
|
return {
|
||||||
used: quota.entitlement - quota.remaining,
|
used: quota.entitlement - quota.remaining,
|
||||||
total: quota.entitlement,
|
total: quota.entitlement,
|
||||||
@@ -211,7 +213,7 @@ async function getAntigravityUsage(accessToken, providerSpecificData) {
|
|||||||
try {
|
try {
|
||||||
// First get project ID from subscription info
|
// First get project ID from subscription info
|
||||||
const projectId = await getAntigravityProjectId(accessToken);
|
const projectId = await getAntigravityProjectId(accessToken);
|
||||||
|
|
||||||
// Fetch quota data
|
// Fetch quota data
|
||||||
const response = await fetch(ANTIGRAVITY_CONFIG.quotaApiUrl, {
|
const response = await fetch(ANTIGRAVITY_CONFIG.quotaApiUrl, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -220,7 +222,11 @@ async function getAntigravityUsage(accessToken, providerSpecificData) {
|
|||||||
"User-Agent": ANTIGRAVITY_CONFIG.userAgent,
|
"User-Agent": ANTIGRAVITY_CONFIG.userAgent,
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(projectId ? { project: projectId } : {}),
|
body: JSON.stringify({
|
||||||
|
...(projectId ? { project: projectId } : {}),
|
||||||
|
metadata: CLIENT_METADATA,
|
||||||
|
mode: 1
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.status === 403) {
|
if (response.status === 403) {
|
||||||
@@ -233,7 +239,7 @@ async function getAntigravityUsage(accessToken, providerSpecificData) {
|
|||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
const quotas = {};
|
const quotas = {};
|
||||||
|
|
||||||
// Parse model quotas (inspired by vscode-antigravity-cockpit)
|
// Parse model quotas (inspired by vscode-antigravity-cockpit)
|
||||||
if (data.models) {
|
if (data.models) {
|
||||||
// Filter only recommended/important models (must match PROVIDER_MODELS ag ids)
|
// Filter only recommended/important models (must match PROVIDER_MODELS ag ids)
|
||||||
@@ -248,26 +254,26 @@ async function getAntigravityUsage(accessToken, providerSpecificData) {
|
|||||||
'gemini-3-flash',
|
'gemini-3-flash',
|
||||||
'gemini-2.5-flash',
|
'gemini-2.5-flash',
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const [modelKey, info] of Object.entries(data.models)) {
|
for (const [modelKey, info] of Object.entries(data.models)) {
|
||||||
// Skip models without quota info
|
// Skip models without quota info
|
||||||
if (!info.quotaInfo) {
|
if (!info.quotaInfo) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip internal models and non-important models
|
// Skip internal models and non-important models
|
||||||
if (info.isInternal || !importantModels.includes(modelKey)) {
|
if (info.isInternal || !importantModels.includes(modelKey)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const remainingFraction = info.quotaInfo.remainingFraction || 0;
|
const remainingFraction = info.quotaInfo.remainingFraction || 0;
|
||||||
const remainingPercentage = remainingFraction * 100;
|
const remainingPercentage = remainingFraction * 100;
|
||||||
|
|
||||||
// Convert percentage to used/total for UI compatibility
|
// Convert percentage to used/total for UI compatibility
|
||||||
const total = 1000; // Normalized base
|
const total = 1000; // Normalized base
|
||||||
const remaining = Math.round(total * remainingFraction);
|
const remaining = Math.round(total * remainingFraction);
|
||||||
const used = total - remaining;
|
const used = total - remaining;
|
||||||
|
|
||||||
// Use modelKey as key (matches PROVIDER_MODELS id)
|
// Use modelKey as key (matches PROVIDER_MODELS id)
|
||||||
quotas[modelKey] = {
|
quotas[modelKey] = {
|
||||||
used,
|
used,
|
||||||
@@ -317,7 +323,7 @@ async function getAntigravitySubscriptionInfo(accessToken) {
|
|||||||
"User-Agent": ANTIGRAVITY_CONFIG.userAgent,
|
"User-Agent": ANTIGRAVITY_CONFIG.userAgent,
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ metadata: { ideType: "ANTIGRAVITY" } }),
|
body: JSON.stringify({ metadata: CLIENT_METADATA, mode: 1 }),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) return null;
|
if (!response.ok) return null;
|
||||||
@@ -345,7 +351,7 @@ async function getClaudeUsage(accessToken) {
|
|||||||
|
|
||||||
if (settingsResponse.ok) {
|
if (settingsResponse.ok) {
|
||||||
const settings = await settingsResponse.json();
|
const settings = await settingsResponse.json();
|
||||||
|
|
||||||
// Try usage endpoint if we have org info
|
// Try usage endpoint if we have org info
|
||||||
if (settings.organization_id) {
|
if (settings.organization_id) {
|
||||||
const usageResponse = await fetch(
|
const usageResponse = await fetch(
|
||||||
@@ -402,7 +408,7 @@ async function getCodexUsage(accessToken) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
// Parse rate limit info
|
// Parse rate limit info
|
||||||
const rateLimit = data.rate_limit || {};
|
const rateLimit = data.rate_limit || {};
|
||||||
const primaryWindow = rateLimit.primary_window || {};
|
const primaryWindow = rateLimit.primary_window || {};
|
||||||
@@ -475,7 +481,7 @@ async function getKiroUsage(accessToken, providerSpecificData) {
|
|||||||
// Parse usage data from usageBreakdownList
|
// Parse usage data from usageBreakdownList
|
||||||
const usageList = data.usageBreakdownList || [];
|
const usageList = data.usageBreakdownList || [];
|
||||||
const quotaInfo = {};
|
const quotaInfo = {};
|
||||||
|
|
||||||
// Parse reset time - supports multiple formats (nextDateReset, resetDate, etc.)
|
// Parse reset time - supports multiple formats (nextDateReset, resetDate, etc.)
|
||||||
const resetAt = parseResetTime(data.nextDateReset || data.resetDate);
|
const resetAt = parseResetTime(data.nextDateReset || data.resetDate);
|
||||||
|
|
||||||
@@ -483,7 +489,7 @@ async function getKiroUsage(accessToken, providerSpecificData) {
|
|||||||
const resourceType = breakdown.resourceType?.toLowerCase() || "unknown";
|
const resourceType = breakdown.resourceType?.toLowerCase() || "unknown";
|
||||||
const used = breakdown.currentUsageWithPrecision || 0;
|
const used = breakdown.currentUsageWithPrecision || 0;
|
||||||
const total = breakdown.usageLimitWithPrecision || 0;
|
const total = breakdown.usageLimitWithPrecision || 0;
|
||||||
|
|
||||||
quotaInfo[resourceType] = {
|
quotaInfo[resourceType] = {
|
||||||
used,
|
used,
|
||||||
total,
|
total,
|
||||||
@@ -496,7 +502,7 @@ async function getKiroUsage(accessToken, providerSpecificData) {
|
|||||||
if (breakdown.freeTrialInfo) {
|
if (breakdown.freeTrialInfo) {
|
||||||
const freeUsed = breakdown.freeTrialInfo.currentUsageWithPrecision || 0;
|
const freeUsed = breakdown.freeTrialInfo.currentUsageWithPrecision || 0;
|
||||||
const freeTotal = breakdown.freeTrialInfo.usageLimitWithPrecision || 0;
|
const freeTotal = breakdown.freeTrialInfo.usageLimitWithPrecision || 0;
|
||||||
|
|
||||||
quotaInfo[`${resourceType}_freetrial`] = {
|
quotaInfo[`${resourceType}_freetrial`] = {
|
||||||
used: freeUsed,
|
used: freeUsed,
|
||||||
total: freeTotal,
|
total: freeTotal,
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export const DEFAULT_SAFETY_SETTINGS = [
|
|||||||
// Convert OpenAI content to Gemini parts
|
// Convert OpenAI content to Gemini parts
|
||||||
export function convertOpenAIContentToParts(content) {
|
export function convertOpenAIContentToParts(content) {
|
||||||
const parts = [];
|
const parts = [];
|
||||||
|
|
||||||
if (typeof content === "string") {
|
if (typeof content === "string") {
|
||||||
parts.push({ text: content });
|
parts.push({ text: content });
|
||||||
} else if (Array.isArray(content)) {
|
} else if (Array.isArray(content)) {
|
||||||
@@ -57,7 +57,7 @@ export function convertOpenAIContentToParts(content) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return parts;
|
return parts;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,9 +85,9 @@ export function generateRequestId() {
|
|||||||
return `agent-${crypto.randomUUID()}`;
|
return `agent-${crypto.randomUUID()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate session ID
|
// Generate session ID (binary-compatible format: UUID + timestamp)
|
||||||
export function generateSessionId() {
|
export function generateSessionId() {
|
||||||
return `-${Math.floor(Math.random() * 9000000000000000000)}`;
|
return crypto.randomUUID() + Date.now().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate project ID
|
// Generate project ID
|
||||||
@@ -102,7 +102,7 @@ export function generateProjectId() {
|
|||||||
// Helper: Remove unsupported keywords recursively from object/array
|
// Helper: Remove unsupported keywords recursively from object/array
|
||||||
function removeUnsupportedKeywords(obj, keywords) {
|
function removeUnsupportedKeywords(obj, keywords) {
|
||||||
if (!obj || typeof obj !== "object") return;
|
if (!obj || typeof obj !== "object") return;
|
||||||
|
|
||||||
if (Array.isArray(obj)) {
|
if (Array.isArray(obj)) {
|
||||||
for (const item of obj) {
|
for (const item of obj) {
|
||||||
removeUnsupportedKeywords(item, keywords);
|
removeUnsupportedKeywords(item, keywords);
|
||||||
@@ -126,12 +126,12 @@ function removeUnsupportedKeywords(obj, keywords) {
|
|||||||
// Convert const to enum
|
// Convert const to enum
|
||||||
function convertConstToEnum(obj) {
|
function convertConstToEnum(obj) {
|
||||||
if (!obj || typeof obj !== "object") return;
|
if (!obj || typeof obj !== "object") return;
|
||||||
|
|
||||||
if (obj.const !== undefined && !obj.enum) {
|
if (obj.const !== undefined && !obj.enum) {
|
||||||
obj.enum = [obj.const];
|
obj.enum = [obj.const];
|
||||||
delete obj.const;
|
delete obj.const;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const value of Object.values(obj)) {
|
for (const value of Object.values(obj)) {
|
||||||
if (value && typeof value === "object") {
|
if (value && typeof value === "object") {
|
||||||
convertConstToEnum(value);
|
convertConstToEnum(value);
|
||||||
@@ -142,11 +142,11 @@ function convertConstToEnum(obj) {
|
|||||||
// Convert enum values to strings (Gemini requires string enum values)
|
// Convert enum values to strings (Gemini requires string enum values)
|
||||||
function convertEnumValuesToStrings(obj) {
|
function convertEnumValuesToStrings(obj) {
|
||||||
if (!obj || typeof obj !== "object") return;
|
if (!obj || typeof obj !== "object") return;
|
||||||
|
|
||||||
if (obj.enum && Array.isArray(obj.enum)) {
|
if (obj.enum && Array.isArray(obj.enum)) {
|
||||||
obj.enum = obj.enum.map(v => String(v));
|
obj.enum = obj.enum.map(v => String(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const value of Object.values(obj)) {
|
for (const value of Object.values(obj)) {
|
||||||
if (value && typeof value === "object") {
|
if (value && typeof value === "object") {
|
||||||
convertEnumValuesToStrings(value);
|
convertEnumValuesToStrings(value);
|
||||||
@@ -157,10 +157,10 @@ function convertEnumValuesToStrings(obj) {
|
|||||||
// Merge allOf schemas
|
// Merge allOf schemas
|
||||||
function mergeAllOf(obj) {
|
function mergeAllOf(obj) {
|
||||||
if (!obj || typeof obj !== "object") return;
|
if (!obj || typeof obj !== "object") return;
|
||||||
|
|
||||||
if (obj.allOf && Array.isArray(obj.allOf)) {
|
if (obj.allOf && Array.isArray(obj.allOf)) {
|
||||||
const merged = {};
|
const merged = {};
|
||||||
|
|
||||||
for (const item of obj.allOf) {
|
for (const item of obj.allOf) {
|
||||||
if (item.properties) {
|
if (item.properties) {
|
||||||
if (!merged.properties) merged.properties = {};
|
if (!merged.properties) merged.properties = {};
|
||||||
@@ -175,12 +175,12 @@ function mergeAllOf(obj) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delete obj.allOf;
|
delete obj.allOf;
|
||||||
if (merged.properties) obj.properties = { ...obj.properties, ...merged.properties };
|
if (merged.properties) obj.properties = { ...obj.properties, ...merged.properties };
|
||||||
if (merged.required) obj.required = [...(obj.required || []), ...merged.required];
|
if (merged.required) obj.required = [...(obj.required || []), ...merged.required];
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const value of Object.values(obj)) {
|
for (const value of Object.values(obj)) {
|
||||||
if (value && typeof value === "object") {
|
if (value && typeof value === "object") {
|
||||||
mergeAllOf(value);
|
mergeAllOf(value);
|
||||||
@@ -192,12 +192,12 @@ function mergeAllOf(obj) {
|
|||||||
function selectBest(items) {
|
function selectBest(items) {
|
||||||
let bestIdx = 0;
|
let bestIdx = 0;
|
||||||
let bestScore = -1;
|
let bestScore = -1;
|
||||||
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
const item = items[i];
|
const item = items[i];
|
||||||
let score = 0;
|
let score = 0;
|
||||||
const type = item.type;
|
const type = item.type;
|
||||||
|
|
||||||
if (type === "object" || item.properties) {
|
if (type === "object" || item.properties) {
|
||||||
score = 3;
|
score = 3;
|
||||||
} else if (type === "array" || item.items) {
|
} else if (type === "array" || item.items) {
|
||||||
@@ -205,20 +205,20 @@ function selectBest(items) {
|
|||||||
} else if (type && type !== "null") {
|
} else if (type && type !== "null") {
|
||||||
score = 1;
|
score = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (score > bestScore) {
|
if (score > bestScore) {
|
||||||
bestScore = score;
|
bestScore = score;
|
||||||
bestIdx = i;
|
bestIdx = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return bestIdx;
|
return bestIdx;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flatten anyOf/oneOf
|
// Flatten anyOf/oneOf
|
||||||
function flattenAnyOfOneOf(obj) {
|
function flattenAnyOfOneOf(obj) {
|
||||||
if (!obj || typeof obj !== "object") return;
|
if (!obj || typeof obj !== "object") return;
|
||||||
|
|
||||||
if (obj.anyOf && Array.isArray(obj.anyOf) && obj.anyOf.length > 0) {
|
if (obj.anyOf && Array.isArray(obj.anyOf) && obj.anyOf.length > 0) {
|
||||||
const nonNullSchemas = obj.anyOf.filter(s => s && s.type !== "null");
|
const nonNullSchemas = obj.anyOf.filter(s => s && s.type !== "null");
|
||||||
if (nonNullSchemas.length > 0) {
|
if (nonNullSchemas.length > 0) {
|
||||||
@@ -228,7 +228,7 @@ function flattenAnyOfOneOf(obj) {
|
|||||||
Object.assign(obj, selected);
|
Object.assign(obj, selected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (obj.oneOf && Array.isArray(obj.oneOf) && obj.oneOf.length > 0) {
|
if (obj.oneOf && Array.isArray(obj.oneOf) && obj.oneOf.length > 0) {
|
||||||
const nonNullSchemas = obj.oneOf.filter(s => s && s.type !== "null");
|
const nonNullSchemas = obj.oneOf.filter(s => s && s.type !== "null");
|
||||||
if (nonNullSchemas.length > 0) {
|
if (nonNullSchemas.length > 0) {
|
||||||
@@ -238,7 +238,7 @@ function flattenAnyOfOneOf(obj) {
|
|||||||
Object.assign(obj, selected);
|
Object.assign(obj, selected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const value of Object.values(obj)) {
|
for (const value of Object.values(obj)) {
|
||||||
if (value && typeof value === "object") {
|
if (value && typeof value === "object") {
|
||||||
flattenAnyOfOneOf(value);
|
flattenAnyOfOneOf(value);
|
||||||
@@ -249,12 +249,12 @@ function flattenAnyOfOneOf(obj) {
|
|||||||
// Flatten type arrays
|
// Flatten type arrays
|
||||||
function flattenTypeArrays(obj) {
|
function flattenTypeArrays(obj) {
|
||||||
if (!obj || typeof obj !== "object") return;
|
if (!obj || typeof obj !== "object") return;
|
||||||
|
|
||||||
if (obj.type && Array.isArray(obj.type)) {
|
if (obj.type && Array.isArray(obj.type)) {
|
||||||
const nonNullTypes = obj.type.filter(t => t !== "null");
|
const nonNullTypes = obj.type.filter(t => t !== "null");
|
||||||
obj.type = nonNullTypes.length > 0 ? nonNullTypes[0] : "string";
|
obj.type = nonNullTypes.length > 0 ? nonNullTypes[0] : "string";
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const value of Object.values(obj)) {
|
for (const value of Object.values(obj)) {
|
||||||
if (value && typeof value === "object") {
|
if (value && typeof value === "object") {
|
||||||
flattenTypeArrays(value);
|
flattenTypeArrays(value);
|
||||||
@@ -266,28 +266,28 @@ function flattenTypeArrays(obj) {
|
|||||||
// Reference: CLIProxyAPI/internal/util/gemini_schema.go
|
// Reference: CLIProxyAPI/internal/util/gemini_schema.go
|
||||||
export function cleanJSONSchemaForAntigravity(schema) {
|
export function cleanJSONSchemaForAntigravity(schema) {
|
||||||
if (!schema || typeof schema !== "object") return schema;
|
if (!schema || typeof schema !== "object") return schema;
|
||||||
|
|
||||||
// Mutate directly (schema is only used once per request)
|
// Mutate directly (schema is only used once per request)
|
||||||
let cleaned = schema;
|
let cleaned = schema;
|
||||||
|
|
||||||
// Phase 1: Convert and prepare
|
// Phase 1: Convert and prepare
|
||||||
convertConstToEnum(cleaned);
|
convertConstToEnum(cleaned);
|
||||||
convertEnumValuesToStrings(cleaned);
|
convertEnumValuesToStrings(cleaned);
|
||||||
|
|
||||||
// Phase 2: Flatten complex structures
|
// Phase 2: Flatten complex structures
|
||||||
mergeAllOf(cleaned);
|
mergeAllOf(cleaned);
|
||||||
flattenAnyOfOneOf(cleaned);
|
flattenAnyOfOneOf(cleaned);
|
||||||
flattenTypeArrays(cleaned);
|
flattenTypeArrays(cleaned);
|
||||||
|
|
||||||
// Phase 3: Remove all unsupported keywords at ALL levels (including inside arrays)
|
// Phase 3: Remove all unsupported keywords at ALL levels (including inside arrays)
|
||||||
removeUnsupportedKeywords(cleaned, UNSUPPORTED_SCHEMA_CONSTRAINTS);
|
removeUnsupportedKeywords(cleaned, UNSUPPORTED_SCHEMA_CONSTRAINTS);
|
||||||
|
|
||||||
// Phase 4: Cleanup required fields recursively
|
// Phase 4: Cleanup required fields recursively
|
||||||
function cleanupRequired(obj) {
|
function cleanupRequired(obj) {
|
||||||
if (!obj || typeof obj !== "object") return;
|
if (!obj || typeof obj !== "object") return;
|
||||||
|
|
||||||
if (obj.required && Array.isArray(obj.required) && obj.properties) {
|
if (obj.required && Array.isArray(obj.required) && obj.properties) {
|
||||||
const validRequired = obj.required.filter(field =>
|
const validRequired = obj.required.filter(field =>
|
||||||
Object.prototype.hasOwnProperty.call(obj.properties, field)
|
Object.prototype.hasOwnProperty.call(obj.properties, field)
|
||||||
);
|
);
|
||||||
if (validRequired.length === 0) {
|
if (validRequired.length === 0) {
|
||||||
@@ -296,7 +296,7 @@ export function cleanJSONSchemaForAntigravity(schema) {
|
|||||||
obj.required = validRequired;
|
obj.required = validRequired;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recurse into nested objects
|
// Recurse into nested objects
|
||||||
for (const value of Object.values(obj)) {
|
for (const value of Object.values(obj)) {
|
||||||
if (value && typeof value === "object") {
|
if (value && typeof value === "object") {
|
||||||
@@ -304,13 +304,13 @@ export function cleanJSONSchemaForAntigravity(schema) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanupRequired(cleaned);
|
cleanupRequired(cleaned);
|
||||||
|
|
||||||
// Phase 5: Add placeholder for empty object schemas (Antigravity requirement)
|
// Phase 5: Add placeholder for empty object schemas (Antigravity requirement)
|
||||||
function addPlaceholders(obj) {
|
function addPlaceholders(obj) {
|
||||||
if (!obj || typeof obj !== "object") return;
|
if (!obj || typeof obj !== "object") return;
|
||||||
|
|
||||||
if (obj.type === "object") {
|
if (obj.type === "object") {
|
||||||
if (!obj.properties || Object.keys(obj.properties).length === 0) {
|
if (!obj.properties || Object.keys(obj.properties).length === 0) {
|
||||||
obj.properties = {
|
obj.properties = {
|
||||||
@@ -322,7 +322,7 @@ export function cleanJSONSchemaForAntigravity(schema) {
|
|||||||
obj.required = ["reason"];
|
obj.required = ["reason"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recurse into nested objects
|
// Recurse into nested objects
|
||||||
for (const value of Object.values(obj)) {
|
for (const value of Object.values(obj)) {
|
||||||
if (value && typeof value === "object") {
|
if (value && typeof value === "object") {
|
||||||
@@ -330,9 +330,9 @@ export function cleanJSONSchemaForAntigravity(schema) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addPlaceholders(cleaned);
|
addPlaceholders(cleaned);
|
||||||
|
|
||||||
return cleaned;
|
return cleaned;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
generateProjectId,
|
generateProjectId,
|
||||||
cleanJSONSchemaForAntigravity
|
cleanJSONSchemaForAntigravity
|
||||||
} from "../helpers/geminiHelper.js";
|
} from "../helpers/geminiHelper.js";
|
||||||
|
import { deriveSessionId } from "../../utils/sessionManager.js";
|
||||||
|
|
||||||
// Core: Convert OpenAI request to Gemini format (base for all variants)
|
// Core: Convert OpenAI request to Gemini format (base for all variants)
|
||||||
function openaiToGeminiBase(model, body, stream) {
|
function openaiToGeminiBase(model, body, stream) {
|
||||||
@@ -259,7 +260,7 @@ function wrapInCloudCodeEnvelope(model, geminiCLI, credentials = null, isAntigra
|
|||||||
userAgent: isAntigravity ? "antigravity" : "gemini-cli",
|
userAgent: isAntigravity ? "antigravity" : "gemini-cli",
|
||||||
requestId: isAntigravity ? `agent-${generateUUID()}` : generateRequestId(),
|
requestId: isAntigravity ? `agent-${generateUUID()}` : generateRequestId(),
|
||||||
request: {
|
request: {
|
||||||
sessionId: generateSessionId(),
|
sessionId: isAntigravity ? deriveSessionId(credentials?.email || credentials?.connectionId) : generateSessionId(),
|
||||||
contents: geminiCLI.contents,
|
contents: geminiCLI.contents,
|
||||||
systemInstruction: geminiCLI.systemInstruction,
|
systemInstruction: geminiCLI.systemInstruction,
|
||||||
generationConfig: geminiCLI.generationConfig,
|
generationConfig: geminiCLI.generationConfig,
|
||||||
@@ -272,11 +273,16 @@ function wrapInCloudCodeEnvelope(model, geminiCLI, credentials = null, isAntigra
|
|||||||
envelope.requestType = "agent";
|
envelope.requestType = "agent";
|
||||||
|
|
||||||
// Inject required default system prompt for Antigravity
|
// Inject required default system prompt for Antigravity
|
||||||
const defaultPart = { text: ANTIGRAVITY_DEFAULT_SYSTEM };
|
// Inject required default system prompt for Antigravity (double injection)
|
||||||
|
const systemParts = [
|
||||||
|
{ text: ANTIGRAVITY_DEFAULT_SYSTEM },
|
||||||
|
{ text: `Please ignore the following [ignore]${ANTIGRAVITY_DEFAULT_SYSTEM}[/ignore]` }
|
||||||
|
];
|
||||||
|
|
||||||
if (envelope.request.systemInstruction?.parts) {
|
if (envelope.request.systemInstruction?.parts) {
|
||||||
envelope.request.systemInstruction.parts.unshift(defaultPart);
|
envelope.request.systemInstruction.parts.unshift(...systemParts);
|
||||||
} else {
|
} else {
|
||||||
envelope.request.systemInstruction = { role: "user", parts: [defaultPart] };
|
envelope.request.systemInstruction = { role: "user", parts: systemParts };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add toolConfig for Antigravity
|
// Add toolConfig for Antigravity
|
||||||
@@ -304,7 +310,7 @@ function wrapInCloudCodeEnvelopeForClaude(model, claudeRequest, credentials = nu
|
|||||||
requestId: `agent-${generateUUID()}`,
|
requestId: `agent-${generateUUID()}`,
|
||||||
requestType: "agent",
|
requestType: "agent",
|
||||||
request: {
|
request: {
|
||||||
sessionId: generateSessionId(),
|
sessionId: deriveSessionId(credentials?.email || credentials?.connectionId),
|
||||||
contents: [],
|
contents: [],
|
||||||
generationConfig: {
|
generationConfig: {
|
||||||
temperature: claudeRequest.temperature || 1,
|
temperature: claudeRequest.temperature || 1,
|
||||||
@@ -378,9 +384,11 @@ function wrapInCloudCodeEnvelopeForClaude(model, claudeRequest, credentials = nu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add system instruction (Antigravity default)
|
// Add system instruction (Antigravity default - double injection)
|
||||||
const defaultPart = { text: ANTIGRAVITY_DEFAULT_SYSTEM };
|
const systemParts = [
|
||||||
const systemParts = [defaultPart];
|
{ text: ANTIGRAVITY_DEFAULT_SYSTEM },
|
||||||
|
{ text: `Please ignore the following [ignore]${ANTIGRAVITY_DEFAULT_SYSTEM}[/ignore]` }
|
||||||
|
];
|
||||||
|
|
||||||
if (claudeRequest.system) {
|
if (claudeRequest.system) {
|
||||||
if (Array.isArray(claudeRequest.system)) {
|
if (Array.isArray(claudeRequest.system)) {
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/**
|
||||||
|
* Session Manager for Antigravity Cloud Code
|
||||||
|
*
|
||||||
|
* Handles session ID generation and caching for prompt caching continuity.
|
||||||
|
* Mimics the Antigravity binary behavior: generates a session ID at startup
|
||||||
|
* and keeps it for the process lifetime, scoped per account/connection.
|
||||||
|
*
|
||||||
|
* Reference: antigravity-claude-proxy/src/cloudcode/session-manager.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
import crypto from "crypto";
|
||||||
|
|
||||||
|
// Runtime storage for session IDs (per connection/account)
|
||||||
|
// Key: connectionId (email or identifier), Value: sessionId
|
||||||
|
const runtimeSessionStore = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or create a session ID for the given connection.
|
||||||
|
*
|
||||||
|
* The binary generates a session ID once at startup: `rs() + Date.now()`.
|
||||||
|
* Since 9router is long-running, we simulate this "per-launch" behavior by
|
||||||
|
* storing a generated ID in memory for each connection.
|
||||||
|
*
|
||||||
|
* - If 9router restarts, the ID changes (matching binary restart behavior).
|
||||||
|
* - Within a running instance, the ID is stable for that connection.
|
||||||
|
* - This enables prompt caching while using the EXACT random logic of the binary.
|
||||||
|
*
|
||||||
|
* @param {string} connectionId - The connection identifier (email or unique ID)
|
||||||
|
* @returns {string} A stable session ID string matching binary format
|
||||||
|
*/
|
||||||
|
export function deriveSessionId(connectionId) {
|
||||||
|
if (!connectionId) {
|
||||||
|
// Fallback for requests without a connection identifier
|
||||||
|
return generateBinaryStyleId();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we already have a session ID for this connection in this process run
|
||||||
|
if (runtimeSessionStore.has(connectionId)) {
|
||||||
|
return runtimeSessionStore.get(connectionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a new ID using the binary's exact logic
|
||||||
|
const newSessionId = generateBinaryStyleId();
|
||||||
|
|
||||||
|
// Store it for future requests from this connection
|
||||||
|
runtimeSessionStore.set(connectionId, newSessionId);
|
||||||
|
|
||||||
|
return newSessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a Session ID using the binary's exact logic.
|
||||||
|
* Format: `rs() + Date.now()` where `rs()` is randomUUID
|
||||||
|
*
|
||||||
|
* @returns {string} A session ID in binary format
|
||||||
|
*/
|
||||||
|
export function generateBinaryStyleId() {
|
||||||
|
return crypto.randomUUID() + Date.now().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears all session IDs (e.g. useful for testing or explicit reset)
|
||||||
|
*/
|
||||||
|
export function clearSessionStore() {
|
||||||
|
runtimeSessionStore.clear();
|
||||||
|
}
|
||||||
@@ -1,6 +1,20 @@
|
|||||||
/**
|
/**
|
||||||
* OAuth Configuration Constants
|
* OAuth Configuration Constants
|
||||||
*/
|
*/
|
||||||
|
import { platform, arch } from "os";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the platform enum value based on the current OS.
|
||||||
|
* Matches Antigravity binary's ClientMetadata.Platform enum.
|
||||||
|
*/
|
||||||
|
function getOAuthPlatformEnum() {
|
||||||
|
const os = platform();
|
||||||
|
const architecture = arch();
|
||||||
|
if (os === "darwin") return architecture === "arm64" ? 2 : 1;
|
||||||
|
if (os === "linux") return architecture === "arm64" ? 4 : 3;
|
||||||
|
if (os === "win32") return 5;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Claude OAuth Configuration (Authorization Code Flow with PKCE)
|
// Claude OAuth Configuration (Authorization Code Flow with PKCE)
|
||||||
export const CLAUDE_CONFIG = {
|
export const CLAUDE_CONFIG = {
|
||||||
@@ -83,9 +97,17 @@ export const ANTIGRAVITY_CONFIG = {
|
|||||||
onboardUserEndpoint: "https://cloudcode-pa.googleapis.com/v1internal:onboardUser",
|
onboardUserEndpoint: "https://cloudcode-pa.googleapis.com/v1internal:onboardUser",
|
||||||
loadCodeAssistUserAgent: "google-api-nodejs-client/9.15.1",
|
loadCodeAssistUserAgent: "google-api-nodejs-client/9.15.1",
|
||||||
loadCodeAssistApiClient: "google-cloud-sdk vscode_cloudshelleditor/0.1",
|
loadCodeAssistApiClient: "google-cloud-sdk vscode_cloudshelleditor/0.1",
|
||||||
loadCodeAssistClientMetadata: `{"ideType":"IDE_UNSPECIFIED","platform":"PLATFORM_UNSPECIFIED","pluginType":"GEMINI"}`,
|
loadCodeAssistClientMetadata: JSON.stringify({ ideType: 9, platform: getOAuthPlatformEnum(), pluginType: 2 }),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get client metadata using numeric enum values for API calls.
|
||||||
|
* @returns {{ ideType: number, platform: number, pluginType: number }}
|
||||||
|
*/
|
||||||
|
export function getOAuthClientMetadata() {
|
||||||
|
return { ideType: 9, platform: getOAuthPlatformEnum(), pluginType: 2 };
|
||||||
|
}
|
||||||
|
|
||||||
// OpenAI OAuth Configuration (Authorization Code Flow with PKCE)
|
// OpenAI OAuth Configuration (Authorization Code Flow with PKCE)
|
||||||
export const OPENAI_CONFIG = {
|
export const OPENAI_CONFIG = {
|
||||||
clientId: "app_EMoamEEZ73f0CkXaXp7hrann",
|
clientId: "app_EMoamEEZ73f0CkXaXp7hrann",
|
||||||
|
|||||||
+18
-16
@@ -14,6 +14,7 @@ import {
|
|||||||
GITHUB_CONFIG,
|
GITHUB_CONFIG,
|
||||||
KIRO_CONFIG,
|
KIRO_CONFIG,
|
||||||
CURSOR_CONFIG,
|
CURSOR_CONFIG,
|
||||||
|
getOAuthClientMetadata,
|
||||||
} from "./constants/oauth";
|
} from "./constants/oauth";
|
||||||
|
|
||||||
// Provider configurations
|
// Provider configurations
|
||||||
@@ -184,7 +185,8 @@ const PROVIDERS = {
|
|||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
metadata: { ideType: "IDE_UNSPECIFIED", platform: "PLATFORM_UNSPECIFIED", pluginType: "GEMINI" },
|
metadata: getOAuthClientMetadata(),
|
||||||
|
mode: 1,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -254,7 +256,7 @@ const PROVIDERS = {
|
|||||||
"X-Goog-Api-Client": ANTIGRAVITY_CONFIG.loadCodeAssistApiClient,
|
"X-Goog-Api-Client": ANTIGRAVITY_CONFIG.loadCodeAssistApiClient,
|
||||||
"Client-Metadata": ANTIGRAVITY_CONFIG.loadCodeAssistClientMetadata,
|
"Client-Metadata": ANTIGRAVITY_CONFIG.loadCodeAssistClientMetadata,
|
||||||
};
|
};
|
||||||
const metadata = { ideType: "IDE_UNSPECIFIED", platform: "PLATFORM_UNSPECIFIED", pluginType: "GEMINI" };
|
const metadata = getOAuthClientMetadata();
|
||||||
|
|
||||||
// Fetch user info
|
// Fetch user info
|
||||||
const userInfoRes = await fetch(`${ANTIGRAVITY_CONFIG.userInfoUrl}?alt=json`, {
|
const userInfoRes = await fetch(`${ANTIGRAVITY_CONFIG.userInfoUrl}?alt=json`, {
|
||||||
@@ -269,7 +271,7 @@ const PROVIDERS = {
|
|||||||
const loadRes = await fetch(ANTIGRAVITY_CONFIG.loadCodeAssistEndpoint, {
|
const loadRes = await fetch(ANTIGRAVITY_CONFIG.loadCodeAssistEndpoint, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers,
|
headers,
|
||||||
body: JSON.stringify({ metadata }),
|
body: JSON.stringify({ metadata, mode: 1 }),
|
||||||
});
|
});
|
||||||
if (loadRes.ok) {
|
if (loadRes.ok) {
|
||||||
const data = await loadRes.json();
|
const data = await loadRes.json();
|
||||||
@@ -295,7 +297,7 @@ const PROVIDERS = {
|
|||||||
const onboardRes = await fetch(ANTIGRAVITY_CONFIG.onboardUserEndpoint, {
|
const onboardRes = await fetch(ANTIGRAVITY_CONFIG.onboardUserEndpoint, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers,
|
headers,
|
||||||
body: JSON.stringify({ tierId, metadata, cloudaicompanionProject: projectId }),
|
body: JSON.stringify({ tierId, metadata, cloudaicompanionProject: projectId, mode: 1 }),
|
||||||
});
|
});
|
||||||
if (onboardRes.ok) {
|
if (onboardRes.ok) {
|
||||||
const result = await onboardRes.json();
|
const result = await onboardRes.json();
|
||||||
@@ -727,9 +729,9 @@ export function generateAuthData(providerName, redirectUri) {
|
|||||||
*/
|
*/
|
||||||
export async function exchangeTokens(providerName, code, redirectUri, codeVerifier, state) {
|
export async function exchangeTokens(providerName, code, redirectUri, codeVerifier, state) {
|
||||||
const provider = getProvider(providerName);
|
const provider = getProvider(providerName);
|
||||||
|
|
||||||
const tokens = await provider.exchangeToken(provider.config, code, redirectUri, codeVerifier, state);
|
const tokens = await provider.exchangeToken(provider.config, code, redirectUri, codeVerifier, state);
|
||||||
|
|
||||||
let extra = null;
|
let extra = null;
|
||||||
if (provider.postExchange) {
|
if (provider.postExchange) {
|
||||||
extra = await provider.postExchange(tokens);
|
extra = await provider.postExchange(tokens);
|
||||||
@@ -761,9 +763,9 @@ export async function pollForToken(providerName, deviceCode, codeVerifier, extra
|
|||||||
if (provider.flowType !== "device_code") {
|
if (provider.flowType !== "device_code") {
|
||||||
throw new Error(`Provider ${providerName} does not support device code flow`);
|
throw new Error(`Provider ${providerName} does not support device code flow`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await provider.pollToken(provider.config, deviceCode, codeVerifier, extraData);
|
const result = await provider.pollToken(provider.config, deviceCode, codeVerifier, extraData);
|
||||||
|
|
||||||
if (result.ok) {
|
if (result.ok) {
|
||||||
// For device code flows, success is only when we have an access token
|
// For device code flows, success is only when we have an access token
|
||||||
if (result.data.access_token) {
|
if (result.data.access_token) {
|
||||||
@@ -777,23 +779,23 @@ export async function pollForToken(providerName, deviceCode, codeVerifier, extra
|
|||||||
// Check if it's still pending authorization
|
// Check if it's still pending authorization
|
||||||
if (result.data.error === 'authorization_pending' || result.data.error === 'slow_down') {
|
if (result.data.error === 'authorization_pending' || result.data.error === 'slow_down') {
|
||||||
// This is not a failure, just still waiting
|
// This is not a failure, just still waiting
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: result.data.error,
|
error: result.data.error,
|
||||||
errorDescription: result.data.error_description || result.data.message,
|
errorDescription: result.data.error_description || result.data.message,
|
||||||
pending: result.data.error === 'authorization_pending'
|
pending: result.data.error === 'authorization_pending'
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// Actual error
|
// Actual error
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: result.data.error || 'no_access_token',
|
error: result.data.error || 'no_access_token',
|
||||||
errorDescription: result.data.error_description || result.data.message || 'No access token received'
|
errorDescription: result.data.error_description || result.data.message || 'No access token received'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { success: false, error: result.data.error, errorDescription: result.data.error_description };
|
return { success: false, error: result.data.error, errorDescription: result.data.error_description };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
|
import { platform, arch } from "os";
|
||||||
import open from "open";
|
import open from "open";
|
||||||
import { ANTIGRAVITY_CONFIG } from "../constants/oauth.js";
|
import { ANTIGRAVITY_CONFIG } from "../constants/oauth.js";
|
||||||
import { getServerCredentials } from "../config/index.js";
|
import { getServerCredentials } from "../config/index.js";
|
||||||
@@ -92,12 +93,20 @@ export class AntigravityService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get metadata object for API calls
|
* Get metadata object for API calls
|
||||||
|
* Uses numeric enum values matching Antigravity binary specifications
|
||||||
*/
|
*/
|
||||||
getMetadata() {
|
getMetadata() {
|
||||||
|
const os = platform();
|
||||||
|
const architecture = arch();
|
||||||
|
let platformEnum = 0; // UNSPECIFIED
|
||||||
|
if (os === "darwin") platformEnum = architecture === "arm64" ? 2 : 1;
|
||||||
|
else if (os === "linux") platformEnum = architecture === "arm64" ? 4 : 3;
|
||||||
|
else if (os === "win32") platformEnum = 5;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ideType: "IDE_UNSPECIFIED",
|
ideType: 9, // ANTIGRAVITY
|
||||||
platform: "PLATFORM_UNSPECIFIED",
|
platform: platformEnum,
|
||||||
pluginType: "GEMINI",
|
pluginType: 2, // GEMINI
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +117,7 @@ export class AntigravityService {
|
|||||||
const response = await fetch(this.config.loadCodeAssistEndpoint, {
|
const response = await fetch(this.config.loadCodeAssistEndpoint, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: this.getApiHeaders(accessToken),
|
headers: this.getApiHeaders(accessToken),
|
||||||
body: JSON.stringify({ metadata: this.getMetadata() }),
|
body: JSON.stringify({ metadata: this.getMetadata(), mode: 1 }),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -117,7 +126,7 @@ export class AntigravityService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
// Extract project ID
|
// Extract project ID
|
||||||
let projectId = data.cloudaicompanionProject;
|
let projectId = data.cloudaicompanionProject;
|
||||||
if (typeof projectId === 'object' && projectId !== null && projectId.id) {
|
if (typeof projectId === 'object' && projectId !== null && projectId.id) {
|
||||||
@@ -149,6 +158,7 @@ export class AntigravityService {
|
|||||||
tierId,
|
tierId,
|
||||||
metadata: this.getMetadata(),
|
metadata: this.getMetadata(),
|
||||||
cloudaicompanionProject: projectId,
|
cloudaicompanionProject: projectId,
|
||||||
|
mode: 1,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -166,7 +176,7 @@ export class AntigravityService {
|
|||||||
async completeOnboarding(accessToken, projectId, tierId, maxRetries = 10) {
|
async completeOnboarding(accessToken, projectId, tierId, maxRetries = 10) {
|
||||||
for (let i = 0; i < maxRetries; i++) {
|
for (let i = 0; i < maxRetries; i++) {
|
||||||
const result = await this.onboardUser(accessToken, projectId, tierId);
|
const result = await this.onboardUser(accessToken, projectId, tierId);
|
||||||
|
|
||||||
if (result.done === true) {
|
if (result.done === true) {
|
||||||
// Extract final project ID from response
|
// Extract final project ID from response
|
||||||
let finalProjectId = projectId;
|
let finalProjectId = projectId;
|
||||||
@@ -301,7 +311,7 @@ export class AntigravityService {
|
|||||||
|
|
||||||
// Load Code Assist to get project ID and tier
|
// Load Code Assist to get project ID and tier
|
||||||
const { projectId, tierId } = await this.loadCodeAssist(tokens.access_token);
|
const { projectId, tierId } = await this.loadCodeAssist(tokens.access_token);
|
||||||
|
|
||||||
if (!projectId) {
|
if (!projectId) {
|
||||||
throw new Error("No Google Cloud Project found. Please ensure you have a GCP project with Gemini Code Assist enabled.");
|
throw new Error("No Google Cloud Project found. Please ensure you have a GCP project with Gemini Code Assist enabled.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
import open from "open";
|
import open from "open";
|
||||||
import { GEMINI_CONFIG } from "../constants/oauth.js";
|
import { GEMINI_CONFIG, getOAuthClientMetadata } from "../constants/oauth.js";
|
||||||
import { getServerCredentials } from "../config/index.js";
|
import { getServerCredentials } from "../config/index.js";
|
||||||
import { startLocalServer } from "../utils/server.js";
|
import { startLocalServer } from "../utils/server.js";
|
||||||
import { spinner as createSpinner } from "../utils/ui.js";
|
import { spinner as createSpinner } from "../utils/ui.js";
|
||||||
@@ -71,18 +71,11 @@ export class GeminiCLIService {
|
|||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"User-Agent": "google-api-nodejs-client/9.15.1",
|
"User-Agent": "google-api-nodejs-client/9.15.1",
|
||||||
"X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1",
|
"X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1",
|
||||||
"Client-Metadata": JSON.stringify({
|
"Client-Metadata": JSON.stringify(getOAuthClientMetadata())
|
||||||
ideType: "IDE_UNSPECIFIED",
|
|
||||||
platform: "PLATFORM_UNSPECIFIED",
|
|
||||||
pluginType: "GEMINI"
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
metadata: {
|
metadata: getOAuthClientMetadata(),
|
||||||
ideType: "IDE_UNSPECIFIED",
|
mode: 1
|
||||||
platform: "PLATFORM_UNSPECIFIED",
|
|
||||||
pluginType: "GEMINI"
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -93,7 +86,7 @@ export class GeminiCLIService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
// Extract project ID
|
// Extract project ID
|
||||||
let projectId = "";
|
let projectId = "";
|
||||||
if (typeof data.cloudaicompanionProject === "string") {
|
if (typeof data.cloudaicompanionProject === "string") {
|
||||||
|
|||||||
+2
-1
@@ -6,6 +6,7 @@ const { promisify } = require("util");
|
|||||||
const os = require("os");
|
const os = require("os");
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
|
const INTERNAL_REQUEST_HEADER = { name: "x-request-source", value: "local" };
|
||||||
const TARGET_HOST = "daily-cloudcode-pa.googleapis.com";
|
const TARGET_HOST = "daily-cloudcode-pa.googleapis.com";
|
||||||
const LOCAL_PORT = 443;
|
const LOCAL_PORT = 443;
|
||||||
const ROUTER_URL = "http://localhost:20128/v1/chat/completions";
|
const ROUTER_URL = "http://localhost:20128/v1/chat/completions";
|
||||||
@@ -174,7 +175,7 @@ const server = https.createServer(sslOptions, async (req, res) => {
|
|||||||
if (bodyBuffer.length > 0) saveRequestLog(req.url, bodyBuffer);
|
if (bodyBuffer.length > 0) saveRequestLog(req.url, bodyBuffer);
|
||||||
|
|
||||||
// Anti-loop: requests from 9Router bypass interception
|
// Anti-loop: requests from 9Router bypass interception
|
||||||
if (req.headers["x-9router-source"] === "9router") {
|
if (req.headers[INTERNAL_REQUEST_HEADER.name] === INTERNAL_REQUEST_HEADER.value) {
|
||||||
return passthrough(req, res, bodyBuffer);
|
return passthrough(req, res, bodyBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user