mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-02 02:05:57 +08:00
fix(usage): pricing routing, SSE lifecycle, and validation hardening
* model pricing routing: extend prefix-match families (gpt-/o1-o5/
gemini-/deepseek-/qwen-/glm-/kimi-/minimax-) with per-family dash
thresholds so short base IDs like gpt-5 no longer mis-match
gpt-5-mini; strip ISO and 8-digit date suffixes via UTF-8-safe
byte matching so claude-haiku-4-5-20251001 falls back to
claude-haiku-4-5 pricing
* SSE collector: SseUsageFinishGuard (RAII) guarantees finish() on
early return or panic; AtomicBool fast path lets push() skip the
Mutex once first-event time is recorded
* validation: shared validate_cost_multiplier / validate_pricing_source
helpers across DAO and service layers; PRICING_SOURCE_RESPONSE /
PRICING_SOURCE_REQUEST constants replace string literals; price
fields in update_model_pricing now reject empty / non-decimal /
negative input before INSERT
* backfill: add backfill_missing_usage_costs_for_model so a single
price edit only scans matching rows instead of the full log table;
startup backfill remains full-scan
* session_usage{,_codex,_gemini}: share find_model_pricing helper from
usage_stats; metadata_modified_nanos centralizes mtime precision
* frontend: NON_NEGATIVE_DECIMAL_REGEX + isNonNegativeDecimalString
replace three copies of the same multiplier regex; isUnpricedUsage
surfaces zero-cost rows that have usage tokens (cached per row to
avoid double evaluation); invalidate usageKeys.all on pricing mutate
so backfilled rows refresh
This commit is contained in:
@@ -195,6 +195,50 @@ export function getFreshInputTokens(log: CacheNormalizableLog): number {
|
||||
return log.inputTokens;
|
||||
}
|
||||
|
||||
export const NON_NEGATIVE_DECIMAL_REGEX = /^\d+(?:\.\d+)?$/;
|
||||
|
||||
export function isNonNegativeDecimalString(value: string): boolean {
|
||||
const trimmed = value.trim();
|
||||
if (!NON_NEGATIVE_DECIMAL_REGEX.test(trimmed)) return false;
|
||||
return Number.isFinite(Number(trimmed));
|
||||
}
|
||||
|
||||
type UsageCostLog = Pick<
|
||||
RequestLog,
|
||||
| "inputTokens"
|
||||
| "outputTokens"
|
||||
| "cacheReadTokens"
|
||||
| "cacheCreationTokens"
|
||||
| "totalCostUsd"
|
||||
| "statusCode"
|
||||
> &
|
||||
Partial<Pick<RequestLog, "costMultiplier">>;
|
||||
|
||||
export function hasUsageTokens(log: UsageCostLog): boolean {
|
||||
return (
|
||||
log.inputTokens > 0 ||
|
||||
log.outputTokens > 0 ||
|
||||
log.cacheReadTokens > 0 ||
|
||||
log.cacheCreationTokens > 0
|
||||
);
|
||||
}
|
||||
|
||||
export function isUnpricedUsage(log: UsageCostLog): boolean {
|
||||
const totalCost = Number.parseFloat(log.totalCostUsd);
|
||||
const multiplier =
|
||||
log.costMultiplier == null
|
||||
? undefined
|
||||
: Number.parseFloat(log.costMultiplier);
|
||||
return (
|
||||
log.statusCode >= 200 &&
|
||||
log.statusCode < 300 &&
|
||||
hasUsageTokens(log) &&
|
||||
Number.isFinite(totalCost) &&
|
||||
(!Number.isFinite(multiplier) || multiplier !== 0) &&
|
||||
totalCost === 0
|
||||
);
|
||||
}
|
||||
|
||||
export interface StatsFilters {
|
||||
timeRange: UsageRangePreset;
|
||||
providerId?: string;
|
||||
|
||||
Reference in New Issue
Block a user