- Enhance passthrough function to support response inspection

- Add cursor tool configuration and update related components
This commit is contained in:
decolua
2026-03-19 15:32:29 +07:00
parent 9877f32efa
commit fd4ec9e5b8
12 changed files with 153 additions and 116 deletions
+3
View File
@@ -5,12 +5,14 @@ const TARGET_HOSTS = [
"cloudcode-pa.googleapis.com",
"api.individual.githubcopilot.com",
"q.us-east-1.amazonaws.com",
"api2.cursor.sh",
];
const URL_PATTERNS = {
antigravity: [":generateContent", ":streamGenerateContent"],
copilot: ["/chat/completions", "/v1/messages", "/responses"],
kiro: ["/generateAssistantResponse"],
cursor: ["/BidiAppend", "/RunSSE", "/RunPoll", "/Run"],
};
function getToolForHost(host) {
@@ -18,6 +20,7 @@ function getToolForHost(host) {
if (h === "api.individual.githubcopilot.com") return "copilot";
if (h === "daily-cloudcode-pa.googleapis.com" || h === "cloudcode-pa.googleapis.com") return "antigravity";
if (h === "q.us-east-1.amazonaws.com") return "kiro";
if (h === "api2.cursor.sh") return "cursor";
return null;
}
+1
View File
@@ -9,6 +9,7 @@ const TOOL_HOSTS = {
antigravity: ["daily-cloudcode-pa.googleapis.com", "cloudcode-pa.googleapis.com"],
copilot: ["api.individual.githubcopilot.com"],
kiro: ["q.us-east-1.amazonaws.com", "codewhisperer.us-east-1.amazonaws.com"],
cursor: ["api2.cursor.sh"],
};
const IS_WIN = process.platform === "win32";
+5
View File
@@ -464,6 +464,11 @@ async function startServer(apiKey, sudoPassword) {
err(msg);
startError = msg;
}
// Detect wrong/missing password — clear cache and stop retry loop
if (!IS_WIN && (msg.includes("incorrect password") || msg.includes("no password was provided"))) {
setCachedPassword(null);
mitmIsRestarting = true; // prevent scheduleMitmRestart from firing
}
});
serverProcess.on("exit", (code) => {
log(`Server exited (code: ${code})`);
+27 -2
View File
@@ -26,6 +26,7 @@ const handlers = {
antigravity: loadHandler("antigravity"),
copilot: loadHandler("copilot"),
kiro: loadHandler("kiro"),
cursor: loadHandler("cursor"),
};
// ── SSL / SNI ─────────────────────────────────────────────────
@@ -118,7 +119,12 @@ function saveRequestLog(url, bodyBuffer) {
} catch { /* ignore */ }
}
async function passthrough(req, res, bodyBuffer) {
/**
* Forward request to real upstream.
* Optional onResponse(rawBuffer) callback — if provided, tees the response
* so it's both forwarded to client AND passed to the callback for inspection.
*/
async function passthrough(req, res, bodyBuffer, onResponse) {
const targetHost = (req.headers.host || TARGET_HOSTS[0]).split(":")[0];
const targetIP = await resolveTargetIP(targetHost);
@@ -132,7 +138,19 @@ async function passthrough(req, res, bodyBuffer) {
rejectUnauthorized: false
}, (forwardRes) => {
res.writeHead(forwardRes.statusCode, forwardRes.headers);
forwardRes.pipe(res);
if (!onResponse) {
forwardRes.pipe(res);
return;
}
// Tee: forward to client AND buffer for callback
const chunks = [];
forwardRes.on("data", chunk => { chunks.push(chunk); res.write(chunk); });
forwardRes.on("end", () => {
res.end();
try { onResponse(Buffer.concat(chunks), forwardRes.headers); } catch { /* ignore */ }
});
});
forwardReq.on("error", (e) => {
@@ -172,6 +190,13 @@ const server = https.createServer(sslOptions, async (req, res) => {
log(`🔍 [${tool}] url=${req.url} | bodyLen=${bodyBuffer.length}`);
// Cursor uses binary proto — model extraction not possible at this layer.
// Delegate directly to handler which decodes proto internally.
if (tool === "cursor") {
log(`⚡ intercept | cursor | proto`);
return handlers[tool].intercept(req, res, bodyBuffer, null, passthrough);
}
const model = extractModel(req.url, bodyBuffer);
log(`🔍 [${tool}] model="${model}"`);