Problem ------- The official DeepSeek provider still modelled the pre-V4.1 world. DeepSeek shipped V4.1 Flash as `deepseek-flash` with native image input, retired `deepseek-v4-flash-vision-exp`, cut the Flash price from 2026-09-10 12:00 Beijing, and left `deepseek-v4-flash` / `deepseek-v4-pro` as transition aliases. The desktop model list therefore showed `deepseek-flash` as "image capability unknown", `deepseek-v4-flash` was hard-blocked from images even though it now serves a multimodal model, and every cost quote was priced on the superseded August table. Root cause ---------- Image capability had no single owner: the settings resolver read `BuiltinModelInfo` and the curated preset, the runtime gate read the pinned-SKU check inside `officialDeepSeekEffectiveVision`, and a separate hard text-only block keyed on an exact two-model list. Adding a SKU meant editing several lists that could silently disagree. Pricing had the same shape: `ResolveScheduledRate` hardcoded one schedule id, and `LookupCatalog` returned the first peak row, so a second dated schedule could not be expressed. Fix --- - `internal/provider/deepseek_models.go` owns the official DeepSeek model lists. `IsOfficialDeepSeekImageModel` covers V4.1 Flash and the ids the vendor still routes to it; `IsOfficialDeepSeekTextModel` is now V4 Pro only. The capability resolver, the runtime vision gate and the local model catalog all consult it, so a new SKU is added in one place. - An explicitly emptied `VisionModels` list still disables image input for a provider; only a stale non-empty list is allowed to omit a model the vendor reports as capable. - `internal/billing/catalog.go` gains `ScheduleDeepSeekV4September2026` (2026-09-10 04:00 UTC). The August rows close at the cutover so the superseded prices stay queryable, and the retired ids are billed at the Flash price. `LookupCatalog` selects the newest peak anchor, and the new `MatchesOfficialPeakAnchor` accepts an anchor from any generation, so a config saved before the price cut is still quoted at the live rate. - `internal/config/pricing.go` carries the new Flash table and recognises the August generation as an official default instead of a custom rate. The config version is deliberately unchanged: `ApplyUserConfigUpgradesOnStartup` stamps `deepSeekOfficialChatUpgradeConfigVersion` (9) for any config below the current version, which would downgrade a v10 config while also skipping the OpenCode Go upgrade that follows it. The refresh is done at quote time instead, so no user config is rewritten on startup. Verification ------------ - `go test ./...` (root): 131 packages ok. The 3 remaining failures reproduce on a pristine `origin/main-v2` checkout: untracked `wt*/` worktrees inside the repo are scanned by the sub-agent boundary test, and `cmd/reasonix-legacy-migrator` reads a real installation on this machine. - `cd desktop && go test ./...`: ok. - `golangci-lint` 2.12.2: 0 issues. `repolint`: clean, baseline unchanged. - New coverage: `TestOfficialDeepSeekMultimodalSKUsEmbedUserImages` (image parts reach the wire), `TestDeepSeekSeptemberScheduleResolvesFlashPriceCut` (peak/off-peak, both currencies, legacy ids, V4 Pro unchanged, August schedule closed), and `TestStaleDeepSeekAnchorQuotesAtTheLiveRate` (end-to-end quote from a config still holding the August anchor).
4.5 KiB
Billing, display currency, and cost quotes
Reasonix keeps three facts separate:
original: an estimate from the selected public/custom rate card in its pricing-table currency. It is not an invoice or a provider debit.valuations: occurrence-timeidentityand, when available, anofficial_tableestimate for the same model in the other official region.- Wallet balances: the exact original-currency values returned by a provider.
Reasonix has no runtime FX download, cache, refresh goroutine, or wallet
conversion. Old fx/rateSnapshot fields remain readable for history only;
new quotes never generate them.
[billing]
display_currency = "auto" # auto | CNY | USD
[[providers]]
billing_currency = "USD" # pricing-table basis, not settlement currency
billing_mode = "payg" # payg | subscription_equivalent
Legacy [desktop].currency remains readable and migrates to
[billing].display_currency. auto is intentionally unresolved in config:
one valid wallet currency may become a tab/session hint; otherwise a single
original currency is selected or mixed currencies are shown as buckets. A
language, browser locale, or host region never changes a rate card.
CostQuote
usage.costQuote is the canonical host-side usage payload:
| Field | Meaning |
|---|---|
original |
Original-currency rate-card estimate |
originalTotals[] |
ISO-sorted original buckets for mixed aggregates |
valuations.*.basis |
identity or official_table for new quotes |
selected |
A single amount only when a display total exists |
costComplete |
Usage and pricing facts are complete |
displayComplete |
A requested single-currency total exists |
complete |
Compatibility alias mirroring displayComplete |
displayStatus |
matched, fallback_original, bucketed, or unavailable |
aggregateMode |
single_currency, common_valuation, or currency_buckets |
rateBand |
DeepSeek occurrence-time band: peak, off_peak, or aggregate mixed |
ratedAt |
UTC request-completion time used to select a scheduled rate |
If a requested currency is unavailable but every original is the same, the
original amount is shown with fallback_original. Mixed originals produce
originalTotals and no scalar zero. — is reserved for missing usage or
pricing (unavailable). Legacy scalar aliases (cost, costUsd,
total_cost) are written only when selected exists.
DeepSeek scheduled rates
For official DeepSeek OpenAI, Responses, and Anthropic endpoints, Flash and V4 Pro use occurrence-time pricing. Peak windows are 09:00–12:00 and 14:00–18:00 Beijing time; boundaries are left-closed/right-open and all other times are off-peak. The request-completion timestamp is used because the provider does not report per-token billing time. Images sent to a vision-capable model are billed as input tokens from provider usage.
Two dated schedules are recorded. From 2026-08-17 00:00 Beijing time, V4 Flash,
deepseek-v4-flash-vision-exp (same list price as Flash), and V4 Pro. From
2026-09-10 12:00 Beijing time, deepseek-flash carries a lower Flash price, and
the retired deepseek-v4-flash and deepseek-v4-flash-vision-exp ids are served
by that model and billed at the same rate. V4 Pro keeps its own price until the
vendor routes it to V4.1 Flash. A quote resolves against the schedule in effect
at its occurrence time, so the superseded August rates stay queryable.
The stored provider price remains the peak anchor. Dynamic resolution is enabled only for PAYG configurations whose complete rate card exactly matches an official anchor. A config that still holds the previous generation's anchor is recognized as an untouched official row and billed against the live schedule, so a dated vendor price change reaches it without a re-save. Custom endpoints, edited prices, and unrecognized models remain static. Persisted session, ledger, and stats quotes are never repriced.
Wallets and diagnostics
Wallets are never converted or cross-added. An explicit target uses the exact matching wallet; if it is absent, the real currency is shown with an ISO prefix. Automatic mode uses a single valid wallet currency only as a runtime hint. Multiple/unknown/error responses do not affect the cost facts.
reasonix doctor billing
reasonix doctor billing --json
The compatible fx report is always enabled=false and has no cache. The
report also lists the automatic selection policy, pricing-table currencies,
and official-catalog matches.