Skip to content

feat(tui): migrate quota sidebar and commands to TUI V2 - #196

Merged
slkiser merged 108 commits into
slkiser:mainfrom
dmmop:main
Sep 29, 2026
Merged

slkiser merged 108 commits into
slkiser:mainfrom
dmmop:main

Conversation

@dmmop

@dmmop dmmop commented Aug 2, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Migrate the TUI integration to the OpenCode V2 plugin API.
  • Add a quota sidebar with collapsible provider details and active-provider count.
  • Register local /quota and token-usage slash commands.
  • BREAKING CHANGE: OpenCode's SQLite credential database replaced auth.json as credential source.

Testing

  • pnpm run typecheck
  • pnpm exec vitest run tests/tui-v2-commands.test.ts
  • git diff --check 87e6540...HEAD
  • manually verified /quota in the OpenCode V2 TUI

@slkiser

slkiser commented Aug 2, 2026

Copy link
Copy Markdown
Owner

@dmmop Thanks for the PR, code looks good.

I believe OpenCode v2 is still in beta, so I'd hold off on releasing this on main for now.

If there is support for v2 or when v2 is out of beta I'll definitely return to the PR.

@dmmop

dmmop commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

@slkiser Thanks you for this plugin!

Yes, v2 is still in beta, thats why "opencode-next.db" is hardcoded in the code.

I adapted the plugin to suit my needs in v2, i'll keep this PR update if they introduce any breaking change.

@dmmop
dmmop force-pushed the main branch 3 times, most recently from 585fc6f to 4d36e34 Compare August 5, 2026 14:29
@rektide

rektide commented Aug 11, 2026

Copy link
Copy Markdown

this has been great to have, thank you!

@ransagy ransagy mentioned this pull request Aug 20, 2026
2 tasks done
@ransagy

ransagy commented Aug 20, 2026 •

Copy link
Copy Markdown
Contributor

Tested locally on Windows with opencode2 (beta-17728), at head 2143358, loaded as a TUI plugin via cli.json. The sidebar and the /quota + /tokens_* commands work. A few things I ran into, possibly expected/how it works now but not to my untrained TUI eyes:

  1. To test the built artifact I had to manually compile tui-v2.tsx → tui-v2.js first — the build emits tui-v2.jsx (jsx: preserve) while dist/index.js imports ./tui-v2.js, so the packaged output can't resolve it. (Related: tests/tui-v2-commands.test.ts also doesn't run on a clean checkout — vite chokes on the raw JSX — so I ran it against the compiled version.). Maybe something the release/CI usually does, not sure.

  2. Two of the new toast triggers don't fire in the V2 event schema:

    • session.compacted doesn't exist in V2 (compaction events are session.compaction.*).
    • session.tool.success has no tool field on data (neither does session.tool.called); only session.tool.input.started carries data.name, so the data?.tool === "question" check never matches. Probably opencode changing the schema as they go along, there were a few beta releases this week already.

One more thing — right now this won't load on V1 anymore, because index.ts no longer exports the V1 { id, server } module. Probably intentional. but worth checking if you intend to support both.

Let me know if I can help test anything further :)

@dmmop

dmmop commented Aug 21, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough report and for testing it, @ransagy! The packaging, clean-checkout test, and V2 event trigger issues are now fixed.

As for V1 support, I wasn’t planning to support both versions. I adapted the project to my own needs and shared it in case it was useful to @slkiser.

dmmop added 11 commits August 21, 2026 21:15
- Extend `src/lib/opencode-auth.ts` with a credential database reader
  - Add `CredentialDatabase` and `CredentialDatabaseConstructor` types
  - Add `runtimeRequire` to conditionally load `better-sqlite3`
  - Implement `getCredentialDbPaths`, `readCredentialDatabase`, `openCredentialDatabase`, and `parseCredentialValue`
- Modify `readAuthFile` and add `readAuthFiles` + `readCredentialDatabases` to merge credentials from auth.json and OpenCode's database
- Preserve precedence: entries in auth.json override database credentials; database serves as a fallback for OAuth credentials
- Add tests covering database fallback and auth.json precedence in `tests/lib.opencode-auth.test.ts`
- src/lib/opencode-auth.ts: update `getCredentialDbPaths` to compute credential database paths from a single base path and return them in a consistent format.
- tests/lib.opencode-auth.test.ts: update the `createCredentialDatabase` helper to construct expected paths using the same base-path logic.

Why: path construction was duplicated across callers, which made it easy for the test helper to drift from production behavior. Centralizing the path calculation keeps runtime and tests aligned on where credential databases are located.
- package.json: update @opentui/core and @opentui/solid from ^0.4.3 to ^0.5.3
- pnpm-lock.yaml: refresh lockfile for updated dependency resolution
- src/tui-v2.tsx: update `TuiContext` and `plugin::setup` to align with the @OpenTui 0.5 API; `trigger` helper has a cosmetic-only change
- tests/tui-v2-commands.test.ts: update "registers every quota command as a local slash command" test to cover the updated setup behavior
Compile the V2 TUI entry to the filename imported by the package and verify the clean distribution can be loaded. Use the current compaction and tool lifecycle events so automatic quota notifications run on OpenCode V2.
dmmop added 2 commits August 23, 2026 17:03
Merge upstream changes through 5158aa3 while preserving the fork's opencode.db credential source and adopting the new OpenCode Go config precedence and responsible update workflow.
Read every native credential row and report quota independently for each connection. Preserve provider plans, aliases, active markers, partial errors, and per-connection accounting across supported providers.
@gdevenyi

Copy link
Copy Markdown

Confirmed on a newer beta build. Additional diagnostics for the V2 port (cc #229).

Environment

  • OpenCode 2 TUI and server: opencode2 v0.0.0-beta-18314 (beta dist-tag)
  • opencode-quota: 4.8.2 (current latest, installed as @slkiser/opencode-quota@latest)

Observed failure

  • TUI /plugins report: @slkiser/opencode-quota@latest → status failed, runtime tui, error Invalid V2 TUI plugin module: @slkiser/opencode-quota@latest.
  • Server side (~/.local/share/opencode/log/opencode.log, role=server), from the same plugin listed in the V1 plugin array of global opencode.json:
level=WARN message="failed to load plugin" target=@slkiser/opencode-quota@latest cause="Cause([Fail(SchemaError(Missing key
  at [\"default\"][\"effect\"]
Missing key
  at [\"default\"][\"setup\"]))])"

Both entry points reject the module. The server loader expects effect and setup on the default export. The TUI loader expects id plus setup.

Root cause in the 4.8.2 dist

  • dist/index.js default export: { id: "@slkiser/opencode-quota", server: QuotaToastPlugin }. This is the V1 shape.
  • dist/tui.js default export: { id, tui }. This is the V1 TuiPluginModule shape from @opencode-ai/plugin/tui 1.x.
  • TUI loader validation in this beta build, extracted from the opencode2 binary:
// Module is valid only when the default export has id: string (non-empty) and setup: function
(mod) =>
  typeof mod === "object" && mod !== null &&
  "id" in mod && typeof mod.id === "string" && mod.id.length > 0 &&
  "setup" in mod && typeof mod.setup === "function"
// On failure it throws: `Invalid V2 TUI plugin module: ${spec}`
  • Build dependency is @opencode-ai/plugin 1.18.11 (peer ^1.4.3). The published dist therefore still compiles against the V1 API. The V2 API is published under the beta dist-tag of @opencode-ai/plugin (0.0.0-beta-18314 today), with Plugin.define available from @opencode-ai/plugin/tui.

Target shape (per current V2 plugin docs)

import { Plugin } from "@opencode-ai/plugin/tui"

export default Plugin.define({
  id: "@slkiser/opencode-quota",
  async setup(context) {
    // context.ui.slot(...), context.keymap.layer(...), context.data.*, context.ui.toast.show(...)
    return () => {
      /* cleanup */
    }
  },
})

Also required:

  • Main server plugin sets tui: true, so the ./tui export loads automatically.
  • package.json uses "@opencode-ai/plugin": "beta" and peer deps @opentui/core, @opentui/solid, solid-js.

Notes for the install and setup path

  • V2 still reads the V1 plugin array in opencode.json (compatibility path), then fails module validation as shown above.
  • V1 layered tui.json no longer applies. V2 uses one global cli.json with a plugins array. A migrated setup carries the spec into cli.json plugins; this is what triggers the TUI-side error above.

I can test a prerelease build against beta-18314 if that helps.

@christian-taillon

christian-taillon commented Aug 27, 2026 •

Copy link
Copy Markdown
Contributor

I've been testing this against opencode2 v0.0.0-beta-18314 and built a follow-on from the current #196 head.

I now have the V2 TUI working on the current { id, setup } loader, including the sidebar and /quota, and added support for multiple native OpenAI connections. I've tested two simultaneous OAuth accounts (Plus + Business); they remain separate and each exposes its Five-hour and Weekly quota windows.

image

I also fixed two issues encountered while testing:

  • tuiSidebarPanel.formatStyle: "allWindows" was being applied after single-window projection, so the sidebar lost the additional quota windows.
  • CLI status could OOM on a large OpenCode database because usage aggregation materialized full message.data JSON payloads. It now queries only the scalar fields required for usage accounting.

Validated on beta-18314:

  • OpenCode sidebar: Plus + Business, Five-hour + Weekly
  • /quota and /quota_status
  • /tokens_all
  • packed CLI show and status
  • typecheck/build
  • full suite: 2,218 tests passing

Follow-on branch: https://github.com/christian-taillon/opencode-quota/tree/v2-native-multi-connection-quota
Commit: 046b4d25a3418c21a0bf6278b32faa196b1cd8d4

This is based on #196 rather than upstream main, so I don't want to create a competing V2 PR. Happy to open a follow-up PR against dmmop/opencode-quota-v2:main if that would be useful.

@dmmop

dmmop commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

This is based on #196 rather than upstream main, so I don't want to create a competing V2 PR. Happy to open a follow-up PR against dmmop/opencode-quota-v2:main if that would be useful.

Thanks, that would be very useful!

I’ve just pushed my own implementation of native multi-connection quota support. It covers the credential-backed providers rather than only OpenAI, and I’m currently testing it.

After reviewing your changes, I noticed that your branch also fixes two separate issues that my implementation does not currently cover:

  • The sidebar-specific allWindows format needs to be applied before quota projection; otherwise a global singleWindow setting discards the additional windows too early.
  • CLI status can consume excessive memory on large OpenCode databases because it materializes the complete message.data payload instead of selecting only the scalar fields required for usage accounting.

I’d be very happy for you to open a follow-up PR against dmmop/opencode-quota-v2:main. If possible, please rebase it onto the latest main and keep the non-overlapping fixes isolated so they are easier to review. I’ll gladly review and merge it.

I’d also appreciate it if you could review my latest push and share any feedback on the multi-provider implementation.

dmmop added 2 commits August 27, 2026 18:46
Use the plan returned by GitHub for user-scoped Copilot quota presentation instead of inferring a personal license from the billing mode. Preserve the user billing scope in diagnostics.
Add the conventional local TUI entrypoint and align OpenTUI with the beta-19151 runtime without changing server or TUI behavior.
@rektide

rektide commented Sep 5, 2026 •

Copy link
Copy Markdown

i've been integrating both dmmop-v2 and upstream for a bit now into my own copy, to run in v2. i humbly submit my own fork that i intend to keep juggling for the foreseeable. it's definitely not as well maintained, caveat emptor, but also, i am excited to share and expect to keep updating for the foreseeable. https://github.com/rektide/opencode-quota-plus/

i also have some features like pro rata #252 that are very helpful to me, and multiple different view formats, which i haven't really been using heavily tbh. i'm making stronger use of the opencode v2 view slots that opencode has been evolving. i used to juggle v1+v2 support but dropped that (v2 is so much better, switch switch switch!).

this plugin is the #1 must have opencode addition to me, and i'm so thankful for all the work folks have put in, and look forward to ongoingly contributing upstream here too. 🙇

Absorb 13 upstream commits (credential-scoped quota cache, cache security review, Windows portability) while preserving fork changes: multi-connection fan-out, opencode.db credential source and root tui.ts entrypoint. Adopt upstream removal of account_email/account_id from OpenAI status details. Adapt the new cache-isolation test mock to the opencode.db field contract.
slkiser and others added 9 commits September 28, 2026 01:34
The toast formatter call dropped resetTimeSpaced (and percentLabelStyle and
accountingDetail), so toasts showed dense countdowns like 3d0h35m while the
sidebar, footers, /quota and the CLI showed 3d 0h 35m. Pass the same
settings the 4.x toast passed. A cross-surface test checks toast, sidebar,
Home footer and prompt bar for both spaced and dense settings.
An OpenCode Console org without a monthly budget returns null from
budgets/org (issue slkiser#216), which showed the error row "Could not parse
OpenCode Console budgets/org response". A null body, an empty body, or {}
now means no org budget, so Zen falls back to billing/account plus
usage/cost-by-day without an error. Other malformed bodies stay errors.

Zen error rows now read "OpenCode Zen:" instead of "OpenCode:"; Go
errors already read "OpenCode Go:".
The sidebar renders each line with wrapMode="none" so bars stay intact,
which cut long error rows at the panel edge ("OpenCode: Could not parse
OpenCode Co"). The formatter already fits every bar and value row to the
36-column sidebar, so buildSidebarQuotaPanelLines now word-wraps only the
lines longer than that (errors and notices). Bars and aligned rows are
unchanged. Toasts already word-wrap in OpenCode, and the compact footers
are single-line by design.
show and status read OpenCode's database through node:sqlite, and Node 22+
printed "ExperimentalWarning: SQLite is an experimental feature and might
change at any time" on every run. The CLI entry now filters process
.emitWarning for that one ExperimentalWarning before any command loads
node:sqlite. Every other warning still prints.
Add a flat-square badge to the top badge row that links to
anomalyco/opencode#38281, and remove the "Like this plugin?" tip it
replaces. The in-app opencode-ecosystem-listing-thumbs-up announcement
stays.
… session

In "dialog" mode (the default), Enter on a prompt that holds exactly a quota
slash command (for example `/quota` or `/tokens_between 2026-09-01 2026-09-25`)
now clears the prompt and runs the command over the quota RPC, before
OpenCode submits it. The report opens only in the popup: it no longer shows
in the chat for a moment, and on Home no new session is created.

A keymap layer binds Enter at priority 1, above OpenCode's prompt, with no
mode, so it is off while the "/" list or a dialog is open. Other text, and
every command in "inline" mode, goes on to OpenCode's submit as before.
tuiCommandDisplay is read at start and again after each typed quota command.
The "/" list still shows only the server commands, and Web and Desktop are
unchanged.

Closes slkiser#296
OpenCode 2 mounts the TUI "app" slot again when it reconnects to its
server. That happens when the server restarts, and it can also happen
right after a cold start. Leaving a mount drops the keymap layers and
event listeners registered in it, but the plugin skipped registering
them again on the next mount. After such a remount, a typed /quota was
no longer caught on Enter, and the listener that cancels a posted report
was gone too. So OpenCode created a new session and the full report
stayed in the chat, with no popup. The palette entries and quota toasts
were also lost.

Each mount of the app slot now disposes the previous listeners and
registers the commands, the Enter binding, and the listeners again.

While tuiCommandDisplay is still being read, a typed quota command now
follows the default, "dialog", and opens the popup. Users with "inline"
may see one popup if they type a quota command in the first moments
after OpenCode starts.

Reports for a session created from Home already work: OpenCode opens
the new session before the server runs the command, so the report
arrives for the session on screen. A test now covers that case.
Brings in the 3 main commits v5 lacked:
- 4394e14 fix(opencode-zen): empty Console org budget means no budget
- 1b6429f docs(readme): OpenCode listing badge
- c5a7ae7 chore(release): sync package version to 4.10.6

Resolutions:
- package.json: kept v5 version 5.0.0 (main had 4.10.6).
- src/providers/opencode-zen.ts: kept v5. v5's 5c604b6 already has the
  same empty-budget handling and "OpenCode Zen:" error label.
- tests/providers.opencode-zen.test.ts, tests/quota-render-data.test.ts:
  kept v5.
- tests/lib.opencode-zen.test.ts: kept v5. The clean auto-merge would have
  added a second copy of the empty org budget test v5 already has.
- src/lib/opencode-zen.ts: auto-merged to v5's version unchanged.
- README.md: auto-merged to v5's version unchanged; exactly one listing
  badge.
- tests/lib.quota-status.test.ts: took main's one-line fixture relabel
  ("OpenCode" -> "OpenCode Zen") so the Zen live-probe fixture matches the
  label v5 already emits.
@slkiser

slkiser commented Sep 28, 2026

Copy link
Copy Markdown
Owner

v5.0.0-beta.1 is out on the npm next tag. To try it with OpenCode 2, use "plugin": ["@slkiser/opencode-quota@next"] (or npx @slkiser/opencode-quota@next init). If something breaks, please open a new issue with the bug form and mention 5.0.0-beta.1, rather than commenting here. This PR will be merged when 5.0.0 ships. Thanks again @dmmop and @ransagy!

@rogierlommers

Copy link
Copy Markdown

v5.0.0

Any idea when it officially will be released? Thanks for this by the way!

@slkiser

slkiser commented Sep 29, 2026

Copy link
Copy Markdown
Owner

v5.0.0

Any idea when it officially will be released? Thanks for this by the way!

Probably today! I've been using it extensively yesterday and trying to see any bugs.

Every quota popup showed its title twice: the dialog title, then the
report's own title line, e.g. "Tokens used (Last 7 Days)" and then
"Tokens used (Last 7 Days) (/tokens_weekly) 16:00 29/09/2026".

A report heading now has two fields: `line`, the first line of the text
report, and `subtitle`, what the dialog shows in one muted line under its
title instead: the facts in the line that the dialog title lacks, and the
time. For example "opencode-quota v5.0.0 · 16:00 29/09/2026" for
/quota_status, the date range for /tokens_between, "Percent used" for
/quota with bare percent labels, or just the time. The dialog no longer
draws the heading line, so the command echo is gone from the popup too.
/quota and the announcements list now keep their first line in the
heading instead of in a section.

Chat, Web, and CLI text is unchanged: it still starts with `line`.
5.0.0 ships as the stable release, so the plain install command gets 5.x and the @next instructions no longer apply.
@slkiser
slkiser merged commit e966c9c into slkiser:main Sep 29, 2026
4 checks passed
@slkiser

slkiser commented Sep 30, 2026

Copy link
Copy Markdown
Owner

5.0.0 is out on npm latest (@rogierlommers). Update with npx @slkiser/opencode-quota@latest update. Thanks again @dmmop and @ransagy!

@rogierlommers

Copy link
Copy Markdown

Tghanks @slkiser , I do have one (related?) question: I see this: "quota details unavailable". I'm out of tokens, but I would expect to see that right?

Screenshot 2026-09-30 at 10 42 35

@slkiser

slkiser commented Sep 30, 2026

Copy link
Copy Markdown
Owner

Tghanks @slkiser , I do have one (related?) question: I see this: "quota details unavailable". I'm out of tokens, but I would expect to see that right?

Screenshot 2026-09-30 at 10 42 35

@rogierlommers Don't know your setup, but it could be a regression. Please open up a bug report and dump /quota_status

@rogierlommers

rogierlommers commented Sep 30, 2026 •

Copy link
Copy Markdown

After increasing quota, everything works now. I guess the plugin needs to (cache?) data and to get that data it needs tokens :) .

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

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.