From 9be87dd8a976953b2e4de758fd22f2a1d05b0340 Mon Sep 17 00:00:00 2001 From: Sam Rolfe Date: Wed, 10 Jun 2026 14:55:33 +1000 Subject: [PATCH] =?UTF-8?q?feat:=20integrate=20Headroom=20compression=20-?= =?UTF-8?q?=20compress=20large=20analysis/code/devops=20contexts=20(?= =?UTF-8?q?=E2=89=A55K=20tokens)=20via=20context=20event?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extensions/smart-router/index.ts | 48 +++++++++++++++++++++++ extensions/smart-router/package-lock.json | 45 +++++++++++++++++++++ extensions/smart-router/package.json | 16 ++++++++ 3 files changed, 109 insertions(+) create mode 100644 extensions/smart-router/package-lock.json create mode 100644 extensions/smart-router/package.json diff --git a/extensions/smart-router/index.ts b/extensions/smart-router/index.ts index 1df2574..0cd5cd4 100644 --- a/extensions/smart-router/index.ts +++ b/extensions/smart-router/index.ts @@ -5,11 +5,30 @@ import type { BeforeAgentStartEventResult, ExtensionCommandContext, } from "@earendil-works/pi-coding-agent"; +import { compress } from "headroom-ai"; // Global state for manual model lock let isLocked = false; let lockedModel: any = null; +// Tags that trigger Headroom context compression. +// read / discuss / search skip compression entirely. +const COMPRESS_TAGS = new Set([ + "devops-low", + "devops-high", + "code-analysis-low", + "code-analysis-high", + "codewrite-low", + "codewrite-high", +]); + +// Minimum message size (in chars) before compression activates. +// ~5K tokens ≈ 20K characters (rough 4:1 ratio). +const COMPRESS_MIN_CHARS = 20_000; + +// Current routing tag for the active turn (used by context handler) +let currentTag: string | null = null; + // Model ID mappings for routing const MODELS: Record = { "free-core": { provider: "openrouter", id: "free" }, @@ -193,6 +212,9 @@ export default function (pi: ExtensionAPI) { pi.setThinkingLevel(thinkingLevel as any); } + // Store tag for compression check in context event + currentTag = tag; + // Show routing decision in status bar ctx.ui.setStatus("router", `🎯 ${tag} → ${modelLabel(modelKey)}`); @@ -203,4 +225,30 @@ export default function (pi: ExtensionAPI) { ctx.ui.setStatus("router", `⚠️ fallback ${modelLabel("free-core")}`); } }); + + // 4. Compress large contexts before LLM turns (Headroom) + pi.on("context", async (event, ctx) => { + // Only compress for analysis/coding/devops tags + if (!currentTag || !COMPRESS_TAGS.has(currentTag)) return; + + // Quick size check before calling the proxy + const totalChars = JSON.stringify(event.messages).length; + if (totalChars < COMPRESS_MIN_CHARS) return; + + try { + const result = await compress(event.messages, { + baseUrl: "http://localhost:8787", + fallback: true, + timeout: 15_000, + }); + if (result.messages && result.messages.length > 0) { + const saved = ((result.tokensBefore - result.tokensAfter) / result.tokensBefore * 100).toFixed(0); + ctx.ui.setStatus("compression", `📦 ${saved}%`); + return { messages: result.messages }; + } + } catch { + // Proxy down — pass through (fallback: true already handles transport errors) + ctx.ui.setStatus("compression", "⚠️ offline"); + } + }); } diff --git a/extensions/smart-router/package-lock.json b/extensions/smart-router/package-lock.json new file mode 100644 index 0000000..cfb45a5 --- /dev/null +++ b/extensions/smart-router/package-lock.json @@ -0,0 +1,45 @@ +{ + "name": "smart-router", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "smart-router", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "headroom-ai": "^0.22.4" + } + }, + "node_modules/headroom-ai": { + "version": "0.22.4", + "resolved": "https://registry.npmjs.org/headroom-ai/-/headroom-ai-0.22.4.tgz", + "integrity": "sha512-9a0rgB/jsWe8gs/ggyUwe6E8DYwKAuBvlUml2ApwlUjb5EfJ611X6X+WG0SiXw3nO6sdyV1/+Ah5uw9P7ecnjw==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@ai-sdk/provider": ">=1.0.0", + "@anthropic-ai/sdk": ">=0.30.0", + "ai": ">=6.0.0", + "openai": ">=4.0.0" + }, + "peerDependenciesMeta": { + "@ai-sdk/provider": { + "optional": true + }, + "@anthropic-ai/sdk": { + "optional": true + }, + "ai": { + "optional": true + }, + "openai": { + "optional": true + } + } + } + } +} diff --git a/extensions/smart-router/package.json b/extensions/smart-router/package.json new file mode 100644 index 0000000..40d4c4d --- /dev/null +++ b/extensions/smart-router/package.json @@ -0,0 +1,16 @@ +{ + "name": "smart-router", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "dependencies": { + "headroom-ai": "^0.22.4" + } +}