Jev
Route OpenCode tool selection through TypeSafe System-1 (Jev): a cheap fast model picks the next tool so the reasoning model executes instead of deliberating.
1
0
22.4
Multi-signal model
4 hours ago
2026-10-05
Install and configure
opencode.jsonWrites to this project's opencode.json — applies to this repository only.
opencode.json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["@danipl/opencode-jev@1.2.0"]
}Writes to ~/.config/opencode/opencode.json — applies to every project.
~/.config/opencode/opencode.json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["@danipl/opencode-jev@1.2.0"]
}If you want to modify the plugin locally, install it into the project and reference the local path.
shell
pnpm add -D @danipl/opencode-jevOpenCode loads npm dependencies through its embedded runtime on startup and caches them locally — no manual global install needed.
Your reasoning model should think about your problem — not about which of twelve tools to call.
opencode-jev puts TypeSafe's cheap, fast System-1 model in front of every inference request: it picks
the next tool, trims the request to that one tool, and lets the expensive model do what you pay it for.
Why
Every iteration of an agentic loop re-sends the whole conversation plus every tool schema to a
premium model, which burns tokens and latency deliberating between read, grep, edit, bash,
MCP tools… — a routing decision, repeated every turn, billed at reasoning prices.
The fix: this plugin observes each outbound inference request (OpenCode V2 http.request session
hook), asks Jev — a cheap, fast, calibrated System-1 model — which of the request's tools comes
next, and when Jev is confident enough, trims the tools array down to that single tool. The
reasoning model then executes instead of deliberating. Dropped tool schemas also shrink the
request itself.
flowchart LR
O["OpenCode<br/>agent loop"] -->|request · 12 tool schemas| J{{"jev plugin<br/>ask System-1"}}
J -->|"confident ✓<br/>tools: [1]"| L["Reasoning model<br/>executes immediately"]
J -->|"not confident ✗<br/>request untouched"| L
style J fill:#12101f,stroke:#818cf8,color:#e2e8f0
style L fill:#0b0b14,stroke:#22d3ee,color:#e2e8f0
style O fill:#0b0b14,stroke:#e879f9,color:#e2e8f0
Trimming — not pinning via tool_choice — is deliberate: providers reject forced tool_choice
in thinking mode (HTTP 400), while a single offered tool is always valid.
The full picture — diagrams inside the agentic loop, the token/latency math, and the trust dial — lives in docs/HOW_IT_WORKS.md.
At a glance
| ⚡ System-1 routing | Jev answers "which tool next, and how sure am I?" — fast, cheap, calibrated. |
| ✂️ Trim, don't pin | Only the tools array shrinks; tool_choice is never touched. Always provider-valid. |
| 🛡️ Fail-safe by design | Low confidence, network error, timeout, parse error, bad key → original request passes through untouched. It can never break a session. |
| 🔭 Live decision log | Every request ends in one tagged line — apply: or bypass: with the reason. tail -f it. |
| 🫥 Invisible when unconfigured | No API key → no hook registered, no log file, zero latency. |
| 🔌 Any compatible gateway | Anthropic Messages (/v1/messages), OpenAI Chat Completions (/chat/completions), OpenAI Responses (/responses) — path-matched. |
Quick start
1. Register the plugin — OpenCode installs it automatically on next start:
// opencode.jsonc — project-level or ~/.config/opencode/
{
"plugins": ["@danipl/opencode-jev"]
}
Requires OpenCode V2 (Plugin.define API; V1 hosts reject it).
The bare name tracks the latest dist-tag — every publish moves it, so a plain
"plugins": ["@danipl/opencode-jev"] keeps you current with zero maintenance. Pin an exact
version for reproducibility or to freeze a known-good release:
{ "plugins": ["@danipl/opencode-jev@1.2.0"] } // x-release-please-version — exact version, never auto-updates
Heads-up while pre-1.0: feat: releases (minor bumps) can change behavior. If that matters to
you, pin; otherwise latest is the recommended choice.
2. Set an API key — either method:
export TYPESAFE_API_KEY="apikey_..."
or copy jev.yaml.example to jev.yaml next to your OpenCode config
(or anywhere, pointed to by JEV_CONFIG_PATH).
3. Watch it route:
tail -f /tmp/opencode-jev.log
That's it. Run a session — you'll see apply: lines where Jev trimmed the tools and bypass:
lines (with reasons) where the request went through untouched.
Configuration
Config sources — first defined value wins per field:
$JEV_CONFIG_PATHfile (JSON or YAML)./jev.config.yaml/.yml/.json./.opencode/jev.yaml/jev.json~/.config/jev/config.yaml/config.json- plugin options (directory-package registrations only)
- env
TYPESAFE_API_KEY/JEV_API_URL/JEV_MIN_CONFIDENCE
Config-file fields: apiKey, apiUrl, minConfidence, model, timeoutMs
(milliseconds; non-positive or bogus values count as unset) — see
jev.yaml.example. JEV_MODEL / JEV_TIMEOUT_MS are env
fallbacks for model / timeoutMs.
| Env var | Default | Meaning |
|---|---|---|
JEV_MODEL |
jev-latest |
Jev model id (fallback for model) |
JEV_TIMEOUT_MS |
2000 |
Jev round-trip timeout (fallback for timeoutMs) |
JEV_DEBUG_FILE |
/tmp/opencode-jev.log |
decision log path |
JEV_DEBUG |
— | 1 echoes the log to stdout |
JEV_DEBUG_MAX_BYTES |
262144 |
log rotation cap (one .1 backup) |
Safety
Jev must never break a session. The request passes through untouched on: low confidence,
respond_to_user, payloads with no usable tools or an already-pinned tool_choice, network
failure, timeout, parse errors, or an invalid API key (latched off after the first 401/403 — zero
added latency afterwards). Only primary agent-loop requests are considered
(event.kind === "primary"); title/compaction traffic is skipped. Responses-API built-in tools
(type !== "function") are never offered to Jev, never trimmed to, and never removed by a trim —
they always survive (issue #12 verdict: Jev may only demote function tools).
Reading the decision log
Every decision is appended to the debug log — tail -f /tmp/opencode-jev.log to watch routing
live. Each request ends in one tagged line:
apply:— Jev acted; tools trimmed to its pick (+ any built-ins kept).bypass:— request untouched, with the reason.
Line-by-line interpretation: docs/DEVELOPMENT.md — "Reading the decision log".
Privacy
With routing active, every eligible request POSTs a compact snapshot to apiUrl (default
https://api.typesafe.ai/v1/systemone — TypeSafe's endpoint; override with the apiUrl config
field or JEV_API_URL). The snapshot contains:
state— the first user message plus the last 4 conversation turns, each turn capped at 2 KB and the whole payload at 8 KB. In a coding agent those turns routinely contain file contents, tool output, and error messages — treat this like sending context to another model provider.- Tool names — up to the first 254 names, offered as routing choices. Names only, never schemas or descriptions.
No apiKey configured = no hook registered = nothing ever leaves your machine. If conversation
egress is not acceptable at all, leave the plugin unconfigured or point apiUrl at a self-hosted
Jev-compatible endpoint.
Development
Full developer guide — architecture, unit testing, local manual testing with OpenCode, PR workflow, SDLC: docs/DEVELOPMENT.md.
npm install
npm run build # tsc -> dist/
npm pack # inspect the tarball
Local trial without publishing — point OpenCode at the checkout:
{ "plugins": ["/absolute/path/to/opencode-jev"] }
Releasing
Fully automated via release-please — the commit type picks the bump: fix: → patch, feat: →
minor, feat!: or a BREAKING CHANGE: footer → major. Merge the resulting PR and a release PR
appears; merge that and the version bump, tag, GitHub Release and npm publish happen on their
own. See docs/CICD.md.
License
MIT — © danipl
Similar plugins
Jev Router
@robertn702/opencode-jev-router
Adaptive reasoning effort for OpenCode V2 with request-local GPT-6 model selection
Context Pruner
opencode-context-pruner
Continuous verbatim context pruning for OpenCode, powered by TypeSafe Jev. Port of fast-jev-compaction adapted to OpenCode's context hook.
Intent Gate
opencode-intent-gate
TypeSafe Jev-powered intent gate for OpenCode: makes the agent confirm intent before diving into underspecified requests.