From fa05ebc849abf1c7fdffac7245ed190ae513d2c4 Mon Sep 17 00:00:00 2001 From: Rudimar Ronsoni Date: Thu, 25 Jun 2026 04:54:05 +0200 Subject: [PATCH] docs: clarify OpenCode integration (#1317) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description Clarifies the OpenCode documentation follow-up for PR #1105 so users can install `headroom-opencode`, configure provider routing, use the native plugin, and copy working retrieve/compression helper examples. ## Type of Change - [x] Documentation update - [ ] Bug fix - [ ] New feature - [ ] Breaking change ## Changes Made - Documented how `headroom wrap opencode` wires provider config, MCP tools, and runtime environment. - Documented the native `HeadroomPlugin` path, `HEADROOM_PROXY_URL`, retrieve tooling, and programmatic config helpers. - Fixed `plugins/opencode/README.md` examples so `compressWithHeadroom` uses the exported options-object API and `headroom_retrieve` uses `hash`. ## Testing - [x] Type checks pass. - [x] Unit tests pass. - [x] Whitespace check passes. ### Test Output ```text plugins/opencode: npm run typecheck > tsc --noEmit plugins/opencode: npm test Test Files 2 passed (2) Tests 9 passed (9) docs: npm run types:check ✓ Types generated successfully repo: git diff --check (no output) ``` ## Real Behavior Proof - Environment: Local macOS worktree at `docs/pr-1105-documentation-followup`, Node/npm project commands run from `plugins/opencode` and `docs`. - Exact command / steps: Updated the README snippets, ran `npm run typecheck`, reran `npm test` with elevated permissions after the sandbox blocked a local `127.0.0.1` listener, ran `npm run types:check` in `docs`, and ran `git diff --check`. - Observed result: Typecheck completed with `tsc --noEmit`; the OpenCode package test suite reported 2 files and 9 tests passed; docs type generation completed successfully; `git diff --check` produced no output. - Not tested: Browser-rendered documentation preview. `docs: npm run build` was started locally but produced no output for roughly 90 seconds and was stopped, so this follow-up does not claim a fresh local docs build result. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Additional Notes - The linked review comment asked for README examples to match `compressWithHeadroom(messages, options)` and `createHeadroomRetrieveTool` requiring `hash`; both snippets now match the exported API. --- docs/content/docs/opencode.mdx | 128 +++++++++++++++++++------------ plugins/opencode/README.md | 135 ++++++++++++++------------------- 2 files changed, 139 insertions(+), 124 deletions(-) diff --git a/docs/content/docs/opencode.mdx b/docs/content/docs/opencode.mdx index 17c35a949..d82981a7b 100644 --- a/docs/content/docs/opencode.mdx +++ b/docs/content/docs/opencode.mdx @@ -3,78 +3,78 @@ title: OpenCode Integration description: Route OpenCode traffic through Headroom for token compression, MCP tools, and cached model access. One command to wrap, one to unwrap. --- -Use `headroom wrap opencode` to route all OpenCode LLM traffic through the Headroom proxy with a single command. The proxy compresses context, injects MCP tools, and routes API calls to your configured backend. +Use `headroom wrap opencode` to route OpenCode LLM traffic through the Headroom proxy with a single command. The wrapper starts or reuses the proxy, writes OpenCode config, injects Headroom MCP tools, adds RTK context filtering, and launches OpenCode with the generated config. -## Quick start +The `headroom-opencode` npm package also exports a native OpenCode plugin. The plugin can be used directly from OpenCode config when you want in-process transport interception plus the Headroom retrieve tool. + +## Quick Start ```bash headroom wrap opencode ``` -This starts the Headroom proxy, injects a `headroom` provider into OpenCode's config, registers Headroom MCP tools, sets up RTK context filtering, and launches OpenCode through the proxy. - -When you're done: +When you are done: ```bash headroom unwrap opencode ``` -## What `wrap opencode` does +## What `wrap opencode` Does | Step | What happens | |---|---| -| Provider injection | Writes a `headroom` provider using `@ai-sdk/openai-compatible` into `opencode.json`, pointing at `http://127.0.0.1:/v1` | -| Runtime env | Sets `OPENCODE_CONFIG_CONTENT` with provider + model + MCP config so OpenCode picks up the proxy provider at launch | -| Provider compatibility | Leaves `OPENAI_BASE_URL` and `ANTHROPIC_BASE_URL` untouched so OpenCode `/connect` providers keep their own routing | -| Context tool | Injects RTK (or `lean-ctx`) instructions into `~/.config/opencode/AGENTS.md` and project `AGENTS.md` | -| MCP setup | Registers the Headroom MCP server (`headroom_compress`, `headroom_retrieve`, `headroom_stats`) | -| Serena MCP | Optionally registers Serena code graph tools (`--no-serena` to skip) | -| Backup | Snapshots `opencode.json` to `opencode.json.headroom-backup` before making any changes | -| Launch | Starts the `opencode` binary through the proxy | +| Proxy | Starts the Headroom proxy unless `--no-proxy` is set | +| Provider config | Writes a `headroom` provider using `@ai-sdk/openai-compatible` into `opencode.json` and `OPENCODE_CONFIG_CONTENT` | +| Runtime env | Sets `OPENCODE_CONFIG_CONTENT` so OpenCode reads provider, model, and MCP config at launch | +| Provider compatibility | Leaves `OPENAI_BASE_URL` and `ANTHROPIC_BASE_URL` untouched so `/connect` providers keep their own routing | +| Context tool | Injects RTK or `lean-ctx` instructions into OpenCode AGENTS files | +| MCP setup | Registers Headroom MCP tools: `headroom_compress`, `headroom_retrieve`, and `headroom_stats` | +| Serena MCP | Optionally registers Serena code graph tools. Use `--no-serena` to skip it | +| Backup | Snapshots `opencode.json` to `opencode.json.headroom-backup` before changing it | +| Launch | Starts the `opencode` binary with the generated config | ## Options ```bash -headroom wrap opencode \ - --port 8787 \ # Proxy port (default: random available port) +headroom wrap opencode --port 8787 \ # Proxy port. Defaults to a random available port --no-rtk \ # Skip RTK context tool injection - --no-mcp \ # Skip headroom MCP registration + --no-mcp \ # Skip Headroom MCP registration --no-serena \ # Skip Serena code graph MCP --code-graph \ # Include code graph in context - --no-proxy \ # Use existing proxy instead of starting one + --no-proxy \ # Use an existing proxy instead of starting one --learn \ # Enable memory and live learning --memory \ # Enable persistent memory - --backend anthropic \ # Set backend: anthropic, openai, anyllm + --backend anthropic \ # Backend: anthropic, openai, anyllm --anyllm-provider ... \ # AnyLLM provider selection --region ... \ # Provider region - -- # Arguments passed to opencode binary + -- # Arguments passed to the opencode binary ``` -## Provider model mapping +## Provider Model Mapping -The `headroom` provider exposes these models, all routed through the proxy: +The generated `headroom` provider exposes these models through the proxy: | Provider model | Upstream model | |---|---| -| `headroom/claude-sonnet-4-6` | Claude Sonnet 4.6 (200K context, 16K output) | -| `headroom/claude-opus-4-6` | Claude Opus 4.6 (200K context, 16K output) | -| `headroom/claude-haiku-4-5-20251001` | Claude Haiku 4.5 (200K context, 8K output) | -| `headroom/gpt-4o` | GPT-4o (128K context, 16K output) | -| `headroom/gpt-4.1` | GPT-4.1 (1M context, 32K output) | +| `headroom/claude-sonnet-4-6` | Claude Sonnet 4.6, 200K context, 16K output | +| `headroom/claude-opus-4-6` | Claude Opus 4.6, 200K context, 16K output | +| `headroom/claude-haiku-4-5-20251001` | Claude Haiku 4.5, 200K context, 8K output | +| `headroom/gpt-4o` | GPT-4o, 128K context, 16K output | +| `headroom/gpt-4.1` | GPT-4.1, 1M context, 32K output | -The default model is `headroom/claude-sonnet-4-6`. Change it in `opencode.json` or via `OPENCODE_CONFIG_CONTENT`. +The default model is `headroom/claude-sonnet-4-6`. Change it in `opencode.json` or in the generated `OPENCODE_CONFIG_CONTENT` payload. -## Environment variables +## Environment Variables | Variable | Description | |---|---| -| `OPENCODE_CONFIG_CONTENT` | JSON payload with provider, model, and MCP config injected by wrap | -| `HEADROOM_PROXY_URL` | Optional metadata used by the `headroom-opencode` plugin | +| `OPENCODE_CONFIG_CONTENT` | JSON payload with provider, model, and MCP config injected by `wrap` | +| `HEADROOM_PROXY_URL` | Proxy URL passed to the native `headroom-opencode` plugin. Defaults to `http://127.0.0.1:8787` inside the plugin | | `HEADROOM_CONTEXT_TOOL` | Set to `lean-ctx` to use lean-ctx instead of RTK | -## Persistent installs +## Persistent Installs -`headroom install` supports opencode as a target for persistent provider wiring: +`headroom install` supports OpenCode as a target for persistent provider wiring: ```bash headroom install apply --preset persistent-service --providers manual --target opencode @@ -88,33 +88,67 @@ Provider scope is also supported: headroom install apply --preset persistent-service --scope provider --providers manual --target opencode ``` -## How it works under the hood +## Native OpenCode Plugin -1. **Config injection** — The wrap command writes a `provider.headroom` block into `opencode.json`. The provider uses the `@ai-sdk/openai-compatible` npm package, which OpenCode already supports natively. Model mappings route requests through `http://127.0.0.1:/v1`. +The `headroom-opencode` package exports `HeadroomPlugin` for direct OpenCode plugin registration. The plugin installs Headroom transport interception inside OpenCode, exposes the `headroom_retrieve` tool, and publishes Headroom metadata through the OpenCode plugin output env. -2. **Runtime config** — `OPENCODE_CONFIG_CONTENT` is set as an env var containing the full provider + model + MCP JSON. OpenCode reads this at startup and merges it with the on-disk config. +Example: -3. **MCP tools** — Three Headroom MCP tools are registered: `headroom_compress` (compress context), `headroom_retrieve` (fetch original from CCR store), `headroom_stats` (compression statistics). +```ts +import { HeadroomPlugin } from "headroom-opencode"; -4. **Unwrap** — Restores `opencode.json` from the pre-wrap backup. If no backup exists, strips Headroom marker blocks from the config. Also unregisters Headroom MCP servers. +export default async function plugin(input) { + return HeadroomPlugin(input, { + proxyUrl: process.env.HEADROOM_PROXY_URL ?? "http://127.0.0.1:8787", + }); +} +``` -## Optional OpenCode plugin +Use this plugin when OpenCode should intercept provider traffic in-process. Use `headroom wrap opencode` when you want the CLI to manage the proxy, config injection, MCP registration, backups, and unwrap behavior. -The `headroom-opencode` npm package also exports an OpenCode plugin. The plugin -is additive: it exposes the `headroom_retrieve` tool and Headroom metadata such -as `HEADROOM_PROXY_URL`, but it does not route provider traffic by itself. +## Programmatic Config Helpers -Provider traffic is still routed by the injected `headroom` provider in -`OPENCODE_CONFIG_CONTENT` or `opencode.json`. This keeps providers configured -through OpenCode `/connect` in charge of their own credentials and base URLs. +The package also exports helpers for custom integrations: + +```ts +import { + buildOpencodeConfigContent, + createHeadroomProvider, + createHeadroomRetrieveTool, +} from "headroom-opencode"; + +const provider = createHeadroomProvider({ proxyPort: 8787 }); +const config = buildOpencodeConfigContent({ + proxyPort: 8787, + defaultModel: "claude-sonnet-4-6", +}); +const retrieve = createHeadroomRetrieveTool({ + proxyBaseUrl: "http://127.0.0.1:8787", +}); +``` + +## How It Works Under The Hood + +1. **Config injection**. The wrapper writes a `provider.headroom` block into `opencode.json`. The provider uses `@ai-sdk/openai-compatible`, which OpenCode supports natively. Model mappings route requests through `http://127.0.0.1:/v1`. +2. **Runtime config**. `OPENCODE_CONFIG_CONTENT` is set as an env var containing the full provider, model, and MCP JSON. OpenCode reads it at startup and merges it with on-disk config. +3. **MCP tools**. Headroom registers `headroom_compress`, `headroom_retrieve`, and `headroom_stats` unless `--no-mcp` is set. +4. **Native plugin path**. `HeadroomPlugin` installs Headroom transport interception and uses `HEADROOM_PROXY_URL` or `http://127.0.0.1:8787` to reach the proxy. +5. **Unwrap**. `headroom unwrap opencode` restores `opencode.json` from the pre-wrap backup when present, strips Headroom marker blocks when no backup exists, and unregisters Headroom MCP servers. ## Troubleshooting -**OpenCode doesn't use the headroom provider.** -Check that `OPENCODE_CONFIG_CONTENT` is set and contains the correct provider block. The wrap command prints the env vars it sets. +**OpenCode does not use the headroom provider.** + +Check that `OPENCODE_CONFIG_CONTENT` is set and contains the `provider.headroom` block. The wrap command prints the env vars it sets. + +**The native plugin cannot reach Headroom.** + +Set `HEADROOM_PROXY_URL` to the running proxy URL, for example `http://127.0.0.1:8787`. **Provider not found after unwrap.** + If unwrap left the provider configured, run `headroom unwrap opencode` again, or manually restore from `~/.config/opencode/opencode.json.headroom-backup`. **Proxy port conflict.** + Use `--port` to select a specific port, or let the proxy auto-select an available one. diff --git a/plugins/opencode/README.md b/plugins/opencode/README.md index 4049db141..d1f1adeea 100644 --- a/plugins/opencode/README.md +++ b/plugins/opencode/README.md @@ -1,6 +1,9 @@ # headroom-opencode -Headroom proxy integration for [OpenCode](https://opencode.ai). Routes LLM traffic through the Headroom proxy for token compression, provides CCR retrieval, and handles provider configuration. +OpenCode integration helpers for Headroom. The package supports two integration paths: + +1. Provider config helpers used by `headroom wrap opencode` and persistent installs. +2. A native OpenCode plugin that installs Headroom transport interception and exposes the retrieve tool. ## Install @@ -8,119 +11,97 @@ Headroom proxy integration for [OpenCode](https://opencode.ai). Routes LLM traff npm install headroom-opencode ``` -## Quick start +## Provider Config Helpers -### Create a Headroom provider for opencode.json +Use these helpers when you need to generate OpenCode config that routes a `headroom` provider through a running Headroom proxy. ```ts -import { createHeadroomProvider } from "headroom-opencode"; +import { + buildOpencodeConfigContent, + createHeadroomProvider, +} from "headroom-opencode"; -const provider = createHeadroomProvider({ - proxyPort: 8787, -}); - -// Write to opencode.json: -// { -// "provider": { "headroom": provider }, -// "model": "headroom/claude-sonnet-4-6" -// } -``` - -### Build OPENCODE_CONFIG_CONTENT - -```ts -import { buildOpencodeConfigContentJson } from "headroom-opencode"; - -const json = buildOpencodeConfigContentJson({ +const provider = createHeadroomProvider({ proxyPort: 8787 }); +const config = buildOpencodeConfigContent({ proxyPort: 8787, defaultModel: "claude-sonnet-4-6", }); -// Set as env var: process.env.OPENCODE_CONFIG_CONTENT = json; +console.log(provider.provider.headroom.npm); +console.log(config.model); ``` -### Compress messages through the proxy +The generated provider uses `@ai-sdk/openai-compatible` and points model requests at `http://127.0.0.1:/v1`. + +## Native OpenCode Plugin + +Use `HeadroomPlugin` when OpenCode should intercept provider traffic in-process and expose Headroom tooling from a plugin. ```ts -import { compressWithHeadroom } from "headroom-opencode"; +import { HeadroomPlugin } from "headroom-opencode"; -const result = await compressWithHeadroom(messages, { - model: "gpt-4o", - proxyUrl: "http://localhost:8787", -}); - -console.log(`Saved ${result.tokensSaved} tokens`); +export default async function plugin(input) { + return HeadroomPlugin(input, { + proxyUrl: process.env.HEADROOM_PROXY_URL ?? "http://127.0.0.1:8787", + }); +} ``` -### CCR retrieve tool +`HeadroomPlugin`: + +- installs Headroom transport interception for OpenCode provider traffic. +- exposes the `headroom_retrieve` tool. +- publishes `HEADROOM_PROXY_URL` in the plugin output env. +- defaults to `http://127.0.0.1:8787` when no proxy URL is supplied. + +## Retrieve Tool ```ts import { createHeadroomRetrieveTool } from "headroom-opencode"; -const retrieveTool = createHeadroomRetrieveTool({ - proxyBaseUrl: "http://localhost:8787", +const retrieve = createHeadroomRetrieveTool({ + proxyBaseUrl: "http://127.0.0.1:8787", }); -// Register in OpenCode's MCP config under mcp.headroom_retrieve +const result = await retrieve.execute({ + hash: "0123456789abcdef01234567", + query: "needle", +}); ``` -## API +The tool calls `/v1/retrieve/` on the Headroom proxy. -### `createHeadroomProvider(options?)` +## Compression Helper -Creates a provider object compatible with OpenCode's `@ai-sdk/openai-compatible` format. +```ts +import { compressWithHeadroom } from "headroom-opencode"; -| Option | Default | Description | -|---|---|---| -| `proxyBaseUrl` | `http://127.0.0.1:8787` | Full proxy base URL | -| `proxyPort` | `8787` | Proxy port (ignored if proxyBaseUrl is set) | -| `models` | See below | Custom model mappings | -| `defaultModel` | `claude-sonnet-4-6` | Default model ID | +const result = await compressWithHeadroom( + [{ role: "user", content: "Summarize this file" }], + { model: "gpt-4o", proxyUrl: "http://127.0.0.1:8787" }, +); -### `buildOpencodeConfigContent(options?)` +console.log(`Saved ${result.tokensSaved} tokens`); +``` -Returns a full `OPENCODE_CONFIG_CONTENT` JSON object with provider and model. +## Models -### `buildOpencodeConfigContentJson(options?)` - -Same as above but returns a JSON string ready for the `OPENCODE_CONFIG_CONTENT` env var. - -### `compressWithHeadroom(messages, options?)` - -Compresses an array of messages through the Headroom proxy. Returns compression stats and compressed messages. - -### `createHeadroomRetrieveTool(config)` - -Creates a CCR retrieve tool for OpenCode's MCP system. - -### `setDefaultProxyUrl(url)` / `getDefaultProxyUrl()` - -Set or get the default proxy URL for all operations. Defaults to `HEADROOM_BASE_URL` env var or `http://localhost:8787`. - -## Default models - -| Model ID | Context | Output | -|---|---|---| +| Model | Context | Output | +|---|---:|---:| | `claude-sonnet-4-6` | 200K | 16K | | `claude-opus-4-6` | 200K | 16K | | `claude-haiku-4-5-20251001` | 200K | 8K | | `gpt-4o` | 128K | 16K | | `gpt-4.1` | 1M | 32K | -## OpenCode plugin +The provider config exposes these as `headroom/` and defaults to `headroom/claude-sonnet-4-6`. -The package default export is an OpenCode plugin. It adds a `headroom_retrieve` -tool and Headroom metadata for shell commands: +## Environment -```json -{ - "plugin": [["headroom-opencode", { "proxyUrl": "http://127.0.0.1:8787" }]] -} -``` - -The plugin does not set `OPENAI_BASE_URL` or `ANTHROPIC_BASE_URL`. Model traffic -is routed by the `headroom` provider config generated by -`buildOpencodeConfigContent` or `headroom wrap opencode`. +| Variable | Used by | Description | +|---|---|---| +| `HEADROOM_PROXY_URL` | Native plugin | Proxy URL used by `HeadroomPlugin` | +| `OPENCODE_CONFIG_CONTENT` | OpenCode wrapper | Generated OpenCode provider, model, and MCP config | ## License