feat(pxpipe): PXPIPE token saver — multimodal prompt compression (#2465)
Add pxpipe as an experimental fifth Token Saver: Claude-format request bodies above a configurable size threshold are rendered as dense PNGs via the pxpipe-proxy library API (transformAnthropicMessages) before dispatch, cutting estimated input tokens by ~35-60% on token-dense contexts. Integration follows the Headroom pattern: applied to the final body in chatCore just before dispatch, fail-open on any error/timeout. Managed npm install into DATA_DIR/pxpipe, dynamic loader with per-version cache-bust, JSONL event log with rotation, /api/pxpipe/* endpoints, Token Saver card (marked experimental) + /dashboard/pxpipe page, and per-request Activated/Skipped annotation in Request Details. Disabled by default.
This commit is contained in:
committed by
decolua
parent
e1f3399b73
commit
dcf1927f22
@@ -0,0 +1,16 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { runHealthCheck } from "@/lib/pxpipe/service.js";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function POST() {
|
||||
try {
|
||||
const result = await runHealthCheck();
|
||||
return NextResponse.json(result);
|
||||
} catch (error) {
|
||||
return NextResponse.json({ healthy: false, checks: [], error: error.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// GET mirrors POST so the card can probe on page load without a mutation call.
|
||||
export const GET = POST;
|
||||
@@ -0,0 +1,20 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { installPxpipe } from "@/lib/pxpipe/install.js";
|
||||
import { unloadPxpipe } from "@/lib/pxpipe/loader.js";
|
||||
import { runHealthCheck } from "@/lib/pxpipe/service.js";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
// npm install can legitimately take minutes on a cold cache.
|
||||
export const maxDuration = 300;
|
||||
|
||||
// Install (or repair — same operation, reinstalls @latest) then re-run the health check.
|
||||
export async function POST() {
|
||||
try {
|
||||
const info = await installPxpipe();
|
||||
unloadPxpipe(); // drop any previously-loaded version so health loads the fresh one
|
||||
const health = await runHealthCheck();
|
||||
return NextResponse.json({ ...info, health });
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: error.message, code: error.code || null }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { getInstallLogTail } from "@/lib/pxpipe/install.js";
|
||||
import { readPxpipeEvents } from "@/lib/pxpipe/events.js";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function GET(request) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const limit = Math.min(Number(searchParams.get("limit")) || 100, 500);
|
||||
return NextResponse.json({
|
||||
installLog: getInstallLogTail(),
|
||||
events: readPxpipeEvents({ limit }).reverse(),
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: error.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { unloadPxpipe, loadPxpipe } from "@/lib/pxpipe/loader.js";
|
||||
import { getPxpipeStatus } from "@/lib/pxpipe/service.js";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
// Reload the in-process module (picks up an upgraded install without a server restart).
|
||||
export async function POST() {
|
||||
try {
|
||||
unloadPxpipe();
|
||||
await loadPxpipe();
|
||||
return NextResponse.json(getPxpipeStatus());
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: error.message, code: error.code || null }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { getSettings } from "@/lib/localDb";
|
||||
import { getInstallInfo, installPxpipe } from "@/lib/pxpipe/install.js";
|
||||
import { loadPxpipe } from "@/lib/pxpipe/loader.js";
|
||||
import { getPxpipeStatus } from "@/lib/pxpipe/service.js";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
export const maxDuration = 300;
|
||||
|
||||
// "Start" in library mode = warm the in-process transform module.
|
||||
// Auto-installs first when the package is missing and pxpipeAutoInstall is on.
|
||||
export async function POST() {
|
||||
try {
|
||||
if (!getInstallInfo().installed) {
|
||||
const settings = await getSettings();
|
||||
if (!settings.pxpipeAutoInstall) {
|
||||
return NextResponse.json({ error: "PXPIPE is not installed", code: "NOT_INSTALLED" }, { status: 409 });
|
||||
}
|
||||
await installPxpipe();
|
||||
}
|
||||
await loadPxpipe();
|
||||
return NextResponse.json(getPxpipeStatus());
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: error.message, code: error.code || null }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { getPxpipeStats } from "@/lib/pxpipe/events.js";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function GET(request) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const recentLimit = Math.min(Number(searchParams.get("limit")) || 100, 500);
|
||||
return NextResponse.json(getPxpipeStats({ recentLimit }));
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: error.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { getSettings } from "@/lib/localDb";
|
||||
import { getPxpipeStatus } from "@/lib/pxpipe/service.js";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const settings = await getSettings();
|
||||
const status = getPxpipeStatus();
|
||||
return NextResponse.json({
|
||||
...status,
|
||||
enabled: !!settings.pxpipeEnabled,
|
||||
autoInstall: !!settings.pxpipeAutoInstall,
|
||||
minChars: settings.pxpipeMinChars,
|
||||
timeoutMs: settings.pxpipeTimeoutMs,
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: error.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { unloadPxpipe } from "@/lib/pxpipe/loader.js";
|
||||
import { getPxpipeStatus } from "@/lib/pxpipe/service.js";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
// "Stop" in library mode = drop the in-process module; requests fail open to
|
||||
// uncompressed passthrough until it is started again.
|
||||
export async function POST() {
|
||||
try {
|
||||
const wasLoaded = unloadPxpipe();
|
||||
return NextResponse.json({ stopped: wasLoaded, ...getPxpipeStatus() });
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: error.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user