Enhance chat handling and introduce Caveman feature

- Refactored handleChatCore to include Caveman functionality, allowing for terse-style system prompts to reduce output token usage.
- Updated APIPageClient to manage Caveman settings, including enabling/disabling and selecting compression levels.
- Adjusted AntigravityExecutor to consolidate function declarations for compatibility with Gemini.
- Removed unnecessary console logs during translator initialization across multiple routes.
This commit is contained in:
decolua
2026-04-30 18:00:38 +07:00
parent b3fb20e237
commit 936d65ae1c
14 changed files with 265 additions and 48 deletions

View File

@@ -14,6 +14,12 @@ const TUNNEL_BENEFITS = [
const TUNNEL_PING_INTERVAL_MS = 2000;
const TUNNEL_PING_MAX_MS = 300000;
const CAVEMAN_LEVELS = [
{ id: "lite", label: "Lite", desc: "Drop filler, keep grammar" },
{ id: "full", label: "Full", desc: "Drop articles, fragments OK" },
{ id: "ultra", label: "Ultra", desc: "Telegraphic, max compression" },
];
export default function APIPageClient({ machineId }) {
const [keys, setKeys] = useState([]);
const [loading, setLoading] = useState(true);
@@ -26,6 +32,8 @@ export default function APIPageClient({ machineId }) {
const [hasPassword, setHasPassword] = useState(true);
const [tunnelDashboardAccess, setTunnelDashboardAccess] = useState(false);
const [rtkEnabled, setRtkEnabledState] = useState(true);
const [cavemanEnabled, setCavemanEnabled] = useState(false);
const [cavemanLevel, setCavemanLevel] = useState("full");
// Cloudflare Tunnel state
const [tunnelChecking, setTunnelChecking] = useState(true);
@@ -82,6 +90,8 @@ export default function APIPageClient({ machineId }) {
setHasPassword(data.hasPassword || false);
setTunnelDashboardAccess(data.tunnelDashboardAccess || false);
setRtkEnabledState(data.rtkEnabled !== false);
setCavemanEnabled(!!data.cavemanEnabled);
setCavemanLevel(data.cavemanLevel || "full");
}
if (statusRes.ok) {
const data = await statusRes.json();
@@ -182,6 +192,28 @@ export default function APIPageClient({ machineId }) {
}
};
const patchSetting = async (patch) => {
try {
await fetch("/api/settings", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(patch),
});
} catch (error) {
console.log("Error updating setting:", error);
}
};
const handleCavemanEnabled = (value) => {
setCavemanEnabled(value);
patchSetting({ cavemanEnabled: value });
};
const handleCavemanLevel = (level) => {
setCavemanLevel(level);
patchSetting({ cavemanLevel: level });
};
const fetchData = async () => {
try {
const keysRes = await fetch("/api/keys");
@@ -813,16 +845,26 @@ export default function APIPageClient({ machineId }) {
)}
</Card>
{/* Token Saver (RTK) */}
{/* Token Saver (RTK + Caveman) */}
<Card id="rtk">
<div className="flex items-center justify-between mb-2">
<h2 className="text-lg font-semibold">Token Saver</h2>
</div>
<div className="flex items-center justify-between pt-2">
<div className="flex items-center justify-between pt-2 pb-4 border-b border-border">
<div className="pr-4">
<p className="font-medium">Compress tool output</p>
<p className="font-medium">
Compress tool output{" "}
<a
href="https://github.com/rtk-ai/rtk"
target="_blank"
rel="noreferrer"
className="text-xs font-normal text-primary underline hover:opacity-80"
>
(RTK)
</a>
</p>
<p className="text-sm text-text-muted">
Auto-compress tool output (git diff/grep/ls/tree/logs) before sending to LLM to save tokens. Disable if you see issues.
Auto-compress tool output (git diff/grep/ls/tree/logs) before sending to LLM (60-90% fewer input tokens on common dev commands). Disable if you see issues.
</p>
</div>
<Toggle
@@ -830,6 +872,46 @@ export default function APIPageClient({ machineId }) {
onChange={() => handleRtkEnabled(!rtkEnabled)}
/>
</div>
<div className="flex items-center justify-between pt-4">
<div className="pr-4">
<p className="font-medium">
Compress LLM output{" "}
<a
href="https://github.com/JuliusBrussee/caveman"
target="_blank"
rel="noreferrer"
className="text-xs font-normal text-primary underline hover:opacity-80"
>
(Caveman)
</a>
</p>
<p className="text-sm text-text-muted">
Inject a terse-style instruction into the system prompt so the LLM replies shorter (~65% fewer output tokens on average, up to 87%). Code, errors and warnings stay exact.
</p>
</div>
<Toggle
checked={cavemanEnabled}
onChange={() => handleCavemanEnabled(!cavemanEnabled)}
/>
</div>
{cavemanEnabled && (
<div className="mt-3 flex items-center gap-2">
{CAVEMAN_LEVELS.map((lvl) => (
<button
key={lvl.id}
onClick={() => handleCavemanLevel(lvl.id)}
className={`px-3 py-1.5 rounded text-xs font-medium border transition-colors ${
cavemanLevel === lvl.id
? "bg-primary text-white border-primary"
: "bg-transparent border-border text-text-muted hover:bg-black/5 dark:hover:bg-white/5"
}`}
title={lvl.desc}
>
{lvl.label}
</button>
))}
</div>
)}
</Card>
{/* API Keys */}

View File

@@ -8,7 +8,6 @@ async function ensureInitialized() {
if (!initialized) {
await initTranslators();
initialized = true;
console.log("[SSE] Translators initialized");
}
}

View File

@@ -11,7 +11,6 @@ async function ensureInitialized() {
if (!initialized) {
await initTranslators();
initialized = true;
console.log("[SSE] Translators initialized");
}
}

View File

@@ -10,7 +10,6 @@ async function ensureInitialized() {
if (!initialized) {
await initTranslators();
initialized = true;
console.log("[SSE] Translators initialized for /v1/messages");
}
}

View File

@@ -7,7 +7,6 @@ async function ensureInitialized() {
if (!initialized) {
await initTranslators();
initialized = true;
console.log("[SSE] Translators initialized for /v1/responses");
}
}

View File

@@ -10,7 +10,6 @@ async function ensureInitialized() {
if (!initialized) {
await initTranslators();
initialized = true;
console.log("[SSE] Translators initialized for /v1beta/models");
}
}

View File

@@ -37,6 +37,8 @@ const DEFAULT_SETTINGS = {
outboundNoProxy: "",
mitmRouterBaseUrl: DEFAULT_MITM_ROUTER_BASE,
rtkEnabled: true,
cavemanEnabled: false,
cavemanLevel: "full",
};
function cloneDefaultData() {

View File

@@ -12,9 +12,15 @@ const { getCertForDomain } = require("./cert/generate");
const DB_FILE = path.join(DATA_DIR, "db.json");
const LOCAL_PORT = 443;
const IS_WIN = process.platform === "win32";
const ENABLE_FILE_LOG = false;
const ENABLE_FILE_LOG = true;
const INTERNAL_REQUEST_HEADER = { name: "x-request-source", value: "local" };
// Host rewrite for upstream forward: PROD cloudcode-pa is rate-limited (429),
// daily-cloudcode-pa (dev endpoint) accepts same body+token. Same trick as open-sse.
const HOST_REWRITE = {
"cloudcode-pa.googleapis.com": "daily-cloudcode-pa.googleapis.com",
};
// Load handlers — dev/ overrides handlers/ for private implementations
function loadHandler(name) {
try { return require(`./dev/${name}`); } catch {}
@@ -43,7 +49,6 @@ function sniCallback(servername, cb) {
cert: `${certData.cert}\n${rootCAPem}`
});
certCache.set(servername, ctx);
log(`🔐 Cert generated: ${servername}`);
cb(null, ctx);
} catch (e) {
err(`SNI error for ${servername}: ${e.message}`);
@@ -123,7 +128,8 @@ function getMappedModel(tool, model) {
* Also tees full stream into a dump file when ENABLE_FILE_LOG is on.
*/
async function passthrough(req, res, bodyBuffer, onResponse) {
const targetHost = (req.headers.host || TARGET_HOSTS[0]).split(":")[0];
const originalHost = (req.headers.host || TARGET_HOSTS[0]).split(":")[0];
const targetHost = HOST_REWRITE[originalHost] || originalHost;
const targetIP = await resolveTargetIP(targetHost);
const dumper = ENABLE_FILE_LOG ? createResponseDumper(req, "passthrough") : null;
@@ -194,25 +200,18 @@ const server = https.createServer(sslOptions, async (req, res) => {
const isChat = patterns.some(p => req.url.includes(p));
if (!isChat) return passthrough(req, res, bodyBuffer);
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}"`);
const mappedModel = getMappedModel(tool, model);
if (!mappedModel) {
log(`⏩ passthrough | no mapping | ${tool} | ${model || "unknown"}`);
return passthrough(req, res, bodyBuffer);
}
log(`⚡ intercept | ${tool} | ${model} → ${mappedModel}`);
return handlers[tool].intercept(req, res, bodyBuffer, mappedModel, passthrough);
} catch (e) {
err(`Unhandled error: ${e.message}`);

View File

@@ -207,6 +207,8 @@ async function handleSingleModelChat(body, modelStr, clientRawRequest = null, re
apiKey,
ccFilterNaming: !!chatSettings.ccFilterNaming,
rtkEnabled: !!chatSettings.rtkEnabled,
cavemanEnabled: !!chatSettings.cavemanEnabled,
cavemanLevel: chatSettings.cavemanLevel || "full",
providerThinking,
// Detect source format by endpoint + body
sourceFormatOverride: request?.url ? detectFormatByEndpoint(new URL(request.url).pathname, body) : null,