Skip to content

Routing, killswitch, menus, and pacing: one shared implementation and the same behavior in each auth plugin #10

Open

Description

@iceteaSA

Summary

Move all routing, killswitch, menu, and pacing logic into common-auth, and give each auth plugin the same routing modes and the same routing behavior. The sidebar must show the shared behavior without more work in each plugin. Each shared part must be a template that a plugin can change for a provider quirk, not a rule that it cannot change.

Current state

The default branches at these commits: common-auth 343c381, anthropic-auth 937b27b, openai-auth 5da845f, antigravity-auth 4533e06.

Part common-auth openai-auth anthropic-auth antigravity-auth
Routing modes (./routing: ordered, sticky-balanced, placements) Yes Uses it Own code Own code
Killswitch floors An input to ./routing. Floors are in the Limits section of ./commands. Uses it Own code Own code
Command menu (./commands) Yes Uses it Uses it Uses it
Auth login menu (./auth-menu) Yes Uses it No login menu Own code
Pacing No Own code Own code Own code
Reserve for each window reservePercent, for sticky-balanced only Uses it Own code Own code

The library has most of the routing and menu parts at this time. Two plugins do not use the routing part, and pacing is in each plugin.

Requested

  1. Routing and killswitch: anthropic-auth and antigravity-auth use ./routing for mode resolution, admission, ordered and sticky-balanced routing, and killswitch verdicts. Each plugin keeps its provider policy in its adapter. The section "Native policy that stays in the adapter" in docs/adoption-inventory.md gives the policy that stays.
  2. The same routing functions: each plugin gives the same routing.mode values and the same behavior for each value. The sidebar reads the mode, the order, and the killswitch state from shared code. Then a change in the library shows in each sidebar.
  3. Menus and options: in each plugin, the auth menus and the option sections (Accounts, Quota, Routing, Limits) come from ./auth-menu and ./commands. Provider-specific sections and menu items stay in the plugin.
  4. Pacing: one shared pacing function in common-auth, for example in ./quota or in a new ./pacing. It reads no clock. The time is a parameter. Each plugin and each sidebar uses it.
  5. Flexibility: each shared part has options or hooks for provider quirks. Examples are a status classifier, the requiredLabels list for admission, provider-specific window groups, and more menu sections. The shared behavior is the default. A plugin can replace one part and keep the other parts.

Follow-up: reserve mode

This is a different request, for after the parity work. It uses the shared pacing function from item 4.

  • The reserve works on pacing, not on a constant percent. For each window, the pace is the share of the window time that is complete. An account that uses its quota faster than its pace uses its reserve.
  • Routing uses accounts that are at or below their pace first.
  • When the account in use gets above its pace, the routing mode selects the next step:
    • Continue on the same account, above its pace, down to the killswitch floor. Then move to the next account.
    • Move to a different account that is at or below its pace. Use accounts that are above their pace only when no account is at or below its pace.
  • A margin for each window is an option: the percent above the pace that does not count as reserve use. The plugin sets the default.
  • Thus the reserve gets smaller near the end of the window. At the end of the window, the pace lets the account use almost all of the window, and only the killswitch floor stays.
  • The sidebar shows when an account is above its pace and uses its reserve.

The reservePercent input of routeSticky is a constant percent. This request adds a reserve that follows the pace, as a replacement or as a second type.

Activity

  1. iceteaSA commented on Oct 29, 2026

    @iceteaSA
    Author

    The body changed: the reserve mode in the follow-up section works on pacing, not on a constant percent.

    • For each window, the pace is the share of the window time that is complete.
    • An account that uses its quota faster than its pace uses its reserve.
    • Routing uses accounts that are at or below their pace first.
    • When the account in use gets above its pace, the routing mode selects one of two steps. It continues on that account down to the killswitch floor, or it moves to an account that is at or below its pace.
    • A margin for each window sets how far above the pace counts as no reserve use. The plugin sets the default.

    Thus the reserve mode uses the shared pacing function in item 4.

  2. oaiauth-alfonso commented on Nov 2, 2026

    @oaiauth-alfonso

    Thanks for laying this out so clearly. We're doing it.

    The plan moves routing modes, admission and killswitch floors, the wait-or-switch decision after a limit, shared pacing and the menu sections into common-auth, for all four auth plugins (openai-auth, anthropic-auth, antigravity-auth and claustrum-auth). Provider quirks stay in each plugin through hooks, as you suggested. The first step is a shared table of expected routing decisions that each plugin's adoption must pass, so behaviour that differs today is decided once instead of drifting.

    The pace-based reserve in your follow-up is part of the plan, built on the shared pacing function.

    We'll link the work here as it lands.

  3. iceteaSA commented on Nov 3, 2026

    @iceteaSA
    Author

    Desktop GUI plugins in OpenCode 2: how the shared quota view and menus could reach the desktop app

    OpenCode 2.2.0 added experimental desktop GUI plugins (docs). A plugin package adds a ./gui-experimental export beside . and ./tui. The export is one prebuilt CommonJS file, built with the Vite preset from @opencode/plugin/gui/vite. The desktop app loads it for each plugin in opencode.json(c), with no separate install step. This could carry the shared pieces of this issue into the desktop app as well as the TUI.

    What the GUI API offers for an auth plugin

    • Status. There is no sidebar like the TUI one, but there are three small surfaces: a Slot at session.header, a TitlebarItem pill, and a ComposerPill whose rows open a menu. Each can show the serving account and the remaining window. A side Panel tab can hold the full view, with the same sections as quota-sidebar.
    • Menus. A SettingsPage (its own page, or a section on the General page) can hold routing mode, account order, killswitch floors and pacing margins. Command entries (palette, keybind, optional / command) can run actions such as "switch account" or "reorder accounts".
    • Data. A window calls the plugin's server part through ctx.client.rpc(...). So the GUI reads the state that the server part already computes, and the window needs no credential access of its own.

    A possible shape for common-auth

    1. Keep the menu and quota model independent of the renderer: sections, rows, values, thresholds and actions as data. Then quota-sidebar (OpenTUI) and a new GUI renderer (Solid) are two thin views over one model, and the shared table of expected routing decisions stays the one source of behaviour.
    2. Ship the GUI side as source that each plugin bundles, for example @cortexkit/common-auth/gui with one helper that the plugin's GUI setup calls to add the status pill or slot, the panel tab and the settings page. The Vite preset bundles every dependency except the modules the app provides (effect, solid-js, @opencode/plugin/gui, @opencode/client and a few more). So the shared GUI code must import those from the app and must not bring its own copy.
    3. One combined view for all four plugins (like the sections of the TUI sidebar) needs one owner, because GUI contracts and registries must use the owner's plugin id as a prefix. The simplest form: each plugin shows its own pill and settings page. The combined form: one owner defines a Registry (for example <owner>.quota), the other plugins add their rows with ctx.add, and the owner renders one panel. A GUI-only package without a server part is also a valid plugin (its id is the package name), if a neutral owner is better than one of the four.
    4. Server side: one RPC per plugin for the GUI, to read a quota and route snapshot and to write settings (order, mode, floors). The TUI menus could use the same RPC.

    Limits to know

    • The API is experimental and can change between releases.
    • The GUI file must be one CommonJS file; dynamic imports are inlined.
    • A plugin whose server part fails to load has no GUI.
    • For an installed package, the GUI changes only with the package version (opencode plugin update).
    • The desktop app reads plugins only from opencode.json(c), not from cli.json.
    • Settings › Extensions › Plugins can turn a plugin's GUI off.
    • Ids in the opencode namespace are reserved.

    If it helps, I can prototype a small ./gui-experimental for one auth plugin (a status pill and a settings page over one RPC snapshot) to test this shape before it goes into common-auth.

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

Metadata

Metadata

Assignees

No one assigned

    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