Repository navigation
feat(providers): expose a usage-fetch capability in the provider plugin manifest - #11903
Conversation
|
CI on this PR is red, but every failure also fails on the base commit with a clean tree — none is introduced here. I re-ran the exact failing files on a pristine checkout of
The drifts are all stale counters and stale references unrelated to this change: provider count 351 vs "357" in
What is green and does depend on this change: Vitest, Change Classification, Merge integrity (changelog + generated skills), semgrep, plus locally The change's own tests: 7/7 in Happy to open a separate maintenance PR for the counter/reference drift if that would help — I left it alone here to keep this PR to the one capability. |
…in manifest
An external dashboard that wants to show, per provider, whether OmniRoute can read
usage or quota has no API for it today — it has to read `open-sse/services/usage.ts`
and re-check the file after every release. The manifest already answers "what can this
provider do" for auth, executor, Responses and sidecar eligibility, so this adds the
missing tag rather than a new surface.
`capabilitiesFor()` now emits `usage-fetch` for registry entries listed in
`USAGE_FETCHER_PROVIDERS`, resolving on the entry id and on its alias: the list is keyed
by the strings the usage dispatcher accepts, so it mixes canonical ids ("hyperagent")
with aliases ("ha"), the same way `getProviderPluginManifestEntryFromRegistry` already
resolves a lookup. 40 of the 267 registry providers carry the tag.
Discovery only — no new fetcher, no quota change, no activation. Nothing reads the tag
yet, and the Dashboard quota widget stays gated by `USAGE_SUPPORTED_PROVIDERS`.
`USAGE_FETCHER_PROVIDERS` moves to a new zero-dependency leaf,
`open-sse/services/usage/fetcherProviders.ts`, and is re-exported from
`services/usage.ts` so every existing import path keeps working. The move is what keeps
the manifest a light leaf: `services/usage.ts` is the dispatcher and pulls ~490 modules
(DB, sockets, child_process), while `config/providerPluginManifest.ts` is a JSON-safe
config module served over HTTP at `GET /api/v1/provider-plugin-manifest`. Importing the
dispatcher there would have grown its graph from 17 modules to ~490; importing the leaf
leaves it at 18 with no new external dependency, and duplicating the list would have
broken the single-source-of-truth invariant the list exists to protect.
Closes diegosouzapw#11722
cb84bf7 to
7f522ba
Compare
15b1648
into
diegosouzapw:release/v3.8.51
Publish the second usage capability from diegosouzapw#11722. The manifest now advertises usage-supported alongside usage-fetch so integrators can tell whether the server usage routes accept a provider without reading TypeScript. USAGE_SUPPORTED_PROVIDERS moves to a zero-import leaf (src/shared/constants/providers/usageSupported.ts) and is re-exported from providers.ts, mirroring the fetcherProviders leaf from diegosouzapw#11903 and keeping the manifest a light module. usage-fetch resolves on id or alias (dispatcher accepts both); usage-supported resolves on id alone, matching the runtime guard (USAGE_SUPPORTED_PROVIDERS.includes with no alias resolution). 7 differences prove the two notions are distinct (42 shared, 4 fetcher-only, 3 supported-only).
…kspace boundary) The manifest lives in open-sse and may not import from src/ (the open-sse typecheck gate). Same pure-data leaf pattern as fetcherProviders.ts (diegosouzapw#11903): open-sse/services/usage/supportedProviders.ts owns the list (typed readonly string[] so .includes(string) keeps compiling), and src/shared/constants/ providers.ts re-exports it so every existing import path is unchanged. The dead UsageSupportedProvider type is gone; the list matches the current base (kilocode included — openrouter/devin-cli arrive with diegosouzapw#12256, which needs a re-sync onto whichever of the pair lands first).
…12214) The provider plugin manifest already exposed usage-fetch (40 providers, #11903); this publishes the second capability, usage-supported, so integrators can tell without reading TypeScript whether the server usage routes accept a provider. #11903 closed #11722 after shipping only half of it and said so at the time — this is the follow-up it promised. The two scopes genuinely differ and the docs now say how: usage-fetch resolves on id or alias (the dispatcher accepts both), usage-supported on id alone, because the runtime guard does a plain USAGE_SUPPORTED_PROVIDERS.includes(providerId) with no alias resolution. 42 providers carry both tags, 4 carry only usage-fetch (opencode, opencode-zen, openrouter, xai) and 3 only usage-supported (adobe-firefly, firefly, xiaomi-mimo-token-plan) — 7 measured differences, so neither tag implies the other. No list mutation, no new route, schemaVersion stays 1. USAGE_SUPPORTED_PROVIDERS moved out of src/shared/constants/providers.ts into an import-free leaf at open-sse/services/usage/supportedProviders.ts, keeping the manifest's import graph light — the same move fetcherProviders.ts got in #11903, landed on the correct side of the workspace boundary. Base note: the branch forked 46 commits before kilocode joined the list, so a wholesale take of its providers.ts would have silently dropped that id. Verified against the current release tip before merging — both sides hold the same 46 ids, nothing lost. Verified on the current tip: typecheck:core clean, check:cycles OK across 417 files (the import-free-leaf claim holds), and 63/63 focused tests across provider-plugin-manifest, usage-fetcher-registration-coverage, adobe-firefly and agentrouter-quota-dashboard-rendering. Thanks @maxmad64bis — and for finishing the half of #11722 that was left open rather than letting it sit.
…in manifest (diegosouzapw#11903) Boarded with 8 other PRs in one combined worktree: typecheck:core, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles, check-native-deps all green; 75/75 focused tests pass. Discovery-only as claimed — nothing reads the new tag yet, dashboard quota widget stays gated by USAGE_SUPPORTED_PROVIDERS. Thanks.
…iegosouzapw#12214) The provider plugin manifest already exposed usage-fetch (40 providers, diegosouzapw#11903); this publishes the second capability, usage-supported, so integrators can tell without reading TypeScript whether the server usage routes accept a provider. diegosouzapw#11903 closed diegosouzapw#11722 after shipping only half of it and said so at the time — this is the follow-up it promised. The two scopes genuinely differ and the docs now say how: usage-fetch resolves on id or alias (the dispatcher accepts both), usage-supported on id alone, because the runtime guard does a plain USAGE_SUPPORTED_PROVIDERS.includes(providerId) with no alias resolution. 42 providers carry both tags, 4 carry only usage-fetch (opencode, opencode-zen, openrouter, xai) and 3 only usage-supported (adobe-firefly, firefly, xiaomi-mimo-token-plan) — 7 measured differences, so neither tag implies the other. No list mutation, no new route, schemaVersion stays 1. USAGE_SUPPORTED_PROVIDERS moved out of src/shared/constants/providers.ts into an import-free leaf at open-sse/services/usage/supportedProviders.ts, keeping the manifest's import graph light — the same move fetcherProviders.ts got in diegosouzapw#11903, landed on the correct side of the workspace boundary. Base note: the branch forked 46 commits before kilocode joined the list, so a wholesale take of its providers.ts would have silently dropped that id. Verified against the current release tip before merging — both sides hold the same 46 ids, nothing lost. Verified on the current tip: typecheck:core clean, check:cycles OK across 417 files (the import-free-leaf claim holds), and 63/63 focused tests across provider-plugin-manifest, usage-fetcher-registration-coverage, adobe-firefly and agentrouter-quota-dashboard-rendering. Thanks @maxmad64bis — and for finishing the half of diegosouzapw#11722 that was left open rather than letting it sit.
Summary
An external dashboard that integrates OmniRoute has no API to ask, per provider, whether
usage or quota can be fetched — today it has to read
open-sse/services/usage.tsandre-check that file after every release. The manifest already answers "what can this
provider do" for auth type, executor, Responses support and sidecar eligibility, so this
adds the missing tag rather than a new surface.
capabilitiesFor()now emitsusage-fetchfor registry entries listed inUSAGE_FETCHER_PROVIDERS. 40 of the 267 registry providers carry the tag.Resolution is on the entry id and its alias:
USAGE_FETCHER_PROVIDERSis keyed by thestrings the usage dispatcher accepts, so it mixes canonical ids (
hyperagent) with aliases(
ha,pql,cnl,xao) — the same waygetProviderPluginManifestEntryFromRegistryalready resolves a lookup. Today no provider matches on alias alone, so the alias arm
changes nothing; it is there so the tag stays correct if a future entry is registered only
under its alias.
Scope is exactly what the issue asked for: discovery only — no new fetcher, no quota
change, no activation. Nothing reads the tag yet, and the Dashboard quota widget stays
gated by
USAGE_SUPPORTED_PROVIDERS.Why the list moved to a leaf
USAGE_FETCHER_PROVIDERSmoves to a new zero-dependency leaf,open-sse/services/usage/fetcherProviders.ts, re-exported fromservices/usage.ts(valueand the derived
UsageFetcherProvidertype), so every existing import path keeps workingunchanged — the 20+ call sites and tests that import from
services/usage.tsareuntouched.
The move is what keeps the manifest a light leaf.
services/usage.tsis the dispatcher andpulls ~490 local modules (DB, sockets,
child_process);config/providerPluginManifest.tsis a JSON-safe config module served over HTTP at
GET /api/v1/provider-plugin-manifest.Importing the dispatcher there would have grown its graph from 17 → ~490 modules and
dragged the DB layer into the manifest route. Importing the leaf leaves it at 18, with
no new external dependency. Duplicating the list instead would have broken the
single-source-of-truth invariant that the list exists to protect.
This follows the pattern already established by the other
usage/leaves (scalars.ts,quota.ts) — a behavior-preserving extraction, which also fits the 3.8.5x "non-breakingstructural prep" phase.
Open question from the issue, deliberately not answered here
The issue asked whether to also expose a
usage-supportedsignal (USAGE_SUPPORTED_PROVIDERS,which gates the quota widget per #10078). The acceptance criteria only list
usage-fetch, sothis PR ships just that. Happy to add the second tag in a follow-up if you want it.
Likewise, the
onUsageFetch(connection)plugin runtime seam raised in the issue comment is aruntime extension, not discoverability, and is out of this PR's scope.
Related Issues
Validation
npm run lintBase is
release/v3.8.51at13afbfa.Failing-then-passing (Hard Rule #18) — demonstrated by toggling only the emission block
in
capabilitiesFor(), everything else identical:capabilitiesFor()emissiontests 7 · pass 5 · fail 2tests 7 · pass 7 · fail 0Both failures are assertion failures on the new behavior, not import errors:
The negative test (
manifest omits usage-fetch for providers without a usage fetcher) passesin both states by design — it is a guard against over-tagging, not a RED/GREEN case.
Regression run over every suite that touches
USAGE_FETCHER_PROVIDERSor the manifest —141/141 passing (15 suites:
provider-plugin-manifest,usage-families-split,qoder-usage-quota,ollama-cloud-usage,xai-usage,xai-oauth-usage,agy-usage-quota,agentrouter-quota-visibility,executor-hyperagent,executor-promptql,firecrawl-usage,command-code-usage,kimi-coding-apikey-quota-4435,grok-cli-provider-limits,qwen-token-plan-quota-fetcher).Manifest parity check against the live registry: the set of providers tagged
usage-fetchequals the set computed independently from
USAGE_FETCHER_PROVIDERS(no missing, no extra),capabilitiesarrays stay sorted, the manifest still JSON-round-trips, andusage-fetchisthe only new tag emitted across all 267 providers.
Gates that depend on this change — all green
check:cycles(CI roots)check:cyclesonopen-sse/config+open-sse/servicescheck:file-sizecheck:docs-synccheck:doc-linkscheck:deprecated-versionseslint(repo invocation, withconfig/quality/eslint-suppressions.json)prettier --checkon changed filesmarkdownlinton the changed docPre-existing red on the base — NOT from this PR
Verified by running each on a pristine checkout of
release/v3.8.51@13afbfawith aclean tree:
check:docs-counts— 11 STRICT drifts, output byte-identical before and afterthis PR (
diffis empty). All of them are stale counters: provider count 351 vs "357" inREADME.md/AGENTS.md/llm.txt/package.jsondescription / fourdocs/diagrams/*.svg, DB migrations 165 vs "160", i18n locales, MCP tools, compressionengines. None touches capabilities or the manifest.
check:fabricated-docs— 1 drift,docs/providers/CHATGPT_WEB.md:129referencingtests/unit/migration-163-retire-chatgpt-web.test.ts, which no longer exists. Same on base.typecheck:core— 1 error,open-sse/executors/antigravity/executeAttempt.ts(392,47)TS2345. Identical on the pristine base; this PR introduces zero new typecheck errors.
Happy to fold any of these into a separate maintenance PR if useful — they are unrelated to
this change, so I left them alone.
Tests Added Or Updated
tests/unit/provider-plugin-manifest.test.ts— 3 new tests (4 → 7):manifest advertises usage-fetch for providers with a wired usage fetcher (#11722)manifest omits usage-fetch for providers without a usage fetcher (#11722)usage-fetch matches the fetcher list by alias too (#11722)Each asserts against the real
USAGE_FETCHER_PROVIDERSrather than a hardcoded copy, with afixture guard, so the tests cannot silently drift if the list changes.
Changelog fragment:
changelog.d/features/11903-usage-fetch-capability.md(
check:changelog-integritypasses).Coverage Notes
open-sse/config/providerPluginManifest.ts: the new branch incapabilitiesFor()is coveredin both directions — tagged (
claude), untagged (openai,anthropic,claude-web), andthe alias arm.
open-sse/services/usage/fetcherProviders.tsis pure data with no branches;it is exercised by the manifest tests and by the 15 suites listed above through the
services/usage.tsre-export.Reviewer Notes
capabilitiesarray of 40 manifest entries. No fetcher, quota path, routing decision or UI reads it.
literal is byte-identical, and the re-export preserves both the value and the
UsageFetcherProvidertype. The 141-test regression run above is what covers it.ProviderPluginCapabilityis a widening union change:schemaVersionstays1, since aconsumer that ignores unknown tags is unaffected. Say the word if you would rather bump it.
docs/reference/PROVIDER_PLUGIN_MANIFEST.mdgainedusage-fetch, plus a new Capability Tags section documenting all seven tags andspelling out that
usage-fetchis discovery only. It also notes why the tag count (40) islower than the fetcher-list length (46):
firecrawlis a search provider andamazon-qisan ACP provider, so neither has an entry in the chat-provider registry the manifest is
generated from.