Skip to content

feat(quota): register as a native quota provider without requiring v8 - #12

Open
zenyr wants to merge 3 commits into
massiveits:mainfrom
zenyr:feat/native-quota-provider
Open

zenyr wants to merge 3 commits into
massiveits:mainfrom
zenyr:feat/native-quota-provider

Conversation

@zenyr

@zenyr zenyr commented Oct 4, 2026 •

Copy link
Copy Markdown

Summary

OpenCode Go credentials now report quota through CLIProxyAPI's native quota API. The plugin keeps the v7.2.138 SDK, so #10's v8 requirement does not apply here.

  • The plugin advertises quota_provider and serves quota.identifier, quota.describe, and quota.fetch. quota.reset returns unsupported.
  • Fetch uses the API key of the credential the host selected (attributes.api_key), never a config default. It reuses the existing upstream /usage call and error redaction.
  • Rolling, weekly, and monthly readings become buckets in one OpenCode Go group, with remainingFraction = (100 - percent) / 100. The rolling window is reported as 5h, since OpenCode Go documents it as a five-hour window; this lets Management Center label and order it like other five-hour limits. Absent windows are skipped and percentages are clamped to 0–100.
  • Quota lookups, native and on the separate page, are capped at 30 seconds. The general request timeout defaults to 5 minutes.
  • The separate OpenCode Go Quota page stays.
  • RELEASE_NOTES.md and README.md describe the native provider.
Host Behaviour
v7.2.138 – v7.2.158 Unchanged. The separate quota page works; the native quota API does not exist on these hosts.
v7.2.159+ / v8 POST /v0/management/quota/fetch and, on the v8 Management API, POST /v8/management/plugins/opencode-go-cliproxyapi/quota serve OpenCode Go quota. auth-files reports supports_quota: true. The separate page still works.

OpenCode Go quota card in Management Center

Screenshot from a live CLIProxyAPI v8.0.13 instance running this branch with a real OpenCode Go key, rendered by a forked Management Center build with generic plugin quota support. Official Management Center does not render plugin quota yet.

Why one build loads on both v7 and v8
  • Host and plugin exchange JSON over ABI version 1 on every release from v7.2.138 through v8 main. Moving from v7 to v8 changes the Go import path and leaves the wire format alone.
  • The host rejects a plugin whose schema_version is newer than its own (resp.SchemaVersion > pluginabi.SchemaVersion in internal/pluginhost/rpc_client.go). An SDK at v7.2.159+ or v8 reports schema 6, which v7.2.138–v7.2.158 hosts refuse.
  • This PR stays on the v7.2.138 SDK (schema 3) and declares the quota method names and wire structs locally in internal/plugin/native_quota.go. Those shapes match between v7.2.159 and v8 main, apart from the optional summary field, which this PR leaves unset.
  • quota_provider is a field in the plugin's own capabilities struct. Hosts older than v7.2.159 decode capabilities without DisallowUnknownFields and ignore it.

Why the separate page stays

Management Center has a fixed set of quota adapters and does not render plugin quota providers yet. Generic plugin rendering is still open in router-for-me/Cli-Proxy-API-Management-Center#432. Removing the page now would leave no UI for OpenCode Go quota, so it can go in a follow-up once Management Center supports plugin quota.

Known limitation

The plugin does not forward the host's host_callback_id to host.http.do. Cancelling a management request therefore leaves the upstream call running until the 30 second cap. The executor and the separate quota page share this through HostBridge, so forwarding the callback ID belongs in its own change.

Relation to #10

This covers the native quota part of #10 without the v8 bump. The readable account names from #10 don't depend on it and can land separately.

Cross-review

A second model, Codex gpt-6-astra, reviewed the branch read-only. It checked the plugin against the CLIProxyAPI source at v7.2.138, v7.2.159, and v8 main.

Findings and how they were handled

It confirmed these with no blocking issues:

  • v7.2.138–v7.2.158 hosts ignore the unknown capability.
  • v7.2.159 and v8 register quota providers without a schema floor.
  • The host passes attributes.api_key for auths that the plugin parses.
  • Response tags, missing-window handling, clamping, reset-time passthrough, the Go plan, error redaction, and config locking are correct.

It reported two findings, both confirmed:

  1. P2, upstream calls are not cancelled. The plugin drops host_callback_id and runs on context.Background(), and the old 5 minute default was the only bound. Fixed in part: lookups are now capped at 30 seconds. Forwarding the callback ID is left for a separate change (see Known limitation).
  2. P3, the compatibility test could miss an SDK bump. The test compared the registration against pluginabi.SchemaVersion, so moving to schema 6 would still pass. Fixed: the test pins schema_version to 3 and checks the fetch response's raw wire keys.

Validation

go vet ./... and go test -tags debug ./... pass, and the -tags debug c-shared build succeeds. End-to-end runs against locally built v8 and v7.2.138 hosts behaved as the table above describes. A live v8.0.13 instance with a real key returned all three windows through both the v0 and v8 routes.

Test and end-to-end details

New tests in internal/plugin/native_quota_test.go cover:

  • registration, with schema pinned to 3 and the capability advertised
  • identifier and describe
  • selected-credential fetch, bucket normalization, and fixed wire keys
  • the timeout cap
  • missing windows and clamping
  • redacted failures
  • unsupported reset

The end-to-end runs used the darwin/arm64 C-shared build and a mock /usage upstream.

  • CLIProxyAPI v8 (main, v8.0.13+): The plugin loads. GET /v0/management/quota/providers lists opencode-go, and auth-files shows supports_quota: true, quota_provider: "opencode-go". POST /v0/management/quota/fetch returns the three buckets, and the legacy quota-usage route still returns 200.
  • CLIProxyAPI v7.2.138: The plugin loads and registers. The legacy quota list and refresh return all three windows, and /v0/management/quota/providers returns 404 because that host has no native quota API.

🤖 Generated with Claude Code

zenyr and others added 3 commits October 4, 2026 22:04
Advertise the quota_provider capability and serve quota.identifier,
quota.describe, and quota.fetch so CLIProxyAPI v7.2.159+ (including v8)
reports OpenCode Go credentials as quota-capable and serves their
rolling, weekly, and monthly windows through /v0/management/quota/fetch.

The quota RPC method names and wire shapes are declared locally, so the
SDK stays on v7.2.138 and the plugin keeps reporting schema version 3.
Older hosts ignore the unknown capability and keep loading the plugin.
The separate quota page stays for hosts and Management Center builds
without generic plugin quota rendering.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Cap quota upstream lookups at 30 seconds for both the native provider
and the separate page, since the default request timeout is minutes
long. Assert schema version 3 literally and check the fetch wire keys,
so an SDK bump that breaks older hosts fails the tests. Add the native
quota provider to the release notes.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
OpenCode Go's rolling limit spans five hours. Reporting it as 5h lets
Management Center label and order it like other five-hour windows.
Document the v8 Management API quota route as well.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
@massiveits

Copy link
Copy Markdown
Owner

Upgrading to v8 is inevitable; we're going to merge #10 eventually. What's the case for landing this PR separately instead of just waiting for #10? Is there a reason to carry local wire structs and keep the v7 SDK when we know we're moving to v8 anyway?

@zenyr

zenyr commented Oct 4, 2026

Copy link
Copy Markdown
Author

Honestly, no strong case. I just wanted native quota on my instance before the v8 transition, and staying on the v7 SDK was the quickest way there. Feel free to close this in favor of #10.

If useful for #10: the 30s cap on quota lookups, and labeling the rolling window 5h.

Thanks for the project, by the way. I use it every day.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants