Skip to content

[FEATURE]: [v2] Plugin API: no way to read token usage in 2.0.x — DCP-style context-pruning plugins are dead; please re-expose it #51265

Description

@famewolf

[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:

  1. 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.
  2. Richer ctx.session.context() — include assistant entries with tokens (and ideally content) alongside the current user/idle/synthetic entries.
  3. 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.
  4. 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)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions