How DeepSeek-Reasonix Tracks Usage and Billing for API Costs: A Technical Deep Dive
DeepSeek-Reasonix tracks API costs by parsing token usage from provider responses via a generic Usage struct and optionally queries a DeepSeek-style wallet endpoint to display real-time balance, all surfaced through server-sent events to the UI.
DeepSeek-Reasonix (from the esengine/DeepSeek-Reasonix repository) implements a lightweight, read-only telemetry system that monitors every LLM request without interfering with the provider's billing logic. The architecture relies on two complementary mechanisms: provider usage telemetry that extracts token counts from streaming responses, and an optional wallet-balance query that fetches real-time account balance from DeepSeek-compatible endpoints.
Provider-Side Usage Telemetry
The Usage Struct and Token Parsing
At the core of the tracking system is the generic provider.Usage type, defined within each provider implementation (see the wireUsage structs in internal/provider/openai/openai.go and similar files). This struct normalizes diverse provider formats into a single schema:
type Usage struct {
PromptTokens int // tokens sent to the model
CompletionTokens int // tokens generated by the model
TotalTokens int // sum of the above (may include cached tokens)
CacheHitTokens int // tokens read from cache
CacheMissTokens int // tokens that missed the cache
Cost float64 // optional: USD cost supplied by the provider
CostUsd float64 // alias used by some providers
Currency string // optional currency code (e.g., "USD")
FinishReason string // why the generation stopped
}
When a provider streams a response, the implementation parses the usage JSON field and populates this struct. If the provider supplies cost or costUsd fields, Reasonix forwards these values directly without recalculating them locally.
Streaming Response Handling
Each provider implementation in internal/provider/* contains a streaming response handler that extracts the usage object from the JSON payload. For example, in internal/provider/openai/openai.go, the handler monitors the SSE stream for the final message containing usage statistics.
Once parsed, the controller emits an event.Usage event via the Emit method in internal/control.Controller. This event encapsulates the token counts and cost data for transmission to the frontend.
SSE Broadcasting to the Frontend
Usage data reaches the UI through Server-Sent Events (SSE). In internal/serve/serve.go, the controller writes the usage struct to the broadcast channel:
// Event emission to connected clients
bc.Emit(event.Usage{
PromptTokens: usage.PromptTokens,
CompletionTokens: usage.CompletionTokens,
Cost: usage.Cost,
Currency: usage.Currency,
})
This ensures that token counts and costs update in real-time as the model generates completions.
Frontend Aggregation and Cost Display
The frontend receives raw usage events through desktop/frontend/src/lib/useController.ts, which accumulates metrics per turn, per session, and for the entire application lifecycle. The UI maintains aggregates such as usageTotalTokens and updates the session ribbon with live cost calculations.
Currency formatting occurs via fmtMoney, which derives the appropriate symbol (¥, $, etc.) from the provider.Usage.Currency field. If a provider omits usage data entirely, Reasonix continues to function, but cost-related UI elements remain blank rather than displaying erroneous estimates.
Wallet Balance and Billing Queries
The Balance Fetch Implementation
For users requiring a hard balance read-out, Reasonix implements a dedicated billing client in internal/billing/balance.go. This module queries the DeepSeek-style endpoint GET /user/balance using the user's API key:
// Fetch queries the balance endpoint with a Bearer API key.
func Fetch(ctx context.Context, url, apiKey string) (*Balance, error) {
return FetchWithClient(ctx, httpClient, url, apiKey)
}
The function returns a *billing.Balance struct containing availability flags and balance information across currencies (CNY, USD, etc.). If no URL is configured, the function returns nil, nil without error, making the feature entirely optional.
Displaying Wallet Status
The desktop client renders wallet information in the status line. During onboarding and startup, desktop/app.go (around line 6460) calls billing.FetchWithClient to retrieve the current balance. The Balance.Display() method selects the primary currency (preferring CNY) and formats it with the appropriate symbol:
- Primary currency selection: Prioritizes CNY over USD when available
- Formatting: Uses locale-aware symbols (¥, $, etc.)
- Fallback: Silently omits the balance display if the endpoint is unreachable or returns an error
End-to-End Data Flow
The complete lifecycle of a tracked API request follows this sequence:
- User invocation triggers the controller to create a request to the configured provider.
- Provider streaming generates tokens, and the provider parser extracts the
Usagerecord from the final response chunk. - Controller emission pushes an
event.Usagestruct through the SSE broadcaster ininternal/serve/serve.go. - Frontend aggregation accumulates token counts and cost values via
useController.ts, updating the UI ribbon withfmtMoneyformatting. - Startup synchronization calls
billing.Fetchto retrieve wallet balance, displayed viaBalance.Display()in the status line. - Error handling ensures that missing usage data or unreachable balance endpoints degrade gracefully—never blocking the core chat functionality.
Summary
- Provider-agnostic telemetry: The
Usagestruct normalizes token counts across OpenAI, Anthropic, DeepSeek, and other providers viainternal/provider/*implementations. - Real-time streaming: Cost data flows through SSE via
internal/serve/serve.go, enabling live UI updates without polling. - Optional wallet integration: The
internal/billing/balance.goclient queries DeepSeek-style endpoints for account balance, with graceful degradation when unavailable. - Read-only architecture: Reasonix never writes back to provider APIs; it purely consumes usage metadata and pre-calculated cost fields.
- Graceful degradation: Missing usage data or balance errors result in blank UI elements rather than application failures.
Frequently Asked Questions
How does DeepSeek-Reasonix handle providers that don't return usage data?
If a provider omits the usage field from responses, Reasonix continues operating normally but leaves cost-related UI elements blank. The system does not attempt to estimate costs locally, ensuring users see only provider-verified billing information.
Where is the API cost calculation performed?
Cost calculation occurs on the provider side. Reasonix extracts the cost or costUsd fields from the provider's JSON response and forwards them unchanged to the frontend. The application acts as a transparent conduit rather than a billing calculator.
How does the wallet balance feature work with non-DeepSeek providers?
The wallet balance query targets a DeepSeek-specific endpoint (GET /user/balance). For non-DeepSeek providers, this feature remains dormant unless the provider exposes a compatible balance API. Users can configure the balance URL manually, or the feature simply disables itself when the endpoint returns errors or is unspecified.
What happens if the balance endpoint is unreachable?
When billing.Fetch encounters network errors or misconfiguration, it returns nil without throwing exceptions. The desktop client (desktop/app.go) handles this by silently omitting the balance display from the status line, ensuring the chat interface remains fully functional even during billing service outages.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →