A refined, battle-tested guide to successfully integrating headroom-ai
with opencode
for context compression and proxy routing, incorporating fixes for absolute path resolution and dynamic chunk binding.
Ensure you have headroom
installed locally and note its absolute path:
which headroom
Navigate to your OpenCode configuration directory and install headroom-ai
and the required plugin package:
cd ~/.config/opencode
npm install headroom-ai
npm install @opencode-ai/plugin@latest
Since pre-compiled chunks must match the build hash, clone the repository and build the plugin from source:
rm -rf /tmp/headroom-build && mkdir -p /tmp/headroom-build
cd /tmp/headroom-build
git clone --filter=blob:none --sparse --depth=1 https://github.com/headroomlabs-ai/headroom.git .
git sparse-checkout set plugins/opencode
cd plugins/opencode
npm install
npm run build
Create the target directory structure and move the built distribution files:
PLUGIN=~/.config/opencode/plugins/headroom-opencode
SRC=/tmp/headroom-build/plugins/opencode
rm -rf "$PLUGIN" && mkdir -p "$PLUGIN/hook-shim"
cp "$SRC/package.json" "$PLUGIN/"
cp -R "$SRC/dist" "$PLUGIN/dist"
cp "$SRC/hook-shim/handler.js" "$PLUGIN/hook-shim/"
Create or verify a package.json
in both the plugin root and the hook-shim folder to enforce ESM ("type": "module"
):
cat > "$PLUGIN/package.json" <<'EOF'
{ "private": true, "type": "module" }
EOF
cat > "$PLUGIN/hook-shim/package.json" <<'EOF'
{ "private": true, "type": "module" }
EOF
Crucial: Hardcoding a generic chunk name will break child-process interception. Use this snippet to automatically detect your build's specific hashed chunk name and write it into handler.js
:
CHUNK=$(ls "$PLUGIN/dist/chunk-"*.js | head -1 | xargs basename)
cat > "$PLUGIN/hook-shim/handler.js" <<EOF
import { installHeadroomTransport } from "../dist/$CHUNK";
const proxyUrl = process.env.HEADROOM_OPENCODE_TRANSPORT_PROXY_URL;
if (proxyUrl) {
installHeadroomTransport({ proxyUrl });
}
EOF
Warning: OpenCode uses Bun's module resolver, which never expands the ~ symbol. Using
~/.config/...
will cause the plugin to be silently skipped. Always use full absolute paths:
{
"$schema": "https://opencode.ai/config.json",
"plugin": [
"/Users/YOUR_USERNAME/.config/opencode/plugins/headroom-opencode/dist/entry.opencode.js"
],
"mcp": {
"headroom": {
"type": "local",
"command": [
"/Users/YOUR_USERNAME/.local/bin/headroom",
"mcp",
"serve"
],
"enabled": true
}
}
}
(Replace /Users/YOUR_USERNAME/ with your actual home directory path).
- Start the Headroom proxy daemon in a separate terminal tab or background process:
headroom proxy &
- Launch OpenCode:
opencode
- Verify the proxy connection using the test script: A
node --input-type=module -e "try { const res = await fetch('http://localhost:8787/v1/retrieve/000000000000000000000000', { signal: AbortSignal.timeout(2000) }); console.log(res.status, await res.text()); } catch (err) { console.error(err); process.exit(1); }"
404 Entry not found
response confirms the proxy is fully up and routing correctly.