Surplus Intelligence status and routing controls for the opencode TUI.
If you buy LLM tokens through Surplus Intelligence, this plugin puts your costs and the live marketplace right in your terminal:
- Live TPS meter next to the input box — tokens per second, average speed, and time-to-first-token.
- "Today" block at the top of the sidebar — today's blended price per 1M tokens, % off vs list, and total spend (from Surplus's settled usage records).
- Routing status in the sidebar footer — your discount floor, the top 3 marketplace offers (blended to your usage), the routing pin, and this session's cost / avg / token total.
- Model discovery — the plugin scrapes the Surplus model catalog at startup (and via
sync-models.mjs) so every tier provider lists the full marketplace, without hand-maintaining model lists. - One discount tier per model — the tier providers are hidden behind a single grouped "Surplus Intelligence" entry in the model picker;
/surplus-tiersets the floor for the active model and the sidebar shows only that one tier. - Floor-failure prompt — with
/surplus-flag yes, aminimum_discount_not_metfailure immediately pops a dialog offering a recommended tier (the strictest one that currently passes) to adjust to; with the flag off, failures just fail normally (no prompt, no auto-change). /surplus-*commands — instant, free, no LLM round-trip (see below).
It ships as three files:
| File | Kind | What it adds |
|---|---|---|
surplus-sidebar.tsx |
TUI plugin | The meter, sidebar blocks, and /surplus-* commands |
surplus-control.js |
server plugin | surplus_* agent tools, model discovery, floor proxy, floor-failure prompt |
sync-models.mjs |
CLI script | Manual model-catalog scrape (cache + optionally write config) |
- opencode 1.18.x or newer.
- A Surplus Intelligence API key saved in opencode (
opencode auth login, provider ID starting withsurplus). The key is read from yourauth.jsonat runtime — it is never stored in this plugin. - Optional but recommended: Surplus providers configured in
opencode.jsonso requests route through the/min{N}discount-floor URLs (see "Providers" below).
No secrets here. This package contains no API keys, no wallet addresses, and no personal paths. It reads your key from opencode's own
auth.json.
git clone https://github.com/<your-user>/opencode-surplus.git
cd opencode-surplus
./install.shOr without git — download this folder and run ./install.sh from inside it.
That copies both plugin files into your opencode config, backs up and updates your tui.json, and tells you to restart. Done.
Manual install (if you prefer to place files yourself):
# TUI plugin:
cp surplus-sidebar.tsx ~/.config/opencode/surplus-sidebar.tsx
# add to ~/.config/opencode/tui.json:
# { "plugin": ["./surplus-sidebar.tsx"] }
# Server plugin:
mkdir -p ~/.config/opencode/plugins
cp surplus-control.js ~/.config/opencode/plugins/surplus-control.jsRestart opencode after installing.
The Surplus API enforces discount floors via the URL path, not the request body. The plugin keeps one surplus catalog provider — every discovered (text-output) model is listed there exactly once, so the model picker shows a single clean "Surplus Intelligence" group:
The provider's baseURL is pointed at a local floor proxy the plugin runs (http://127.0.0.1:<port>/v1). On every request the proxy reads the model's current floor from ~/.local/share/opencode/surplus-control.json and forwards to https://api.surplusintelligence.ai/min{floor}/v1. That makes floors instant:
/surplus-tier NN(orsurplus_settier) sets the floor for the active model (≥70/80/90/95% or off) — it applies to the very next request, no restart.- Legacy tier providers you've already configured (
surplus-70/80/90/95) keep working as floor shortcuts, and catalog models you've explicitly placed there are skipped from the catalog so nothing is listed twice. - If the proxy can't start, the plugin falls back to per-model
provider.apiURLs (floors apply on the next start).
Then run opencode auth login for the surplus provider (or copy the existing surplus credential), pick the model in /models, and the sidebar shows its floor.
Note: Surplus ignores
max_price_per_1m/X-Max-Price-Per-1M(verified live). The floor is the/min{N}URL.
Run these with / (autocomplete) or Ctrl-P (command palette, category Surplus). They run in-process — no model call, instant, free:
| Command | What it shows |
|---|---|---|
| /surplus-status (alias /surplus) | Active model, provider, single discount floor, pin, offers, session stats |
| /surplus-offers | Top 10 healthy sellers for the active model (optionally filtered to the pin) |
| /surplus-tier | Set the discount floor for the active model (≥70/80/90/95% or off) |
| /surplus-flag | Toggle backoff-prompt mode (prompt to adjust tier after 60s vs auto-switch) |
| /surplus-models | List the discovered Surplus model catalog |
| /surplus-spend | Today's spend + tokens + blended $/1M + savings vs list, and this session's spend/avg |
| /surplus-pin | Interactive picker to pin the active model to a seller host (or reset/clear/type a URL) |
| /surplus-clear | Clears stored overrides for the active model |
| /surplus-tui | Disable the TUI integration (meter, sidebar, commands) for this session |
The agent-callable server tools mirror most of these: surplus_offers, surplus_pin, surplus_tier, surplus_settier, surplus_flag, surplus_models, surplus_backoff, surplus_spend, surplus_clear.
Context <- opencode's built-in panel (this session)
...tokens
...$ spent
Today <- our block: today's blended price, % off, total spend
Floor ≥90% (min90) <- the discount floor of the provider you're on
94.2% off · $0.00072/1M · OpenRouter <- top marketplace offers,
94.0% off · ... <- blended to YOUR usage
Pin none <- no routing pin; Surplus picks the best seller
$0.12 · avg $0.0008/1M · 148.2M tok <- THIS session's totals
Color coding: green = ≥90% off, yellow = ≥80%, red = below.
- Today avg $/1M = settled cost ÷ total tokens. The usage CSV's
input_tokensalready includes cached prompt tokens, and the settled cost prices them at the cache-read rate. - Offer $/1M = each seller's per-token rates weighted by your real input/output mix, adjusted for your cache-hit fraction (capped at 95%). This is closer to what you'd actually pay than the sticker rate card.
- Session totals (cost / tokens) come from opencode's authoritative session record and include cached tokens, so they match the Context panel.
The TUI plugin accepts options via the tui.json tuple form:
{
"plugin": [["./surplus-sidebar.tsx", {
"enabled": true,
"offers": { "filterByPin": false },
"spinner": { "theme": "tech", "intervalMs": 100, "cells": 6 },
"tiers": {
"tps": { "slow": 20, "normal": 50, "fast": 100 },
"ttft": { "fast": 10000, "ok": 20000 }
}
}]]
}enabled: falsedisables the whole TUI integration (meter, sidebar blocks, commands) without uninstalling — restart to apply./surplus-tuidisables it for the current session.offers.filterByPin: truerestricts the sidebar and/surplus-offersto offers matching the model's routing pin (host URL or provider family).- Spinner themes:
tech(cyan/blue/purple),red, or"theme": "none"to disable. Customcolorsarray also works. tiers.tps/tiers.ttfttune the color thresholds for the speed meter.
The server plugin's routing pins (for GLM models → the Z.ai coding host) are in BASE_PINS at the top of surplus-control.js; edit and reinstall to change them.
The catalog is scraped from the Surplus inference endpoint (https://www.surplusintelligence.ai/api/inference/v1/models) in the background at startup, then cached to ~/.cache/opencode/models-surplus.json and merged into the single surplus provider before opencode resolves providers (two-phase: sync cache load, async refresh). Only models with text output are kept — the catalog's image/video/audio-only models are excluded. To scrape manually (e.g. for CI or first setup):
node sync-models.mjs # discover + cache only
node sync-models.mjs --write-config # also write models into provider "surplus" in opencode.json
# (models already in a surplus-NN tier provider are skipped — no duplicates)
node sync-models.mjs --pricing # seed/refresh the pricing cache (surplus-pricing.json) for session cost
node sync-models.mjs --query gpt # list matching models with live discountsWhen a request fails with minimum_discount_not_met, the floor proxy annotates the error so the plugin reliably detects it:
- Default (flag off): the failure is surfaced normally — no prompt, no automatic floor change.
- Flag on (
/surplus-flag yes): the plugin immediately raises a dialog to adjust the discount tier — it recommends the strictest tier below the current floor that live offers pass, plus the other tiers and off. Picking one persists the floor and re-submits your last message through the new floor.
State lives in surplus-control.json (_flags.backoffPrompt, _backoffPrompt); surplus_backoff reports/clears pending prompts. An automatic retry loop (re-driving a failed request with backoff) is planned future work.
See BENCHMARKS.md for the design of a benchmark-vs-market comparison tool (surplus_benchmarks / /surplus-benchmarks) that recommends models and estimates daily/project spend. Not implemented yet.
- Sidebar surplus rows don't appear → no Surplus provider/model is active in this session, or the API key isn't in
auth.jsonunder asurplus*ID. /surplus-*commands missing → opencode wasn't restarted after install, the plugin isn't intui.json, orenabledis false.- Floor errors (
minimum_discount_not_met) → the/min{N}floor is stricter than any current offer. With/surplus-flag yesa dialog immediately offers a recommended tier to adjust to; otherwise the request just fails (no auto-change). You can always lower the floor manually with/surplus-tier. - Model picker shows one "Surplus Intelligence" group with each model once — the discovered catalog lives in the single
surplusprovider. Models you've explicitly configured in asurplus-70/80/90/95tier provider appear there too (once per tier — that's the floor shortcut); catalog models are skipped from the catalog if they already exist in a tier provider. - Floor changes don't apply → floors apply via the local floor proxy, which reads the model's floor from
surplus-control.jsonon every request. If you edited the file manually or the proxy isn't running (check the port in the plugin log), restart opencode. - Model discovery fails /
/surplus-modelsis empty → runnode sync-models.mjs. If it reports no models, the Surplus edge's Brotli compression can trip Node's undici; all plugin API calls sendAccept-Encoding: identityto work around it.
MIT — see LICENSE.
{ "provider": { "surplus": { "name": "Surplus Intelligence", "npm": "@ai-sdk/openai-compatible", "options": {}, "models": { "deepseek-v4-flash": { "name": "DeepSeek V4 Flash" } } } } }