refactor: dedupe finite number coercion helper

This commit is contained in:
Peter Steinberger
2026-04-06 22:17:08 +01:00
parent cfebdee073
commit 1a08d23e09
5 changed files with 16 additions and 34 deletions

View File

@@ -1,3 +1,5 @@
import { asFiniteNumber } from "../shared/number-coercion.js";
export type UsageLike = {
input?: number;
output?: number;
@@ -68,16 +70,6 @@ export function makeZeroUsageSnapshot(): AssistantUsageSnapshot {
};
}
const asFiniteNumber = (value: unknown): number | undefined => {
if (typeof value !== "number") {
return undefined;
}
if (!Number.isFinite(value)) {
return undefined;
}
return value;
};
export function hasNonzeroUsage(usage?: NormalizedUsage | null): usage is NormalizedUsage {
if (!usage) {
return false;

View File

@@ -1,3 +1,4 @@
import { asFiniteNumber } from "../shared/number-coercion.js";
import { sleep } from "../utils.js";
import { generateSecureFraction } from "./secure-random.js";
@@ -30,9 +31,6 @@ const DEFAULT_RETRY_CONFIG = {
jitter: 0,
};
const asFiniteNumber = (value: unknown): number | undefined =>
typeof value === "number" && Number.isFinite(value) ? value : undefined;
const clampNumber = (value: unknown, fallback: number, min?: number, max?: number) => {
const next = asFiniteNumber(value);
if (next === undefined) {

View File

@@ -18,6 +18,7 @@ import {
} from "../config/sessions/paths.js";
import type { SessionEntry } from "../config/sessions/types.js";
import { stripEnvelope, stripMessageIdHints } from "../shared/chat-envelope.js";
import { asFiniteNumber } from "../shared/number-coercion.js";
import { countToolResults, extractToolCallNames } from "../utils/transcript-tools.js";
import { estimateUsageCost, resolveModelCostConfig } from "../utils/usage-format.js";
import type {
@@ -74,16 +75,6 @@ const emptyTotals = (): CostUsageTotals => ({
missingCostEntries: 0,
});
const toFiniteNumber = (value: unknown): number | undefined => {
if (typeof value !== "number") {
return undefined;
}
if (!Number.isFinite(value)) {
return undefined;
}
return value;
};
const extractCostBreakdown = (usageRaw?: UsageLike | null): CostBreakdown | undefined => {
if (!usageRaw || typeof usageRaw !== "object") {
return undefined;
@@ -94,17 +85,17 @@ const extractCostBreakdown = (usageRaw?: UsageLike | null): CostBreakdown | unde
return undefined;
}
const total = toFiniteNumber(cost.total);
const total = asFiniteNumber(cost.total);
if (total === undefined || total < 0) {
return undefined;
}
return {
total,
input: toFiniteNumber(cost.input),
output: toFiniteNumber(cost.output),
cacheRead: toFiniteNumber(cost.cacheRead),
cacheWrite: toFiniteNumber(cost.cacheWrite),
input: asFiniteNumber(cost.input),
output: asFiniteNumber(cost.output),
cacheRead: asFiniteNumber(cost.cacheRead),
cacheWrite: asFiniteNumber(cost.cacheWrite),
};
};
@@ -117,7 +108,7 @@ const parseTimestamp = (entry: Record<string, unknown>): Date | undefined => {
}
}
const message = entry.message as Record<string, unknown> | undefined;
const messageTimestamp = toFiniteNumber(message?.timestamp);
const messageTimestamp = asFiniteNumber(message?.timestamp);
if (messageTimestamp !== undefined) {
const parsed = new Date(messageTimestamp);
if (!Number.isNaN(parsed.valueOf())) {
@@ -152,7 +143,7 @@ const parseTranscriptEntry = (entry: Record<string, unknown>): ParsedTranscriptE
const costBreakdown = extractCostBreakdown(usageRaw);
const stopReason = typeof message.stopReason === "string" ? message.stopReason : undefined;
const durationMs = toFiniteNumber(message.durationMs ?? entry.durationMs);
const durationMs = asFiniteNumber(message.durationMs ?? entry.durationMs);
return {
message,

View File

@@ -0,0 +1,3 @@
export function asFiniteNumber(value: unknown): number | undefined {
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
}

View File

@@ -1,3 +1,5 @@
export { asFiniteNumber } from "../shared/number-coercion.js";
export function trimToUndefined(value: unknown): string | undefined {
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
}
@@ -6,10 +8,6 @@ export function asBoolean(value: unknown): boolean | undefined {
return typeof value === "boolean" ? value : undefined;
}
export function asFiniteNumber(value: unknown): number | undefined {
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
}
export function asObject(value: unknown): Record<string, unknown> | undefined {
return typeof value === "object" && value !== null && !Array.isArray(value)
? (value as Record<string, unknown>)