Every demo feature uses this pattern to communicate key status and quota to the user.
Key status
Shows which key is active on a feature page — the user's own key, or the demo quota with a usage meter.
Your provider keys
Shown on the account page when a user follows the add-key nudge. Supports save, test, and remove; the purpose annotation links the provider to its feature.
No key saved.
Rime
Used for Text-to-Speech
Demo quota
The quota system tracks usage across all features. The meter appears inline on each feature page; the summary card appears on the account page for a full overview.
Features
- Key resolution via
resolveProviderKey. Returns{ key, source: 'byok' | 'app' }. BYOK takes precedence over the app env var. - Quota only applies to
source === 'app'. Users with BYOK bypass the demo limit entirely. - Account page shows demo usage for features without BYOK. Built from
usageFeaturesconfig — absence of a DB row shows 0%, presence of BYOK hides the feature from the section entirely. - Chat uses one shared
chatquota spanning all providers. A user with BYOK for all four chat providers has no demo quota tracked.
Setup
Add the provider's API key to .dev.vars and .env for local dev:
# Single-provider features
RIME_API_KEY=
ASSEMBLYAI_API_KEY=
# Chat providers (all optional; each one independently enables that provider in the demo)
ANTHROPIC_API_KEY=
GEMINI_API_KEY=
OPENAI_API_KEY=
OPENROUTER_API_KEY=
For production, set as Cloudflare Worker secrets via wrangler secret put <NAME>.
Implementation details
- Key resolver —
src/lib/server/svelm/key-resolver.ts.ENV_VAR_MAPmaps eachProviderto its env var.resolveProviderKey(provider, locals, platform)checks BYOK first, then falls back to the env var. - Usage config —
src/lib/configs/usage.config.ts. Each entry hasfeatureKey,displayName,unitLabel,defaultLimit, and optionallyprovider(omitted for multi-provider features likechat). - Feature endpoint pattern — call
resolveProviderKey, gateconsumeTokensonresolved.source === 'app'. - Account page —
account/+server.tsiteratesusageFeatures, skips features where BYOK covers the provider, and returns DB row data (or defaults) for the rest. Returnsnullwhen all features are covered. - Chat topology —
chat-handler.tsaccumulatestotalTokensacross the agentic loop and callsconsumeTokens(db, userId, 'chat', totalTokens)after the stream completes. The singlechatfeature key is shared across all four chat providers. - STT usage endpoint —
src/routes/api/(svelm)/stt/usage/+server.ts. AcceptsPOST { seconds }, silently no-ops for unauthenticated or BYOK users. - DictateButton —
src/lib/svelm/stt/dictate-button.svelte. Records duration and POSTs to/api/stt/usageon stop.
Applying to a new single-provider feature
- Add the provider to
Providerinsrc/lib/svelm/agents/types.tsandPROVIDERSinagents/keys.ts. - Add
provider: 'ENV_VAR_NAME'toENV_VAR_MAPinkey-resolver.ts. - Add a
usageFeaturesentry inusage.config.tswith aproviderfield. - In the feature's API endpoint, call
resolveProviderKey(provider, …)and gateconsumeTokensonsource === 'app'. - Add the provider to the
providersarray inai-provider-keys.svelte(account UI). - Add the env var to
.env.example,.dev.vars.example, andDEV_VARS_KEYSinscaffold.js.
The account page demo usage section updates automatically — no changes needed there.