Repository navigation
v0.0.4
0.0.4 — 2026-04-04
Opt-in usage upload pipeline and cache-aware pricing for the leaderboard flow.
New Modules
aistatus.config — persistent upload configuration
Manages SDK-wide upload identity, backed by ~/.aistatus/config.yaml.
AIStatusConfigdataclass — five fields:upload_enabled: bool = False— master switch for usage uploadsname: str = ""— user or organization display nameorganization: str = ""— organization identifieremail: str = ""— contact email for the upload identitybase_url: str = "https://aistatus.cc"— upload API endpoint
get_config() → AIStatusConfig— returns a lazy-loaded singleton;
thread-safe via double-checked locking (threading.Lock).
On first call: loads~/.aistatus/config.yaml(YAMLupload:section),
then overlays environment variables. Subsequent calls return the cached
instance.configure(*, upload, name, organization, email) → AIStatusConfig—
mutates the singleton in-memory, then persists the entire config back to
~/.aistatus/config.yamlviayaml.safe_dump. Returns the updated config.- Config precedence:
configure()overrides > env vars > YAML file > defaults - Environment variables:
Variable Maps to Type AISTATUS_UPLOADupload_enabledbool ( 1/true/yes/on)AISTATUS_NAMEnamestr AISTATUS_ORGorganizationstr AISTATUS_EMAILemailstr
aistatus.uploader — fire-and-forget usage upload
Bridges local usage tracking to the remote leaderboard API.
UsageUploaderclass:- Constructor takes
AIStatusConfig; readsconfig.base_urlfor the
endpoint. - Shared thread pool: class-level
ThreadPoolExecutor(max_workers=2, thread_name_prefix="aistatus-upload"), initialized once via
double-checked locking. Registered foratexitshutdown with
wait=Falseso it never blocks interpreter exit. upload(record: dict)— the only public method:- Guards: skips if
upload_enabledisFalseor ifname/email
are empty - Builds a sanitized payload:
- Maps short keys to full names:
in→input_tokens,
out→output_tokens,cache_creation_in→
cache_creation_input_tokens,cache_read_in→
cache_read_input_tokens - Truncates
name(200 chars),organization(200),email(254) - Includes
sdk_version(package__version__)
- Maps short keys to full names:
- Submits
_post(payload)to the shared executor — fire-and-forget,
never awaits
- Guards: skips if
_post(payload)—urllib.request.urlopenPOST to
{base_url}/api/usage/uploadwithContent-Type: application/json,
5-second timeout. Catches all exceptions silently.
- Constructor takes
UsageUploadSinkProtocol (inusage.py) — structural typing
interface (upload(record: dict) → None) soUsageTrackerdoesn't
depend directly onUsageUploader.
Enhanced
Usage tracking pipeline
UsageTracker.__init__— new optionaluploader: UsageUploadSink | None
parameter. When set, bothrecord()andrecord_usage()forward every
usage record touploader.upload()after persisting to local storage.UsageTracker.record()— now includes conditionalcache_creation_in
andcache_read_inkeys in the record dict when the response carries
non-zero cache tokens.UsageTracker.record_usage()— new keyword args:
cache_creation_input_tokens: int = 0,
cache_read_input_tokens: int = 0,billing_mode: str | None. Same
conditional inclusion and upload fan-out.UsageTracker.calculate_cost()— automatically routes to
calculate_cost_with_cache()when the response has non-zero cache
tokens; falls back to the basiccalculate_cost()otherwise.
Router & Gateway wiring
Router.__init__— whentrack_usage=True, constructs:All routed requests (sync and streaming) now feed the upload pipelineUsageTracker(uploader=UsageUploader(get_config()))
automatically with zero caller effort.GatewayServer.__init__— same pattern:Bothself.usage = UsageTracker(uploader=UsageUploader(get_config()))
_record_stream_usage()and_record_usage_if_possible()now
capture and forward cache token fields.
Data model additions
-
RouteResponse— two new frozen dataclass fields:cache_creation_input_tokens: int = 0cache_read_input_tokens: int = 0
Backward-compatible (both default to 0).
-
StreamUsageChunk(TypedDict) — already had optional
cache_creation_input_tokensandcache_read_input_tokenskeys;
these are now propagated through toRouteResponseand usage records.
Cache-aware pricing
-
CostCalculator.calculate_cost_with_cache()— new method:def calculate_cost_with_cache( self, provider, model, input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens, ) -> float
Cost formula:
- Base input:
input_tokens × input_per_million - Cache creation:
cache_creation_input_tokens × write_price
(fetched from API; fallback 1.25× input price) - Cache read:
cache_read_input_tokens × read_price
(fetched from API; fallback 0.10× input price) - Output:
output_tokens × output_per_million
- Base input:
-
_fetch_pricing()— now also extractsinput_cache_readand
input_cache_writefrom the API response, returning them as
input_cache_read_per_millionandinput_cache_write_per_million
in the pricing dict.
Fixes & Hardening
Security
- Gateway auth —
/healthendpoint now respectspublic_pathsconfig
and requires auth when not listed - Anthropic adapter — concatenate multiple system messages instead of
last-wins; prevents silent prompt loss - Router — deduplicate system messages in
_normalize_messages()when
bothsystemoption and messages list contain a system role
Provider adapters — client reuse
- Compatible adapters (DeepSeek, Groq, Mistral, xAI, etc.) — cache
HTTP client per(base_url, api_key)to prevent connection leak;
previously created a newhttpx.Clientper request - OpenAI adapter — same client caching pattern
- Anthropic adapter — same client caching pattern
- Google adapter — check for API key change before reusing cached
client to avoid stale credentials
API client
StatusAPI— reusehttpx.Clientacross calls instead of creating
per-request; add properclose()/ context manager support
Gateway server
- SSE translator — fix
finish_reason/[DONE]ordering edge case
on stream truncation; emit error SSE event on mid-stream upstream failure - Streaming usage — gateway
_record_stream_usage()now correctly
captures cache token fields from streamed chunks - Translate path — usage extraction for translated (cross-provider)
requests now recorded properly
Usage & pricing
- Retry latency — include sleep backoff time in retry latency
measurement (was undercounting) - Pricing cache — atomic write-then-rename for
pricing-cache.json
to prevent corruption on concurrent access; file cache read hardened
against malformed JSON
Uploader
- Thread safety —
UsageUploaderuses class-level
threading.Lock+ double-checked locking for shared executor init
(was racy on first concurrent use)
Tests
- New and expanded test suites:
test_uploader.py(316 lines),
test_proxy_model_extraction.py,test_pricing.py,
test_model_health.py— covering retry handling, translate-path
usage extraction, cooldown persistence, and cache-safe persistence
Public API
New top-level exports in aistatus.__init__:
from aistatus import AIStatusConfig, configure, get_configUser Flow
from aistatus import configure, Router
# One-time setup (persisted to ~/.aistatus/config.yaml)
configure(name="Alice", email="alice@example.com", upload=True)
# Every route() call now uploads usage in the background
router = Router()
resp = router.route("Hello", model="claude-sonnet-4-6")
# → usage record POSTed to aistatus.cc/api/usage/upload (async, silent)