From 222e22fa5304a56625193d29ea0c66fad1511bb2 Mon Sep 17 00:00:00 2001 From: lukmanfauzie <119554523+lukmanfauzie@users.noreply.github.com> Date: Sun, 26 Apr 2026 17:47:13 +0800 Subject: [PATCH] Fix GitHub Copilot agent mode with Antigravity Co-authored-by: Copilot --- open-sse/executors/antigravity.js | 25 ++++++++++++++++++--- open-sse/handlers/chatCore.js | 2 +- open-sse/translator/helpers/geminiHelper.js | 24 +++++++++++--------- open-sse/translator/index.js | 8 +++---- open-sse/utils/clientDetector.js | 7 ++++++ 5 files changed, 47 insertions(+), 19 deletions(-) diff --git a/open-sse/executors/antigravity.js b/open-sse/executors/antigravity.js index 4753d41e..d3f41784 100644 --- a/open-sse/executors/antigravity.js +++ b/open-sse/executors/antigravity.js @@ -263,21 +263,34 @@ export class AntigravityExecutor extends BaseExecutor { * - Inject AG default decoy tools after client tools * Returns { cloakedBody, toolNameMap } where toolNameMap maps suffixed → original */ - static cloakTools(body) { + static cloakTools(body, clientTool = null) { const tools = body.request?.tools; if (!tools || tools.length === 0) { return { cloakedBody: body, toolNameMap: null }; } + const isCopilot = clientTool === "github-copilot"; const toolNameMap = new Map(); const clientDeclarations = []; + const decoyNames = new Set(AG_DECOY_TOOLS.map(tool => tool.name)); // First: collect 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 + // For GitHub Copilot, avoid emitting duplicate native Antigravity tool names. + // Keep the decoys only once in the final declaration list. + if (isCopilot && AG_DEFAULT_TOOLS.has(func.name)) { + continue; + } + + // Skip if already covered by decoys for Copilot + if (isCopilot && decoyNames.has(func.name)) { + continue; + } + + // Preserve native AG names for non-Copilot clients if (AG_DEFAULT_TOOLS.has(func.name)) { clientDeclarations.push(func); continue; @@ -290,7 +303,13 @@ export class AntigravityExecutor extends BaseExecutor { } // Client tools first, then AG decoy tools - const allDeclarations = [...clientDeclarations, ...AG_DECOY_TOOLS]; + const allDeclarations = []; + const seenNames = new Set(); + for (const decl of [...clientDeclarations, ...AG_DECOY_TOOLS]) { + if (!decl?.name || seenNames.has(decl.name)) continue; + seenNames.add(decl.name); + allDeclarations.push(decl); + } // Rename tool names in conversation history (contents) const cloakedContents = body.request?.contents?.map(msg => { diff --git a/open-sse/handlers/chatCore.js b/open-sse/handlers/chatCore.js index 79ec2f41..3cc751a5 100644 --- a/open-sse/handlers/chatCore.js +++ b/open-sse/handlers/chatCore.js @@ -82,7 +82,7 @@ export async function handleChatCore({ body, modelInfo, credentials, log, onCred log?.debug?.("PASSTHROUGH", `${clientTool} → ${provider} | native lossless`); translatedBody = { ...body, model }; } else { - translatedBody = translateRequest(sourceFormat, targetFormat, model, body, stream, credentials, provider, reqLogger, stripList, connectionId, rtkEnabled); + translatedBody = translateRequest(sourceFormat, targetFormat, model, body, stream, credentials, provider, reqLogger, stripList, connectionId, rtkEnabled, clientTool); if (!translatedBody) { trackPendingRequest(model, provider, connectionId, false, true); return createErrorResult(HTTP_STATUS.BAD_REQUEST, `Failed to translate request for ${sourceFormat} → ${targetFormat}`); diff --git a/open-sse/translator/helpers/geminiHelper.js b/open-sse/translator/helpers/geminiHelper.js index 623b4ea9..35768f06 100644 --- a/open-sse/translator/helpers/geminiHelper.js +++ b/open-sse/translator/helpers/geminiHelper.js @@ -8,9 +8,9 @@ export const UNSUPPORTED_SCHEMA_CONSTRAINTS = [ // Claude rejects these in VALIDATED mode "default", "examples", // JSON Schema meta keywords - "$schema", "$defs", "definitions", "const", "$ref", + "$schema", "$defs", "definitions", "const", "$ref", "$comment", // Object validation keywords (not supported) - "additionalProperties", "propertyNames", "patternProperties", + "additionalProperties", "propertyNames", "patternProperties", "enumDescriptions", // Complex schema keywords (handled by flattenAnyOfOneOf/mergeAllOf) "anyOf", "oneOf", "allOf", "not", // Dependency keywords (not supported) @@ -111,16 +111,18 @@ function removeUnsupportedKeywords(obj, keywords) { for (const item of obj) { removeUnsupportedKeywords(item, keywords); } - } else { - for (const key of Object.keys(obj)) { - if (keywords.includes(key) || key.startsWith("x-")) { - delete obj[key]; - } + return; + } + + for (const key of Object.keys(obj)) { + if (keywords.includes(key) || key.startsWith("x-")) { + delete obj[key]; + continue; } - for (const value of Object.values(obj)) { - if (value && typeof value === "object") { - removeUnsupportedKeywords(value, keywords); - } + + const value = obj[key]; + if (value && typeof value === "object") { + removeUnsupportedKeywords(value, keywords); } } } diff --git a/open-sse/translator/index.js b/open-sse/translator/index.js index 6b20862b..442693a3 100644 --- a/open-sse/translator/index.js +++ b/open-sse/translator/index.js @@ -71,7 +71,7 @@ function stripContentTypes(body, stripList = []) { } // Translate request: source -> openai -> target -export function translateRequest(sourceFormat, targetFormat, model, body, stream = true, credentials = null, provider = null, reqLogger = null, stripList = [], connectionId = null, rtkEnabled = false) { +export function translateRequest(sourceFormat, targetFormat, model, body, stream = true, credentials = null, provider = null, reqLogger = null, stripList = [], connectionId = null, rtkEnabled = false, clientTool = null) { ensureInitialized(); let result = body; @@ -141,9 +141,9 @@ export function translateRequest(sourceFormat, targetFormat, model, body, stream } // 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); + // Only apply for GitHub Copilot requests so other clients are unaffected. + if (provider === FORMATS.ANTIGRAVITY && clientTool === "github-copilot") { + const { cloakedBody, toolNameMap } = AntigravityExecutor.cloakTools(result, clientTool); result = cloakedBody; if (toolNameMap?.size > 0) { result._toolNameMap = toolNameMap; diff --git a/open-sse/utils/clientDetector.js b/open-sse/utils/clientDetector.js index 99f31970..1368b97a 100644 --- a/open-sse/utils/clientDetector.js +++ b/open-sse/utils/clientDetector.js @@ -20,10 +20,17 @@ const NATIVE_PAIRS = { export function detectClientTool(headers = {}, body = {}) { const ua = (headers["user-agent"] || "").toLowerCase(); const xApp = (headers["x-app"] || "").toLowerCase(); + const openaiIntent = (headers["openai-intent"] || "").toLowerCase(); + const initiator = (headers["x-initiator"] || headers["X-Initiator"] || "").toLowerCase(); // Antigravity: detected via body field (not header) if (body.userAgent === "antigravity") return "antigravity"; + // GitHub Copilot / OAI compatible extension using Copilot chat headers + if (ua.includes("githubcopilotchat") || openaiIntent === "conversation-panel" || initiator === "user") { + return "github-copilot"; + } + // Claude Code / Claude CLI if (ua.includes("claude-cli") || ua.includes("claude-code") || xApp === "cli") return "claude";