[v2] Plugin API: no way to read token usage in 2.0.x — DCP-style context-pruning plugins are dead; please re-expose it
On opencode 2.0.16 (verified on the binary bundled in the OpenChamber 2.0.16 AppImage, opencode serve), the plugin API no longer exposes any token-usage data, while the HTTP API still does. This makes token-aware plugins — most notably @tarquinen/opencode-dcp (dynamic context pruning: nudges + auto-compression at context-limit thresholds) — non-functional: its token counter reads ~0, so isContextOverLimits() never goes true and no nudge or auto-compression ever triggers at any context % (even past 100k tokens). Tracked upstream at Tarquinen/opencode-dynamic-context-pruning#618 (comment 2026-09-25).
What the 2.0.16 plugin API exposes (probe-verified)
A probe plugin ({id, setup} export) inspecting the API surface found:
| API |
2.0.16 shape |
token data? |
ctx.session.context({sessionID}) |
flat minimal entries: id/time/type/text (+description/outcome); observed types user, idle, synthetic |
❌ no assistant entries at all, no tokens, no content |
native event.messages in the session.context hook |
id/role/content only |
❌ no tokens/usage fields |
ctx.session.* methods |
hook, create, get, switchAgent, switchModel, prompt, generate, command, synthetic, interrupt, update, move, wait, context |
no messages() method |
chat.message / chat.transform hooks |
absent from the 2.0.16 binary |
— |
For comparison, on 2.0.12 session.context() returned rich entries (assistant entries with content + tokens), which is the shape DCP 3.2.0 was built against (peer dep @opencode-ai/plugin >=1.18.29).
The data still exists — HTTP API asymmetry
On the same 2.0.16 server:
GET /api/session/:id/message returns assistant entries with content and tokens: {input, output, reasoning, cache:{read, write}} (e.g. a real turn: {"input":611,"output":6275,"cache":{"read":110651}});
GET /api/session/:id returns session-level aggregate tokens.
So usage is fully tracked server-side; only the plugin surface lost access to it.
What breaks (concrete)
- DCP:
getCurrentTokenUsage() sums msg.info.tokens over the projected view → all zero → contextLimitAnchors/turnNudgeAnchors/iterationNudgeAnchors in storage/plugin/dcp/<session>.json stay empty forever; manual /dcp-compress still works (different code path).
- Any other plugin doing token budgeting, context-% gating, or usage-based routing (e.g. model escalation at N tokens).
- Plugins can currently only estimate usage (local tokenizer over
event.messages content), which is workable but inaccurate (no cache accounting, no reasoning tokens) and expensive to recompute per hook.
Ask
Re-expose token usage to the plugin API. Ranked options:
ctx.session.messages({sessionID}) returning the same per-message shape as GET /api/session/:id/message (assistant entries with content + tokens) — restores the 2.0.12 contract DCP and other plugins were built against.
- Richer
ctx.session.context() — include assistant entries with tokens (and ideally content) alongside the current user/idle/synthetic entries.
- Session aggregate only — expose the
GET /api/session/:id aggregate tokens through the plugin API (e.g. on ctx.session.get({sessionID})); enough for context-limit nudging (what DCP's nudge layer needs), though per-message usage is still needed for fine-grained pruning decisions.
- Or simply add
tokens/usage to the native event.messages objects in the session.context hook.
Any of these un-breaks the plugin; option 1 (or 2) is the drop-in fix.
Minimal repro
export default {
id: "token-probe",
setup: (ctx) => {
ctx.session.hook("context", async (event) => {
const entries = await ctx.session.context({ sessionID: event.sessionID })
console.log(
"entry types:", [...new Set(entries.map((e) => e.type))],
"entry keys:", Object.keys(entries[0] ?? {}),
"native msg keys:", Object.keys(event.messages[0] ?? {}),
"session methods:", Object.keys(ctx.session),
)
// 2.0.16: types=[user,idle,synthetic], keys=[id,time,type,text],
// native keys=[id,role,content] — no tokens anywhere
})
},
}
Run against any session with assistant turns on 2.0.16 (opencode serve, auth opencode:$PW), and compare GET /api/session/:id + .../message from the same server.
References
Verification
System
- OS: Garuda Linux (Arch-based), x86_64
- opencode: 2.0.16 (OpenChamber AppImage bundled binary,
opencode serve)
- Provider: @opencode/ai/providers/openai-compatible -> llama-swap / llama-server v1 (http://192.168.2.215:11436/v1)
[v2] Plugin API: no way to read token usage in 2.0.x — DCP-style context-pruning plugins are dead; please re-expose it
On opencode 2.0.16 (verified on the binary bundled in the OpenChamber 2.0.16 AppImage,
opencode serve), the plugin API no longer exposes any token-usage data, while the HTTP API still does. This makes token-aware plugins — most notably @tarquinen/opencode-dcp (dynamic context pruning: nudges + auto-compression at context-limit thresholds) — non-functional: its token counter reads ~0, soisContextOverLimits()never goes true and no nudge or auto-compression ever triggers at any context % (even past 100k tokens). Tracked upstream at Tarquinen/opencode-dynamic-context-pruning#618 (comment 2026-09-25).What the 2.0.16 plugin API exposes (probe-verified)
A probe plugin (
{id, setup}export) inspecting the API surface found:ctx.session.context({sessionID})id/time/type/text(+description/outcome); observed typesuser,idle,syntheticassistantentries at all, notokens, nocontentevent.messagesin thesession.contexthookid/role/contentonlytokens/usagefieldsctx.session.*methodshook, create, get, switchAgent, switchModel, prompt, generate, command, synthetic, interrupt, update, move, wait, contextmessages()methodchat.message/chat.transformhooksFor comparison, on 2.0.12
session.context()returned rich entries (assistant entries withcontent+tokens), which is the shape DCP 3.2.0 was built against (peer dep@opencode-ai/plugin >=1.18.29).The data still exists — HTTP API asymmetry
On the same 2.0.16 server:
GET /api/session/:id/messagereturns assistant entries withcontentandtokens: {input, output, reasoning, cache:{read, write}}(e.g. a real turn:{"input":611,"output":6275,"cache":{"read":110651}});GET /api/session/:idreturns session-level aggregatetokens.So usage is fully tracked server-side; only the plugin surface lost access to it.
What breaks (concrete)
getCurrentTokenUsage()sumsmsg.info.tokensover the projected view → all zero →contextLimitAnchors/turnNudgeAnchors/iterationNudgeAnchorsinstorage/plugin/dcp/<session>.jsonstay empty forever; manual/dcp-compressstill works (different code path).event.messagescontent), which is workable but inaccurate (no cache accounting, no reasoning tokens) and expensive to recompute per hook.Ask
Re-expose token usage to the plugin API. Ranked options:
ctx.session.messages({sessionID})returning the same per-message shape asGET /api/session/:id/message(assistant entries withcontent+tokens) — restores the 2.0.12 contract DCP and other plugins were built against.ctx.session.context()— include assistant entries withtokens(and ideallycontent) alongside the currentuser/idle/syntheticentries.GET /api/session/:idaggregatetokensthrough the plugin API (e.g. onctx.session.get({sessionID})); enough for context-limit nudging (what DCP's nudge layer needs), though per-message usage is still needed for fine-grained pruning decisions.tokens/usageto the nativeevent.messagesobjects in thesession.contexthook.Any of these un-breaks the plugin; option 1 (or 2) is the drop-in fix.
Minimal repro
Run against any session with assistant turns on 2.0.16 (
opencode serve, authopencode:$PW), and compareGET /api/session/:id+.../messagefrom the same server.References
executeremoval), filed the same day for contextlib/v2/messages.ts(project()),lib/token-utils.ts(getCurrentTokenUsage)Verification
This request is distinct: opencode 2.0.x REMOVED token data from the plugin API —
session.context()now returns minimal entries (id/time/type/text; no assistant entries, no tokens) and there is nosession.messages()— so existing plugins built against the 2.0.12-era API (DCP 3.2.0) are non-functional.System
opencode serve)