- JavaScript 53.5%
- TypeScript 46.5%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| doc | ||
| examples | ||
| src | ||
| test | ||
| .gitignore | ||
| .gitlab-ci.yml | ||
| LICENSE | ||
| notes.md | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
pi-billing-provider
pi-billing-provider registers the billing-provider virtual provider. Each configured real
model gets an included route, which is allowed only while a local usage snapshot says the
included allowance is available, and an api route, which always delegates to the real provider.
This makes it possible to prefer included usage and fail over to API billing with ordinary Pi
provider routing.
Install
Install the published package and restart Pi:
pi install npm:pi-billing-provider
For local development, install the checkout instead:
pi install /path/to/billing-provider
The extension delegates to existing real Pi providers. Configure and authenticate those providers
normally; billing-provider does not own upstream credentials or collect quota data itself.
Model IDs
The virtual provider is always billing-provider. Its virtual model IDs have this form:
<realProvider>/<realModel>/<variant>
variant is exactly one of included or api. For example:
{ "provider": "billing", "model": "openai-codex/gpt-5.6-sol/included" }
{ "provider": "billing", "model": "openai-codex/gpt-5.6-sol/api" }
The parser splits on the last /; that suffix must be included or api. It then splits the
remaining prefix on its first / into realProvider and realModel. Therefore real model IDs
may contain /, while real provider IDs may not.
includedreads the configured usage snapshot before delegation. A blocked route makes no upstream request.apidelegates unconditionally and never reads the snapshot.
Configuration
Create one global configuration file at:
<agentDir>/billing-provider/config.json
<agentDir> is Pi's agent directory (normally ~/.pi/agent). There is no project-local config
search. models is an explicit allow-list: only these real provider/model pairs get virtual
models. mode defaults to provider-wide; snapshotPath defaults to usage.json relative to
<agentDir>/billing-provider/; stalenessMs defaults to 300000; and log defaults to true.
Provider-wide mode
Use provider-wide mode when one included allowance applies to every configured model:
{
"mode": "provider-wide",
"snapshotPath": "usage.json",
"stalenessMs": 300000,
"log": true,
"models": [
{
"provider": "openai-codex",
"model": "gpt-5.6-sol",
"name": "GPT-5.6 Sol"
},
{
"provider": "openai-codex",
"model": "gpt-5.6-mini",
"name": "GPT-5.6 Mini",
"variants": ["included", "api"]
}
]
}
variants is optional and defaults to ["included", "api"]. Set it to ["api"] to expose only
the always-delegating API route for one model.
Tiered mode
Use tiered mode when availability differs by a flat named tier. Every configured model is matched against every tier pattern:
{
"mode": "tiered",
"snapshotPath": "usage.json",
"stalenessMs": 300000,
"log": true,
"models": [
{
"provider": "openai-codex",
"model": "gpt-5.6-sol",
"name": "GPT-5.6 Sol"
},
{
"provider": "openai-codex",
"model": "gpt-5.6-mini",
"name": "GPT-5.6 Mini"
},
{
"provider": "anthropic",
"model": "haiku-4.5",
"name": "Claude Haiku 4.5"
}
],
"tiers": {
"premium": ["gpt-5.6-sol"],
"standard": ["gpt-5.6-mini", "anthropic/haiku-*"]
}
}
A pattern containing / matches <realProvider>/<realModel>. A pattern without / matches only
the bare <realModel>. A model matching no tier has its included route blocked; a model matching
multiple tiers is a configuration error. api routes are unaffected in both cases.
Glob semantics
Tier patterns are case-sensitive, whole-string matches. They are flat strings rather than filesystem
paths: * can cross /. Supported syntax is:
*— zero or more characters.?— exactly one character.[abc]and[a-z]— character classes and ranges.[!abc]and[^abc]— negated character classes.
All other regular-expression metacharacters are literal. An unterminated [ is a literal [.
There is no path-segment behavior, brace expansion, extglob, or glob negation.
Snapshot and blocking behavior
A compatible statusline, job, or other writer supplies the local snapshot. See
doc/snapshot-format.md for its schema and the exported atomic writer.
The included route fails closed: a missing, invalid, stale, exhausted, wrong-scope, or tier-missing
snapshot blocks it without contacting the upstream API. In tiered mode, missing or stale data blocks
only its assigned tier; file-level problems block every included route. The returned blocked error has a uniform
message for a given real model:
[billing] included allowance unavailable for <realProvider>/<realModel>
The reason, tier, and snapshot-age detail are log-only. When a fresh snapshot confirms an exhausted
allowance and supplies a future resetAt, the error also carries an optional generic routingHint:
its exact candidates list contains all affected configured included routes, and until is that
reset timestamp. Compatible routing engines can cool those exact routes until reset; otherwise the
error remains an ordinary failed provider candidate. Tier conflicts block the affected included
route; invalid variant lists are narrowed to the safe api route; and invalid model entries are
omitted from the catalog. Other configuration errors are logged and surfaced once per session.
Status and logging
Run this Pi command to inspect the selected mode, resolved snapshot path, the current verdict for
every registered included model, and each tier's observation age and reset countdown (or the
provider-wide included allowance's age and reset), using updatedAt and resetAt from the snapshot:
/billing-provider-status
By default, operational events are appended best-effort to:
<agentDir>/billing-provider/billing-provider.log
Set "log": false to suppress ordinary lines. Configuration errors are always logged. The log
includes block reasons and other diagnostic detail; successful API and successful included requests
are not logged individually.
Cost handling
Virtual included models report zero cost. Virtual api models inherit the real model's cost
metadata. This affects catalog/reporting metadata only; the routes still delegate to the same real
provider and model when allowed.
pi-provider-manager failover example
With @jiajun0413/pi-provider-manager installed, use two ordered candidate tiers to prefer the
included route and use API billing only after it fails or is unavailable. Put this in
~/.pi/agent/roundrobin/config.json:
{
"candidates": [
[
{
"provider": "billing",
"model": "openai-codex/gpt-5.6-sol/included"
}
],
[
{
"provider": "billing",
"model": "openai-codex/gpt-5.6-sol/api"
}
]
],
"strategy": "round-robin",
"sticky": false
}
The outer arrays are priority tiers. A blocked included candidate fails normally, so
pi-provider-manager can advance to the variant-last api candidate without parsing quota data.