Skip to content

feat: support quota published by custom OAuth-login plugins - #883

Open
collegeming wants to merge 4 commits into
seakee:devfrom
collegeming:feat/plugin-quota-support
Open

collegeming wants to merge 4 commits into
seakee:devfrom
collegeming:feat/plugin-quota-support

Conversation

@collegeming

Copy link
Copy Markdown

Summary

Custom plugins that implement OAuth login for other providers can only offer
their accounts to CPA, not to CPAMP: CPA already exposes each plugin's quota
over its management API and the plugins answer it, but the Manager Server has no
plugin quota source, so those credentials show no quota anywhere in the panel.
This adds a generic plugin quota source that consumes CPA's plugin quota
contract, and renders the items it returns in the places the panel already
shows quota.

Target branch: dev.

Scope

  • Frontend panel
  • Manager Server
  • CPA panel mode
  • Full Docker mode
  • Native packages / release
  • Docs / Wiki
  • CI / build / tooling

Changes

  • Manager Server: a new internal/pluginquota client for the generic plugin
    quota endpoints (GET /v0/management/quota/providers,
    GET /v0/management/plugins/:id/quota?auth_index=…,
    POST /v0/management/quota/fetch), and an internal/service/pluginquota
    service that resolves a credential by CPA's auth index, fetches what its
    plugin reports and persists it through the quota snapshot service the panel
    and the collector already use.
  • Manager Server: quota snapshots accept the plugin providers CPA reports. The
    built-in provider allowlist still rejects every unknown provider; the plugin
    catalogue is a runtime set, and a plugin provider is only accepted after CPA
    named it on /v0/management/quota/providers.
  • Manager Server: WritePluginQuotaResult maps each labelled item into the
    existing window model, and /v0/management/plugin-quota/{providers,credential,refresh}
    exposes the catalogue, the per-credential readings (the explicit refresh
    action) and a periodic refresh worker modelled on the Codex inspection worker.
  • Frontend: a plugin quota type whose config is derived at runtime from the
    catalogue, so a credential is recognised by the binding CPA reports
    (quota_provider on the auth file) or by the catalogue's supported
    credentials. Each reading becomes a non-window display item: the accounts
    list 额度 column shows the plugin's own amount and the detail 额度 tab renders
    the labelled items under "other quota items".
  • Docs: the accounts manual and the provider capability tables record the new
    evidence source in both languages.

User Impact

A credential that belongs to a plugin with a quota provider now shows that
plugin's labelled readings instead of "no quota data": the credential list's
quota column, the credential detail quota tab and the persisted quota snapshot
history all show the same items. Built-in providers are unaffected. When a
plugin is missing or does not answer, nothing is shown and nothing breaks.

Compatibility / Runtime Notes

  • CPA panel mode: the panel only shows what the Manager Server resolves; a panel
    without a Manager Server connection keeps its previous behaviour and simply
    has no plugin quota source.
  • Manager Server mode: two opt-in knobs, CPAMP_PLUGIN_QUOTA_ENABLED (default
    on) and CPAMP_PLUGIN_QUOTA_REFRESH_INTERVAL (default 10m, accepting a Go
    duration or seconds). The periodic pass only reads the plugin's cached quota;
    /v0/management/quota/fetch is called only when a caller explicitly
    requests a refresh.
  • Full Docker / native packages: no new listener, port, file or dependency.
    A CPA build without these endpoints answers the catalogue with an empty list,
    which is a valid "no plugin quota" answer.
  • Plugin providers and credentials are read from CPA at runtime. No plugin
    name, provider name or item key is compiled into the panel, so a new plugin
    works without a CPAMP change.

Data / Security Notes

  • No schema migration. Plugin items are stored as quota snapshot windows
    (window_kind=item, window_mode=non_window, the plugin's unit in
    quota_unit and a finite numeric reading as the window amount), keyed by the
    credential identity the panel already uses (auth file + auth_index).
  • Plugin labels, formats and currencies are never persisted: the snapshot model
    deliberately stores no provider display strings, and the panel renders them
    from the live observation.
  • The new endpoints authenticate through the same panel authorization as the
    existing quota snapshot and monitoring routes and reuse CPA's Management Key
    through the existing server-side connection, so no key is exposed to the
    browser and no credential file is read or modified.

Risk / Rollback

Risk level: Low

Rollback notes: the change is additive and needs no migration. Revert the
commits, or disable the worker with CPAMP_PLUGIN_QUOTA_ENABLED=false to stop
periodic plugin quota reads; previously stored snapshots and every built-in
provider keep working either way.

Verification

  • Type check
  • Lint
  • Tests
  • Build
  • Manual UI check
  • Docs/link check
  • Not applicable, docs-only

Commands / evidence:

npm run type-check                 # tsc --noEmit, clean
npm run lint                       # clean
npm run test                       # 252 web files / 4174 tests + repo tests, all pass
npm run manager-server:test        # go test ./... , all packages pass
go vet ./...                       # clean
docker build -f Dockerfile.manager-server -t plugin-quota-check .

Live check against a running CPA with plugins that publish quota:

GET /v0/management/plugin-quota/providers
→ {"providers":[{"plugin_id":"…","provider":"…","display_name":"…",
                 "supported_providers":["…"],"supports_reset":false}, …],
   "status":{"credentials":[{"auth_file_name":"…","item_count":3}, …]}}

GET /v0/management/plugin-quota/credential?auth_index=…
→ {"provider":"…","display_name":"…","observed_at_ms":…,
   "items":[{"key":"credit_remaining","label":"剩余额度","value":1739.5,
             "unit":"credit","format":"number"},
            {"key":"credit_used","label":"已用额度","value":760.5,
             "unit":"credit","format":"number"},
            {"key":"daily_checkin","label":"今日可签到","value":1,
             "format":"boolean"}]}

POST /v0/management/quota-snapshots/query  (same credential)
→ windows:[{"provider_window_id":"credit_remaining","window_kind":"item",
            "window_mode":"non_window","limit_value":1739.5,
            "quota_unit":"credit","source":"api_query","availability":"active"}]

GET /v0/management/quota-snapshots/query  (a built-in provider row)
→ HTTP 200, windows:[]        # no regression
GET /v0/management/quota-snapshots/query  ("not-a-provider")
→ HTTP 400 unsupported provider "not-a-provider"   # allowlist unchanged

Manual check: with the built image running, the credential list quota column
shows the plugin's items and amounts, and the credential detail quota tab
renders the same items instead of the "no quota window" empty state; the
dashboard, monitoring, config, plugins and logs pages still load.

Screenshots / Recordings

Manual UI check screenshots (credential list quota column, credential detail
quota tab) were captured during the live check described above.

Docs

  • README / README_CN updated for user-visible capabilities
  • Matching docs manual and navigation updated
  • Demo fixtures, screenshots, and deep links reviewed
  • Release notes needed
  • Not needed — explanation included below

Docs decision: apps/docs/manual/accounts.md and
apps/docs/en/manual/accounts.md gained a plugin-provider row in the quota
evidence table. README does not enumerate quota sources per provider, and the
demo fixtures have no plugin credential, so neither needed a change.

Related

N/A

CPA exposes plugin quota generically: a plugin publishes a quota provider on
/v0/management/quota/providers, binds its credentials through the quota_provider
reported on /v0/management/auth-files, and answers labelled readings on
/v0/management/plugins/:id/quota.

The quota snapshot store only accepted the providers compiled into
validProviders, so a plugin-owned credential could never keep quota evidence.
The service now validates a provider against the built-in allowlist plus a
runtime catalogue the caller registers from CPA's own answer, and
WritePluginQuotaResult maps each labelled reading into the existing window
model: the plugin's item key is the provider window id, its unit is the stored
quota unit and a finite numeric reading is stored as the window amount. Plugin
labels, formats and currencies are presentation hints CPA returns on every
read, and the snapshot model deliberately persists no provider display strings.

An unregistered plugin provider is ignored by WritePluginQuotaResult and stays
rejected by the generic Write and Query entry points, so the built-in allowlist
is unchanged.
Add a plugin quota source that is driven entirely by CPA's own contract: the
provider catalogue, the credential binding and the item keys all arrive at
runtime, so no provider name or channel is compiled into the panel.

- internal/pluginquota is a transport-only client for
  /v0/management/quota/providers, /v0/management/plugins/:id/quota and the
  explicit /v0/management/quota/fetch action. It keeps a plugin's value shape
  (number, boolean or string) instead of coercing it into fake numeric
  evidence, and keeps CPA's plugin id verbatim for management URLs.
- internal/service/pluginquota resolves one credential by CPA's auth index,
  writes what the plugin reports through the same quota snapshot service the
  panel and the collector already use, and reuses a short-lived provider
  catalogue so a panel refresh does not re-list providers per credential.
- The panel reads and triggers it through /v0/management/plugin-quota/*, and a
  worker mirrors the Codex inspection worker: one pass at startup, then a
  periodic pass that only reads cached plugin quota. CPAMP_PLUGIN_QUOTA_ENABLED
  and CPAMP_PLUGIN_QUOTA_REFRESH_INTERVAL tune it.

Unknown or removed plugins produce no rows and no error, and a summary without
usable items is ignored rather than deactivating a credential that still owns
quota.
A plugin quota is a set of labelled readings rather than the fixed windows the
seven built-in providers expose, so the panel gains a 'plugin' quota type whose
config is derived at runtime from the Manager Server's provider catalogue.

- The provider catalogue is loaded once per connection and published through a
  subscribable store, so a credential is recognised by CPA's own quota_provider
  binding and an unknown plugin still resolves.
- Each reading becomes a non-window display item that reuses the existing
  quota window pipeline: the accounts list 额度 column shows the plugin's own
  amount, and the detail 额度 tab renders the items under "other quota items"
  with the plugin's label, value, unit and format.
- Plugin amounts render through their plugin format hint (number, currency,
  percent, boolean) without the panel interpreting a plugin unit, and a failed
  refresh keeps the previous items visible.
- Adds plugin_quota strings for zh-CN, zh-TW, en and ru, and tests for the
  catalogue, the fetcher, the config state machine and the display windows.
Record that a plugin quota provider publishes its labelled items through CPA's
quota.* management endpoints, that CPA names the provider and its credential
binding, and that CPAMP renders each item verbatim instead of interpreting a
plugin unit.

This branch has not been deployed

No deployments
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.

1 participant