diff --git a/open-sse/config/appConstants.js b/open-sse/config/appConstants.js index 042fe2e..60a94ea 100644 --- a/open-sse/config/appConstants.js +++ b/open-sse/config/appConstants.js @@ -62,6 +62,34 @@ export const CLIENT_METADATA = { // Internal anti-loop header export const INTERNAL_REQUEST_HEADER = { name: "x-request-source", value: "local" }; +// Prefix added to client tools when forwarding to Antigravity provider (anti-ban cloaking) +export const AG_TOOL_PREFIX = "ide_"; + +// AG native default tools — kept as decoys with neutral description/properties +// These names must match exactly what AG sends in the real request log +export const AG_DEFAULT_TOOLS = new Set([ + "browser_subagent", + "command_status", + "find_by_name", + "generate_image", + "grep_search", + "list_dir", + "list_resources", + "multi_replace_file_content", + "notify_user", + "read_resource", + "read_terminal", + "read_url_content", + "replace_file_content", + "run_command", + "search_web", + "send_command_input", + "task_boundary", + "view_content_chunk", + "view_file", + "write_to_file" +]); + // Antigravity chat/stream headers export const ANTIGRAVITY_HEADERS = { "User-Agent": `antigravity/1.107.0 ${platform()}/${arch()}` diff --git a/open-sse/executors/antigravity.js b/open-sse/executors/antigravity.js index 3912f60..e5a2d9c 100644 --- a/open-sse/executors/antigravity.js +++ b/open-sse/executors/antigravity.js @@ -1,7 +1,7 @@ import crypto from "crypto"; import { BaseExecutor } from "./base.js"; import { PROVIDERS } from "../config/providers.js"; -import { OAUTH_ENDPOINTS, ANTIGRAVITY_HEADERS, INTERNAL_REQUEST_HEADER } from "../config/appConstants.js"; +import { OAUTH_ENDPOINTS, ANTIGRAVITY_HEADERS, INTERNAL_REQUEST_HEADER, AG_DEFAULT_TOOLS, AG_TOOL_PREFIX } from "../config/appConstants.js"; import { HTTP_STATUS } from "../config/runtimeConfig.js"; import { deriveSessionId } from "../utils/sessionManager.js"; import { proxyAwareFetch } from "../utils/proxyFetch.js"; @@ -256,6 +256,197 @@ export class AntigravityExecutor extends BaseExecutor { throw lastError || new Error(`All ${fallbackCount} URLs failed with status ${lastStatus}`); } + + /** + * Cloak tools before sending to Antigravity provider (anti-ban): + * - Rename client tools with ide_ prefix + * - Inject AG default decoy tools (same names, neutral description/properties) + * Returns { cloakedBody, toolNameMap } where toolNameMap maps prefixed → original + */ + static cloakTools(body) { + const tools = body.request?.tools; + if (!tools || tools.length === 0) { + return { cloakedBody: body, toolNameMap: null }; + } + + const toolNameMap = new Map(); + const allDeclarations = []; + + // First: add AG decoy tools (to appear first in the list) + allDeclarations.push(...AG_DECOY_TOOLS); + + // Second: add renamed client tools + for (const toolGroup of tools) { + if (!toolGroup.functionDeclarations) continue; + + for (const func of toolGroup.functionDeclarations) { + // Skip if already an AG default tool name + if (AG_DEFAULT_TOOLS.has(func.name)) { + allDeclarations.push(func); + continue; + } + + const prefixed = `${AG_TOOL_PREFIX}${func.name}`; + toolNameMap.set(prefixed, func.name); + allDeclarations.push({ ...func, name: prefixed }); + } + } + + // Rename tool names in conversation history (contents) + const cloakedContents = body.request?.contents?.map(msg => { + if (!msg.parts) return msg; + + const cloakedParts = msg.parts.map(part => { + // Rename functionCall.name + if (part.functionCall && !AG_DEFAULT_TOOLS.has(part.functionCall.name)) { + return { + ...part, + functionCall: { + ...part.functionCall, + name: `${AG_TOOL_PREFIX}${part.functionCall.name}` + } + }; + } + + // Rename functionResponse.name + if (part.functionResponse && !AG_DEFAULT_TOOLS.has(part.functionResponse.name)) { + return { + ...part, + functionResponse: { + ...part.functionResponse, + name: `${AG_TOOL_PREFIX}${part.functionResponse.name}` + } + }; + } + + return part; + }); + + return { ...msg, parts: cloakedParts }; + }); + + // Single functionDeclarations group with decoys first, then renamed client tools + return { + cloakedBody: { + ...body, + request: { + ...body.request, + tools: [{ functionDeclarations: allDeclarations }], + contents: cloakedContents || body.request.contents + } + }, + toolNameMap + }; + } } +// AG decoy tools — same names as AG native defaults, neutral description & minimal properties +const AG_DECOY_TOOLS = [ + { + name: "browser_subagent", + description: "This tool is not available in the current context.", + parameters: { type: "OBJECT", properties: {}, required: [] } + }, + { + name: "command_status", + description: "This tool is not available in the current context.", + parameters: { type: "OBJECT", properties: {}, required: [] } + }, + { + name: "find_by_name", + description: "This tool is not available in the current context.", + parameters: { type: "OBJECT", properties: {}, required: [] } + }, + { + name: "generate_image", + description: "This tool is not available in the current context.", + parameters: { type: "OBJECT", properties: {}, required: [] } + }, + { + name: "grep_search", + description: "This tool is not available in the current context.", + parameters: { type: "OBJECT", properties: {}, required: [] } + }, + { + name: "list_dir", + description: "This tool is not available in the current context.", + parameters: { type: "OBJECT", properties: {}, required: [] } + }, + { + name: "list_resources", + description: "This tool is not available in the current context.", + parameters: { type: "OBJECT", properties: {}, required: [] } + }, + { + name: "mcp_sequential-thinking_sequentialthinking", + description: "This tool is not available in the current context.", + parameters: { type: "OBJECT", properties: {}, required: [] } + }, + { + name: "multi_replace_file_content", + description: "This tool is not available in the current context.", + parameters: { type: "OBJECT", properties: {}, required: [] } + }, + { + name: "notify_user", + description: "This tool is not available in the current context.", + parameters: { type: "OBJECT", properties: {}, required: [] } + }, + { + name: "read_resource", + description: "This tool is not available in the current context.", + parameters: { type: "OBJECT", properties: {}, required: [] } + }, + { + name: "read_terminal", + description: "This tool is not available in the current context.", + parameters: { type: "OBJECT", properties: {}, required: [] } + }, + { + name: "read_url_content", + description: "This tool is not available in the current context.", + parameters: { type: "OBJECT", properties: {}, required: [] } + }, + { + name: "replace_file_content", + description: "This tool is not available in the current context.", + parameters: { type: "OBJECT", properties: {}, required: [] } + }, + { + name: "run_command", + description: "This tool is not available in the current context.", + parameters: { type: "OBJECT", properties: {}, required: [] } + }, + { + name: "search_web", + description: "This tool is not available in the current context.", + parameters: { type: "OBJECT", properties: {}, required: [] } + }, + { + name: "send_command_input", + description: "This tool is not available in the current context.", + parameters: { type: "OBJECT", properties: {}, required: [] } + }, + { + name: "task_boundary", + description: "This tool is not available in the current context.", + parameters: { type: "OBJECT", properties: {}, required: [] } + }, + { + name: "view_content_chunk", + description: "This tool is not available in the current context.", + parameters: { type: "OBJECT", properties: {}, required: [] } + }, + { + name: "view_file", + description: "This tool is not available in the current context.", + parameters: { type: "OBJECT", properties: {}, required: [] } + }, + { + name: "write_to_file", + description: "This tool is not available in the current context.", + parameters: { type: "OBJECT", properties: {}, required: [] } + } +]; + export default AntigravityExecutor; diff --git a/open-sse/translator/index.js b/open-sse/translator/index.js index d0d1c95..b29686a 100644 --- a/open-sse/translator/index.js +++ b/open-sse/translator/index.js @@ -3,6 +3,7 @@ import { ensureToolCallIds, fixMissingToolResponses } from "./helpers/toolCallHe import { prepareClaudeRequest } from "./helpers/claudeHelper.js"; import { filterToOpenAIFormat } from "./helpers/openaiHelper.js"; import { normalizeThinkingConfig } from "../services/provider.js"; +import { AntigravityExecutor } from "../executors/antigravity.js"; // Registry for translators const requestRegistry = new Map(); @@ -97,6 +98,16 @@ export function translateRequest(sourceFormat, targetFormat, model, body, stream result = prepareClaudeRequest(result, provider, apiKey); } + // Antigravity cloaking: rename client tools + inject decoys (anti-ban) + // Skip if client is native AG (userAgent = antigravity) + if (provider === FORMATS.ANTIGRAVITY && body.userAgent !== FORMATS.ANTIGRAVITY) { + const { cloakedBody, toolNameMap } = AntigravityExecutor.cloakTools(result); + result = cloakedBody; + if (toolNameMap?.size > 0) { + result._toolNameMap = toolNameMap; + } + } + return result; } diff --git a/open-sse/translator/response/gemini-to-openai.js b/open-sse/translator/response/gemini-to-openai.js index 96e93cb..7512396 100644 --- a/open-sse/translator/response/gemini-to-openai.js +++ b/open-sse/translator/response/gemini-to-openai.js @@ -59,7 +59,9 @@ export function geminiToOpenAIResponse(chunk, state) { } if (hasFunctionCall) { - const fcName = part.functionCall.name; + const rawName = part.functionCall.name; + // Restore original tool name from mapping (AG cloaking) + const fcName = state.toolNameMap?.get(rawName) || rawName; const fcArgs = part.functionCall.args || {}; const toolCallIndex = state.functionIndex++; @@ -107,7 +109,9 @@ export function geminiToOpenAIResponse(chunk, state) { // Function call if (part.functionCall) { - const fcName = part.functionCall.name; + const rawName = part.functionCall.name; + // Restore original tool name from mapping (AG cloaking) + const fcName = state.toolNameMap?.get(rawName) || rawName; const fcArgs = part.functionCall.args || {}; const toolCallIndex = state.functionIndex++; diff --git a/open-sse/translator/response/openai-to-antigravity.js b/open-sse/translator/response/openai-to-antigravity.js index b1c3ebf..8e8d98c 100644 --- a/open-sse/translator/response/openai-to-antigravity.js +++ b/open-sse/translator/response/openai-to-antigravity.js @@ -59,9 +59,11 @@ export function openaiToAntigravityResponse(chunk, state) { const accum = state._toolCallAccum[idx]; let args = {}; try { args = JSON.parse(accum.arguments); } catch { /* empty */ } + // Restore original tool name if it was prefixed during cloaking + const originalName = state.toolNameMap?.get(accum.name) || accum.name; parts.push({ functionCall: { - name: accum.name, + name: originalName, args } }); diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/MitmToolCard.js b/src/app/(dashboard)/dashboard/cli-tools/components/MitmToolCard.js index f6b18f4..d7a2848 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/components/MitmToolCard.js +++ b/src/app/(dashboard)/dashboard/cli-tools/components/MitmToolCard.js @@ -240,7 +240,7 @@ export default function MitmToolCard({ {/* Warning below button */} {warning && ( -
+
warning {warning}