Skip to content

Repository files navigation

opencode-hermes

Claude Code-style context reporting for OpenCode — delivered as x-hermes-* HTTP request headers instead of prompt mutations.

On every LLM request the plugin computes the same dynamic context blocks that Claude Code injects into its system prompt (environment, scratchpad directory, git startup snapshot) and sends them as request headers. A gateway (e.g. Hermes) parses the headers, rebuilds the context blocks, and injects them into the request body — so context assembly lives in one place, on the server side.

中文说明 · Gateway contract (解析与注入规范)

Note: This plugin only reports facts. It does not modify your system prompt — injection is the gateway's job. Without a gateway that understands the headers, they pass through harmlessly.

Quick Start

  1. Add the plugin to your opencode.json (project or global ~/.config/opencode/opencode.json):
{
  "plugin": ["@ephemushroom/opencode-hermes"]
}

OpenCode installs plugin entries automatically with Bun on startup — no manual npm install step needed.

  1. Restart OpenCode.
  2. Send a message with a Claude model — the outgoing request now carries the x-hermes-* headers.

OpenCode 2 beta

From v0.2.0, both OpenCode generations use the same package name. OpenCode 2 uses the native plugins configuration (plural):

{
  "$schema": "https://opencode.ai/config.json",
  "plugins": [{
    "package": "@ephemushroom/opencode-hermes",
    "options": { "toolSearchDelivery": "immediate" }
  }]
}

The unified entry is tested with opencode 1.18.29 and opencode2 0.0.0-beta-19192; the V2 SDK is pinned to 0.0.0-beta-18050. Older V1 loaders without object-style server() support are not supported by this entry. V2 registers a direct ToolSearch tool and sends a versioned x-hermes-client-family: opencode2 contract. Hermes converts the OpenCode 2 catalog into Claude Code's eager/deferred ToolSearch shape; when Claude calls ToolSearch, the plugin resolves the schemas and submits the <functions> result through the local OpenCode 2 session API.

immediate maps to the current API's steer delivery and is the default; deferred maps to queue. The current names steer and queue are accepted directly.

The root, main, and /server exports share a plain { id, server, setup } definition: V1 calls server, V2 calls setup. Effect is optional; this plugin uses the officially supported Promise API and needs no Effect rewrite. Importing the module does not initialize either adapter.

The /v2 export remains available for programmatic imports. Do not use an npm /v2 suffix in the current beta's plugins configuration: it is interpreted as a local path. Replace any old suffixed entry with the bare name, rather than configuring both. Direct callers of the former default function should use the named HermesPlugin export (or default.server).

After upgrading, finish active work and restart opencode. For V2, also run opencode2 service restart before reopening the client.

With options:

{
  "plugin": [["@ephemushroom/opencode-hermes", {
    "environment": true,
    "scratchpad": true,
    "contextManagement": true,
    "gitStatus": true,
    "modelFilter": "claude",
    "scratchpadDirName": "claude",
    "gitTimeoutMs": 5000,
    "extraEnvironment": { "team": "platform" }
  }]]
}

What You Get

Header Value Description
x-hermes-context-version "1" Contract version, bumped on breaking changes
x-hermes-environment ASCII-safe JSON agent, cwd, isGitRepo, platform, shell, osVersion (model info is not reported — the gateway reads it from the request body's model field)
x-hermes-scratchpad ASCII-safe JSON { "path" } — directory already created on disk (mode 0o700)
x-hermes-context-management "true" Flag only; the block is a static constant owned by the gateway
x-hermes-git-status ASCII-safe JSON { branch, mainBranch, user?, status, statusTruncated, recentCommits }omitted entirely outside git repos

ASCII-safe JSON: every non-ASCII character (e.g. CJK file names in git status) is escaped as \uXXXX, because HTTP header values are transported as latin-1. JSON.parse / System.Text.Json restore them natively — no custom decoding.

Model filtering

Headers are sent only when the model ID contains claude (case-insensitive). Requests to other models (kimi, gpt, …) pass through untouched — the gateway should not inject Claude Code-style context for them. Bedrock/OpenRouter-style IDs like anthropic.claude-* match as well. Change the substring with modelFilter, or set it to "" to report for all models. Filtering is per-request: switching models mid-session only affects the requests that use them.

How It Works

flowchart LR
    OC[OpenCode<br/>+ this plugin] -->|LLM request +<br/>x-hermes-* headers| GW[Gateway<br/>e.g. Hermes]
    GW -->|1. parse headers<br/>2. inject context blocks<br/>into system prompt<br/>3. strip headers| UP[Upstream<br/>LLM provider]
Loading
  • The chat.headers hook fires on every LLM request; per-session caches keep git and scratchpad work to one shot per session.
  • The gateway must strip all x-hermes-* headers before forwarding — they contain local paths and must never leak upstream raw.
  • Parsing, injection templates, and C# examples: docs/gateway-contract.md.

Options

Option Default Description
environment true Send x-hermes-environment
scratchpad true Send x-hermes-scratchpad and create the directory
contextManagement true Send x-hermes-context-management
gitStatus true Send x-hermes-git-status
modelFilter "claude" Only report when the model ID contains this substring; "" disables filtering
scratchpadDirName "claude" Base directory name under the temp root
gitTimeoutMs 5000 Per-git-command timeout
extraEnvironment Extra key/value pairs merged into the environment JSON
toolSearchDelivery "immediate" (steer) OpenCode 2 only: steer/queue, with immediate/deferred aliases

Environment Variables

Variable Effect
HERMES_CONTEXT_DISABLE=1 Disable the plugin entirely — no headers at all
CLAUDE_CODE_TMPDIR Scratchpad temp root (same semantics as Claude Code)

Development

bun install
bun test            # bun's built-in runner, includes a live git-repo smoke test
bun run typecheck   # tsc --noEmit (strict)
bun run build       # bundle dist/index.js + emit declarations
bun run lint        # oxlint

The plugin executes using Node/Bun built-ins and Zod. Both OpenCode SDK imports are type-only (erased at build time); their packages remain dependencies so published TypeScript declarations can resolve them. No SDK or Effect code is imported at runtime.

License

MIT

About

OpenCode plugin: Claude Code-style context reporting as x-hermes-* request headers for gateway-side injection

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages