Fix codex

This commit is contained in:
decolua
2026-05-26 11:35:39 +07:00
parent b876e0225a
commit a648a42bdb
14 changed files with 645 additions and 52 deletions
+58
View File
@@ -0,0 +1,58 @@
"use strict";
// Rewrite Antigravity IDE markers so upstream AG 2.x backend accepts the request.
// User-Agent header (antigravity/<old>) and body.metadata.ideVersion are forced
// to a known-good IDE version. Hardcoded MVP — toggle/version configurable later.
const ANTIGRAVITY_IDE_VERSION = "1.23.2";
const ANTIGRAVITY_IDE_VERSION_OVERRIDE_ENABLED = true;
function shouldRewriteMetadata(metadata) {
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) return false;
if (String(metadata.ideName || "").toLowerCase() === "antigravity") return true;
if (String(metadata.ideType || "").toUpperCase() === "ANTIGRAVITY") return true;
return Object.prototype.hasOwnProperty.call(metadata, "ideVersion");
}
function rewriteAntigravityUserAgent(userAgent, version) {
if (typeof userAgent !== "string" || !userAgent.includes("antigravity/")) return userAgent;
return userAgent.replace(/antigravity\/[^\s]+/, `antigravity/${version}`);
}
function applyAntigravityIdeVersionOverride(bodyBuffer, headers, log = () => {}) {
if (!ANTIGRAVITY_IDE_VERSION_OVERRIDE_ENABLED) {
return { bodyBuffer, headers, applied: false, version: ANTIGRAVITY_IDE_VERSION };
}
const nextHeaders = { ...headers };
const nextUserAgent = rewriteAntigravityUserAgent(nextHeaders["user-agent"], ANTIGRAVITY_IDE_VERSION);
const userAgentChanged = nextUserAgent !== nextHeaders["user-agent"];
if (userAgentChanged) nextHeaders["user-agent"] = nextUserAgent;
try {
const parsed = JSON.parse(bodyBuffer.toString());
if (!shouldRewriteMetadata(parsed?.metadata)) {
if (userAgentChanged) log(`🛰️ [antigravity] user-agent version override → ${ANTIGRAVITY_IDE_VERSION}`);
return { bodyBuffer, headers: nextHeaders, applied: userAgentChanged, version: ANTIGRAVITY_IDE_VERSION };
}
const previousVersion = parsed.metadata.ideVersion;
parsed.metadata.ideVersion = ANTIGRAVITY_IDE_VERSION;
const nextBodyBuffer = Buffer.from(JSON.stringify(parsed));
log(`🛰️ [antigravity] IDE version override: ${previousVersion || "unknown"}${ANTIGRAVITY_IDE_VERSION}`);
return { bodyBuffer: nextBodyBuffer, headers: nextHeaders, applied: true, version: ANTIGRAVITY_IDE_VERSION };
} catch (e) {
if (userAgentChanged) {
log(`🛰️ [antigravity] user-agent version override → ${ANTIGRAVITY_IDE_VERSION}`);
return { bodyBuffer, headers: nextHeaders, applied: true, version: ANTIGRAVITY_IDE_VERSION };
}
log(`🛰️ [antigravity] IDE version override skipped: ${e.message}`);
return { bodyBuffer, headers: nextHeaders, applied: false, version: ANTIGRAVITY_IDE_VERSION };
}
}
module.exports = {
ANTIGRAVITY_IDE_VERSION,
applyAntigravityIdeVersionOverride,
rewriteAntigravityUserAgent,
};
+1 -1
View File
@@ -31,7 +31,7 @@ const URL_PATTERNS = {
// Synonym map: rawModel from request → canonical alias key in mitmAlias DB
const MODEL_SYNONYMS = {
antigravity: {
"gemini-default": "gemini-3-flash-agent",
"gemini-default": "gemini-3.5-flash-low",
"gemini-3.1-pro-high": "gemini-pro-agent",
"gemini-3-pro-high": "gemini-pro-agent",
"gemini-3-pro-low": "gemini-3.1-pro-low",
+126 -4
View File
@@ -1,4 +1,6 @@
const https = require("https");
const http2 = require("http2");
const tls = require("tls");
const fs = require("fs");
const path = require("path");
const dns = require("dns");
@@ -9,6 +11,7 @@ const { IS_DEV, LSOF_BIN, TARGET_HOSTS, URL_PATTERNS, MODEL_SYNONYMS, MODEL_PATT
const { DATA_DIR, MITM_DIR } = require("./paths");
const { getCertForDomain } = require("./cert/generate");
const { getMitmAlias } = require("./dbReader");
const { applyAntigravityIdeVersionOverride } = require("./antigravityIdeVersion");
const LOCAL_PORT = 443;
const IS_WIN = process.platform === "win32";
const ENABLE_FILE_LOG = IS_DEV;
@@ -129,16 +132,136 @@ function getMappedModel(tool, model) {
*/
async function passthrough(req, res, bodyBuffer, onResponse) {
const originalHost = (req.headers.host || TARGET_HOSTS[0]).split(":")[0];
const targetHost = HOST_REWRITE[originalHost] || originalHost;
const targetIP = await resolveTargetIP(targetHost);
// Only rewrite host for chat endpoints — daily-cloudcode-pa rejects auth/login requests
const isChatEndpoint = req.url.includes(":generateContent") || req.url.includes(":streamGenerateContent");
const targetHost = isChatEndpoint ? (HOST_REWRITE[originalHost] || originalHost) : originalHost;
const dumper = ENABLE_FILE_LOG ? createResponseDumper(req, "passthrough") : null;
const tool = getToolForHost(req.headers.host);
const versionOverride = tool === "antigravity"
? applyAntigravityIdeVersionOverride(bodyBuffer, req.headers, log)
: { bodyBuffer, headers: req.headers };
const bodyForForwarding = versionOverride.bodyBuffer;
const headersForForwarding = { ...versionOverride.headers, host: targetHost };
if (bodyForForwarding !== bodyBuffer) {
headersForForwarding["content-length"] = String(bodyForForwarding.length);
}
// ALPN negotiate: try HTTP/2 first (like browsers/mitmweb), fallback HTTP/1.1
try {
const proto = await negotiateAlpn(targetHost);
if (proto === "h2") {
return await passthroughHttp2(req, res, bodyForForwarding, headersForForwarding, targetHost, onResponse, dumper);
}
} catch (e) {
err(`[mitm] ALPN negotiate failed: ${e.message}, fallback to HTTP/1.1`);
}
return passthroughHttps(req, res, bodyForForwarding, headersForForwarding, targetHost, onResponse, dumper);
}
// ── ALPN negotiation cache ────────────────────────────────────
const alpnCache = new Map(); // host → "h2" | "http/1.1"
async function negotiateAlpn(host) {
if (alpnCache.has(host)) return alpnCache.get(host);
const ip = await resolveTargetIP(host);
return new Promise((resolve, reject) => {
const socket = tls.connect({
host: ip, port: 443, servername: host,
ALPNProtocols: ["h2", "http/1.1"], rejectUnauthorized: false,
}, () => {
const proto = socket.alpnProtocol || "http/1.1";
alpnCache.set(host, proto);
log(`🔗 [mitm] ALPN ${host}${proto}`);
socket.end();
resolve(proto);
});
socket.once("error", reject);
socket.setTimeout(5000, () => { socket.destroy(new Error("ALPN timeout")); });
});
}
// HTTP/2 passthrough using node:http2 native
async function passthroughHttp2(req, res, bodyBuffer, headers, targetHost, onResponse, dumper) {
const targetIP = await resolveTargetIP(targetHost);
// HTTP/2 pseudo-headers required; strip HTTP/1.1-only headers
const h2Headers = {};
for (const [k, v] of Object.entries(headers)) {
const lk = k.toLowerCase();
if (lk === "host" || lk === "connection" || lk === "keep-alive" ||
lk === "transfer-encoding" || lk === "upgrade" || lk === "proxy-connection") continue;
h2Headers[lk] = v;
}
h2Headers[":method"] = req.method;
h2Headers[":path"] = req.url;
h2Headers[":scheme"] = "https";
h2Headers[":authority"] = targetHost;
return new Promise((resolve) => {
const client = http2.connect(`https://${targetHost}`, {
createConnection: () => tls.connect({
host: targetIP, port: 443, servername: targetHost,
ALPNProtocols: ["h2"], rejectUnauthorized: false,
}),
});
client.once("error", (e) => {
err(`[mitm] http2 client error: ${e.message}`);
if (dumper) { dumper.writeChunk(`\n[ERROR h2] ${e.message}\n`); dumper.end(); }
if (!res.headersSent) res.writeHead(502);
if (!res.writableEnded) res.end("Bad Gateway");
try { client.close(); } catch {}
resolve();
});
const stream = client.request(h2Headers, { endStream: bodyBuffer.length === 0 });
if (bodyBuffer.length > 0) stream.end(bodyBuffer);
stream.once("response", (responseHeaders) => {
const status = responseHeaders[":status"];
// Filter pseudo-headers + connection-specific
const outHeaders = {};
for (const [k, v] of Object.entries(responseHeaders)) {
if (k.startsWith(":")) continue;
if (k === "connection" || k === "keep-alive" || k === "transfer-encoding") continue;
outHeaders[k] = v;
}
res.writeHead(status, outHeaders);
if (dumper) dumper.writeHeader(status, outHeaders);
const chunks = [];
stream.on("data", chunk => {
if (dumper) dumper.writeChunk(chunk);
if (onResponse) chunks.push(chunk);
res.write(chunk);
});
stream.on("end", () => {
if (dumper) dumper.end();
if (!res.writableEnded) res.end();
if (onResponse) try { onResponse(Buffer.concat(chunks), outHeaders); } catch {}
try { client.close(); } catch {}
resolve();
});
});
stream.once("error", (e) => {
err(`[mitm] http2 stream error: ${e.message}`);
if (dumper) { dumper.writeChunk(`\n[ERROR h2-stream] ${e.message}\n`); dumper.end(); }
if (!res.headersSent) res.writeHead(502);
if (!res.writableEnded) res.end();
try { client.close(); } catch {}
resolve();
});
});
}
// Fallback: raw https.request HTTP/1.1 with custom DNS (bypasses /etc/hosts MITM loop)
async function passthroughHttps(req, res, bodyBuffer, headers, targetHost, onResponse, dumper) {
const targetIP = await resolveTargetIP(targetHost);
const forwardReq = https.request({
hostname: targetIP,
port: 443,
path: req.url,
method: req.method,
headers: { ...req.headers, host: targetHost },
headers,
servername: targetHost,
rejectUnauthorized: false
}, (forwardRes) => {
@@ -150,7 +273,6 @@ async function passthrough(req, res, bodyBuffer, onResponse) {
return;
}
// Tee: forward to client AND optionally buffer + dump
const chunks = [];
forwardRes.on("data", chunk => {
if (dumper) dumper.writeChunk(chunk);
+5 -4
View File
@@ -8,12 +8,13 @@ export const MITM_TOOLS = {
description: "Google Antigravity IDE with MITM",
configType: "mitm",
mitmDomain: "daily-cloudcode-pa.googleapis.com",
modelAliases: ["gemini-3-flash-agent", "gemini-3.5-flash-low", "gemini-pro-agent", "gemini-3.1-pro-low", "claude-sonnet-4-6", "claude-opus-4-6-thinking", "gpt-oss-120b-medium", "gemini-3-flash"],
modelAliases: ["gemini-3.5-flash-low", "gemini-3-flash-agent", "gemini-3.5-flash-extra-low", "gemini-3.1-pro-low", "gemini-pro-agent", "claude-sonnet-4-6", "claude-opus-4-6-thinking", "gpt-oss-120b-medium", "gemini-3-flash"],
defaultModels: [
{ id: "gemini-3-flash-agent", name: "Gemini 3.5 Flash (High) / Default", alias: "gemini-3-flash-agent" },
{ id: "gemini-3.5-flash-low", name: "Gemini 3.5 Flash (Medium)", alias: "gemini-3.5-flash-low" },
{ id: "gemini-pro-agent", name: "Gemini 3.1 Pro (High)", alias: "gemini-pro-agent" },
{ id: "gemini-3.5-flash-low", name: "Gemini 3.5 Flash (Medium) / Default", alias: "gemini-3.5-flash-low" },
{ id: "gemini-3-flash-agent", name: "Gemini 3.5 Flash (High)", alias: "gemini-3-flash-agent" },
{ id: "gemini-3.5-flash-extra-low", name: "Gemini 3.5 Flash (Low)", alias: "gemini-3.5-flash-extra-low" },
{ id: "gemini-3.1-pro-low", name: "Gemini 3.1 Pro (Low)", alias: "gemini-3.1-pro-low" },
{ id: "gemini-pro-agent", name: "Gemini 3.1 Pro (High)", alias: "gemini-pro-agent" },
{ id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6 (Thinking)", alias: "claude-sonnet-4-6" },
{ id: "claude-opus-4-6-thinking", name: "Claude Opus 4.6 (Thinking)", alias: "claude-opus-4-6-thinking" },
{ id: "gpt-oss-120b-medium", name: "GPT-OSS 120B (Medium)", alias: "gpt-oss-120b-medium" },