contents/ui/ProviderNormalizer.js
.pragma library
.import "Guards.js" as Guards
.import "ProviderIdentity.js" as ProviderIdentity
.import "SafeText.js" as SafeText
// Pure normalization of untrusted `codexbar` CLI payloads.
//
// `main.qml` owns the two halves that used to share the `parse*` prefix: the
// process lifecycle, the root properties, the error text, and the loading flag
// stay there, while the raw-JSON-to-validated-structure half lives here. The
// split is mechanical: nothing in this file may touch `root`, a QML property,
// `Qt`, `Kirigami`, or `i18n`.
//
// The `i18n` exclusion is not a style rule. This file is in the gettext
// extraction glob, so a user-facing string here would be extracted untranslated.
// That is why the label of a rate window, the reset description, and the cost
// section titles stay in `main.qml` and only the numbers, the bounds, and the
// shape checks moved: `rateWindowMetrics` returns clamped percentages and the
// caller supplies the words.
//
// Every bound below is a delegate/allocation budget applied to attacker- or
// bug-influenced array lengths, so a malformed payload truncates instead of
// hanging the popup. They are asserted by `scripts/test_security_regressions.sh`
// and exercised by `tests/tst_provider_normalizer.qml`.
var maximumProviderSnapshots = 256
var maximumAccountSnapshots = 128
var maximumCostSnapshots = 256
var maximumCostProjects = 128
var maximumExtraRateWindows = 24
var maximumSessions = 128
var maximumCostHistoryPoints = 365
var maximumCostHistoryScanItems = 2048
var maximumModelBreakdownsPerDay = 128
var maximumCostModelRows = 6
// Coverage counters are metadata, not allocation sizes, but bounding them keeps
// later aggregation inside an exact and intentionally small numeric domain.
var maximumCostCoverageCount = 1000000000
// Longest CLI-controlled pace ETA we will keep, in seconds.
var maximumPaceEtaSeconds = 31536000
function hasOwnKey(item, key) {
return Guards.hasOwnKey(item, key)
}
function isUnsafeObjectKey(key) {
return Guards.isUnsafeObjectKey(key)
}
// A CLI record is a plain object. Arrays and null are rejected because every
// caller indexes named fields on the result.
function isCliRecord(value) {
return value !== null && typeof value === "object" && !Array.isArray(value)
}
function copyObject(item) {
return Guards.copyObject(item)
}
function clamp(value, minimum, maximum) {
return Math.max(minimum, Math.min(maximum, value))
}
function strictFiniteNumber(value) {
var numericType = typeof value === "number"
|| (typeof value === "string" && value.trim().length > 0)
if (!numericType) {
return Number.NaN
}
var numeric = Number(value)
return isFinite(numeric) ? numeric : Number.NaN
}
function firstStrictFiniteNumber(preferred, fallback) {
var numeric = strictFiniteNumber(preferred)
return isFinite(numeric) ? numeric : strictFiniteNumber(fallback)
}
function boundedDisplayText(value, maximumLength) {
var limit = Number(maximumLength)
if (!isFinite(limit) || limit <= 0) {
limit = 500
}
limit = Math.min(2000, Math.floor(limit))
return SafeText.cliMessage(value, limit)
}
// The canonical map key for a provider: CLI aliases resolved, then screened for
// keys that would reach Object.prototype or smuggle control characters.
function providerSnapshotKey(providerID) {
return ProviderIdentity.providerMapKey(ProviderIdentity.resolveProviderKey(providerID))
}
// "" for anything that cannot name a provider, so callers skip the entry rather
// than inventing an id from a non-string or an oversized payload field.
function normalizedProviderID(value) {
return ProviderIdentity.normalizedProviderID(value)
}
// `config providers` output reduced to the enabled provider ids plus every
// display name it carried. A disabled provider still contributes its name,
// because the popup labels providers it learns about from a later usage payload.
// null means the envelope had no provider-config records. An empty array is a
// valid success and remains distinguishable from an error-shaped JSON payload.
function normalizeProviderConfigEntries(payload) {
var providerIDs = []
var displayNames = ({})
var seenProviderIDs = ({})
var items = Array.isArray(payload) ? payload : [payload]
var sawProviderConfigEntry = items.length === 0
var itemLimit = Math.min(items.length, maximumProviderSnapshots)
for (var i = 0; i < itemLimit; i++) {
if (!isCliRecord(items[i])
|| !hasOwnKey(items[i], "enabled")
|| typeof items[i].enabled !== "boolean") {
continue
}
var providerID = normalizedProviderID(items[i].provider)
if (providerID.length === 0) {
continue
}
sawProviderConfigEntry = true
var displayName = boundedDisplayText(items[i].displayName, 120)
if (displayName.length > 0) {
displayNames[providerID] = displayName
}
if (items[i].enabled && !hasOwnKey(seenProviderIDs, providerID)) {
seenProviderIDs[providerID] = true
providerIDs.push(providerID)
}
}
if (!sawProviderConfigEntry) {
return null
}
return {
providerIDs: providerIDs,
displayNames: displayNames
}
}
function providerSnapshotHasError(item) {
return isCliRecord(item)
&& item.error !== undefined
&& item.error !== null
&& String(item.error).trim().length > 0
}
// Provider tabs are keyed by canonical provider id. Keep their model unique and
// retain the first healthy snapshot when a malformed payload repeats an id.
function dedupeProviderSnapshots(items) {
var result = []
var indexes = ({})
if (!Array.isArray(items)) {
return result
}
var itemLimit = Math.min(items.length, maximumProviderSnapshots)
for (var i = 0; i < itemLimit; i++) {
var item = isCliRecord(items[i]) ? items[i] : null
var providerID = item ? normalizedProviderID(item.provider) : ""
if (providerID.length === 0) {
continue
}
var snapshot = copyObject(item)
snapshot.provider = providerID
var hasError = providerSnapshotHasError(item)
if (!hasOwnKey(indexes, providerID)) {
indexes[providerID] = result.length
result.push(snapshot)
continue
}
var existingIndex = indexes[providerID]
var existing = result[existingIndex]
var existingHasError = providerSnapshotHasError(existing)
if (existingHasError && !hasError) {
result[existingIndex] = snapshot
}
}
return result
}
function normalizeCodexCreditLimit(providerID, creditLimit) {
if (normalizedProviderID(providerID) !== "codex" || !isCliRecord(creditLimit)) {
return null
}
var requiredAmountFields = ["used", "limit", "remaining", "remainingPercent"]
for (var fieldIndex = 0; fieldIndex < requiredAmountFields.length; fieldIndex++) {
if (!hasOwnKey(creditLimit, requiredAmountFields[fieldIndex])) {
return null
}
}
var used = strictFiniteNumber(creditLimit.used)
var limit = strictFiniteNumber(creditLimit.limit)
var remaining = strictFiniteNumber(creditLimit.remaining)
var remainingPercent = strictFiniteNumber(creditLimit.remainingPercent)
if (!isFinite(used) || used < 0
|| !isFinite(limit) || limit <= 0
|| !isFinite(remaining) || remaining < 0
|| !isFinite(remainingPercent)) {
return null
}
remainingPercent = clamp(remainingPercent, 0, 100)
return {
title: boundedDisplayText(hasOwnKey(creditLimit, "title") ? creditLimit.title : "", 120),
used: used,
limit: limit,
remaining: remaining,
usedPercent: 100 - remainingPercent,
leftPercent: remainingPercent,
resetsAt: boundedDisplayText(hasOwnKey(creditLimit, "resetsAt") ? creditLimit.resetsAt : "", 128)
}
}
// The numeric half of one usage row. `null` when the payload carries no window
// record at all, which is how the caller distinguishes "no such window" from
// "window present but percentage unknown" (`hasPercent === false`).
//
// A provider that reports 137% used, or a negative percentage, must not paint
// outside its meter, so both the used and the pace percentages are clamped.
// `pacePercent` is -1 rather than 0 when the CLI omits it: an omitted pace is
// not a pace of zero, and a marker drawn at 0 would claim the provider is
// comfortably ahead of schedule.
function rateWindowMetrics(window, pace, usageKnown) {
if (!isCliRecord(window)) {
return null
}
var known = usageKnown !== false
var used = strictFiniteNumber(window.usedPercent)
var hasPercent = known && isFinite(used)
var expectedUsed = strictFiniteNumber(pace && pace.expectedUsedPercent)
var paceEta = strictFiniteNumber(pace && pace.etaSeconds)
var paceWillLastKnown = isCliRecord(pace)
&& hasOwnKey(pace, "willLastToReset")
&& typeof pace.willLastToReset === "boolean"
var paceKnown = paceWillLastKnown
&& (pace.willLastToReset === true || (isFinite(paceEta) && paceEta > 0))
var paceValue = isFinite(expectedUsed)
? clamp(expectedUsed, 0, 100)
: -1
return {
hasPercent: hasPercent,
usedPercent: hasPercent ? clamp(used, 0, 100) : 0,
leftPercent: hasPercent ? clamp(100 - used, 0, 100) : 0,
paceKnown: paceKnown,
pacePercent: paceValue,
paceOnTop: !pace || pace.willLastToReset !== false,
paceEtaSeconds: isFinite(paceEta)
? Math.max(0, Math.min(maximumPaceEtaSeconds, paceEta))
: 0
}
}
function statusSeverity(status) {
if (!status) {
return ""
}
var indicator = String(status.indicator || "").toLowerCase()
switch (indicator) {
case "minor":
case "maintenance":
case "major":
case "critical":
case "unknown":
return indicator
default:
return ""
}
}
function statusIncidentKey(status) {
if (!status) {
return ""
}
var keys = [
"incidentId",
"incident_id",
"incidentID",
"id"
]
for (var i = 0; i < keys.length; i++) {
var value = status[keys[i]]
if (value !== null && value !== undefined && String(value).length > 0) {
return String(value)
}
}
var incident = status.incident || null
if (incident && incident.id !== null && incident.id !== undefined && String(incident.id).length > 0) {
return String(incident.id)
}
return ""
}
function httpsUrlHost(url) {
var match = String(url || "").trim().match(/^https:\/\/([^\/?#]+)/i)
return match ? match[1].toLowerCase() : ""
}
// A provider-supplied status URL is honored only when it stays on the host of
// the URL we already ship for that provider, so a compromised payload cannot
// point the status action anywhere else. The fallback is passed in rather than
// looked up here, which keeps the host comparison testable on its own.
function safeStatusUrl(fallbackStatusUrl, url) {
var fallback = String(fallbackStatusUrl || "")
var fallbackHost = httpsUrlHost(fallback)
var candidate = String(url || "").trim()
var candidateHost = httpsUrlHost(candidate)
if (fallbackHost.length === 0) {
return ""
}
if (candidateHost.length === 0) {
return fallback
}
return candidateHost === fallbackHost ? candidate : fallback
}
function accountLabel(item) {
if (!item) {
return ""
}
if (typeof item.account === "string" && item.account.length > 0) {
return item.account
}
if (typeof item.organization === "string" && item.organization.length > 0) {
return item.organization
}
if (typeof item.loginMethod === "string" && item.loginMethod.length > 0) {
return item.loginMethod
}
return ""
}
function dedupeAccountOptions(items) {
if (!Array.isArray(items)) {
return []
}
var seen = ({})
var result = []
var itemLimit = Math.min(items.length, maximumAccountSnapshots)
for (var i = 0; i < itemLimit; i++) {
var label = accountLabel(items[i])
var key = "account:" + label
if (label.length === 0 || hasOwnKey(seen, key)) {
continue
}
seen[key] = true
result.push(items[i])
}
return result
}
function isMissingTokenAccountsError(errorMessage) {
// codexbar --all-accounts reports "No token accounts configured for
// <provider>." even when the provider works through OAuth/CLI auth
// without named token accounts; that is an empty list, not a failure.
return String(errorMessage || "").toLowerCase().indexOf("no token accounts configured") !== -1
}
function normalizeSession(item) {
if (!isCliRecord(item)) {
return null
}
var providerID = normalizedProviderID(item.provider)
var projectName = boundedDisplayText(item.projectName, 160)
var sessionName = boundedDisplayText(item.sessionName, 240)
var host = boundedDisplayText(item.host, 160)
var state = boundedDisplayText(item.state, 40).toLowerCase()
var sourceName = boundedDisplayText(item.source, 80)
var activityAt = boundedDisplayText(item.lastActivityAt, 128)
var activityMs = Date.parse(activityAt)
if (!isFinite(activityMs)) {
activityAt = boundedDisplayText(item.startedAt, 128)
activityMs = Date.parse(activityAt)
}
if (!isFinite(activityMs)) {
activityAt = ""
activityMs = 0
}
if (providerID.length === 0 && projectName.length === 0 && sessionName.length === 0) {
return null
}
return {
provider: providerID,
projectName: projectName,
sessionName: sessionName,
host: host,
state: state,
source: sourceName,
activityAt: activityAt,
activityMs: activityMs
}
}
// `null` for a payload shape we do not recognize, which the caller reports as an
// error. An unrecognized shape must not read as an empty successful snapshot,
// because that would silently drop the sessions the user was already looking at.
function normalizeSessions(payload) {
var items
if (Array.isArray(payload)) {
items = payload
} else if (isCliRecord(payload) && Array.isArray(payload.sessions)) {
items = payload.sessions
} else {
return null
}
// The bound applies before the sort, so the kept slice is the first
// `maximumSessions` entries in payload order, then ordered newest first.
// (Pinned by test_sortsSessionsByRecentActivityAndTruncatesAtTheBound.)
var nextSessions = []
var itemLimit = Math.min(items.length, maximumSessions)
for (var i = 0; i < itemLimit; i++) {
var normalized = normalizeSession(items[i])
if (normalized) {
nextSessions.push(normalized)
}
}
nextSessions.sort(function(a, b) { return b.activityMs - a.activityMs })
return nextSessions
}
// NaN when no nonnegative part is usable; an explicitly measured zero stays zero.
function sumTokenParts(inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens) {
var total = 0
var hasObservedPart = false
var values = [inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens]
for (var i = 0; i < values.length; i++) {
var value = strictFiniteNumber(values[i])
if (isFinite(value) && value >= 0) {
hasObservedPart = true
total += value
}
}
return hasObservedPart ? total : Number.NaN
}
function boundedHistoryDays(days) {
var numericDays = strictFiniteNumber(days)
return isFinite(numericDays)
? Math.max(1, Math.min(maximumCostHistoryPoints, numericDays))
: 30
}
function calendarDateKey(year, month, day) {
var monthText = month < 10 ? "0" + month : String(month)
var dayText = day < 10 ? "0" + day : String(day)
return String(year) + "-" + monthText + "-" + dayText
}
function parsedCalendarDateKey(value) {
var match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(value || ""))
if (!match) {
return null
}
var year = Number(match[1])
var month = Number(match[2])
var day = Number(match[3])
if (year < 1970 || month < 1 || month > 12 || day < 1 || day > 31) {
return null
}
var timestampMs = Date.UTC(year, month - 1, day)
var date = new Date(timestampMs)
if (date.getUTCFullYear() !== year
|| date.getUTCMonth() !== month - 1
|| date.getUTCDate() !== day) {
return null
}
return { key: calendarDateKey(year, month, day), timestampMs: timestampMs }
}
function localCalendarDateKey(value) {
if (typeof value !== "string" || value.trim().length === 0 || value.length > 100) {
return ""
}
var dateOnly = parsedCalendarDateKey(value.trim())
if (dateOnly) {
return dateOnly.key
}
var date = new Date(value)
if (!isFinite(date.getTime())) {
return ""
}
return calendarDateKey(date.getFullYear(), date.getMonth() + 1, date.getDate())
}
function costHistoryCalendarWindow(days, updatedAt) {
var endDate = parsedCalendarDateKey(localCalendarDateKey(updatedAt))
if (!endDate) {
return null
}
var historyDays = Math.floor(boundedHistoryDays(days))
return {
firstTimestampMs: endDate.timestampMs - (historyDays - 1) * 24 * 60 * 60 * 1000,
lastTimestampMs: endDate.timestampMs
}
}
function fillMissingCostDays(rows, currency, days, updatedAt, blockedDateKeys) {
if (!rows || rows.length === 0) {
return rows || []
}
var window = costHistoryCalendarWindow(days, updatedAt)
if (!window) {
return rows
}
var historyDays = Math.floor(boundedHistoryDays(days))
var dayMilliseconds = 24 * 60 * 60 * 1000
var firstTimestampMs = window.firstTimestampMs
var byDate = ({})
var hasObservedCost = false
var hasObservedTokens = false
for (var i = 0; i < rows.length; i++) {
var parsed = parsedCalendarDateKey(rows[i].label)
if (!parsed) {
// Preserve malformed labels in the retained tail, but older labels
// discovered by the extended scan must not suppress a healthy window.
if (i >= rows.length - historyDays) {
return rows
}
continue
}
if (parsed.timestampMs < firstTimestampMs || parsed.timestampMs > window.lastTimestampMs) {
continue
}
if (hasOwnKey(byDate, parsed.key)) {
return rows
}
byDate[parsed.key] = rows[i]
hasObservedCost = hasObservedCost
|| (typeof rows[i].cost === "number"
&& isFinite(rows[i].cost))
hasObservedTokens = hasObservedTokens
|| (typeof rows[i].tokens === "number"
&& isFinite(rows[i].tokens))
}
var result = []
var observedInRange = false
var safeCurrency = boundedDisplayText(currency || "USD", 12)
for (var offset = 0; offset < historyDays; offset++) {
var date = new Date(firstTimestampMs + offset * dayMilliseconds)
var key = calendarDateKey(
date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate())
if (hasOwnKey(blockedDateKeys, key) && !hasOwnKey(byDate, key)) {
return rows
}
if (hasOwnKey(byDate, key)) {
result.push(byDate[key])
observedInRange = true
} else {
// Omitted calendar days mean no recorded activity only for metrics
// observed in this snapshot. An unavailable metric stays unknown.
result.push({
label: key,
cost: hasObservedCost ? 0 : null,
tokens: hasObservedTokens ? 0 : null,
inputTokens: 0,
outputTokens: 0,
cacheReadTokens: 0,
cacheCreationTokens: 0,
currency: safeCurrency,
models: [],
modelsTruncated: false
})
}
}
// No observation in a valid window must not revive older or future dates.
// Leave it empty rather than manufacture a zero-cost history without evidence.
return observedInRange ? result : []
}
function normalizedCostCoverage(coverage) {
if (!isCliRecord(coverage)) {
return null
}
var fields = ["priced", "unpriced", "unmetered", "estimated"]
var result = ({})
for (var i = 0; i < fields.length; i++) {
var field = fields[i]
if (!hasOwnKey(coverage, field)) {
return null
}
var value = coverage[field]
if (typeof value !== "number"
|| !isFinite(value)
|| Math.floor(value) !== value
|| value < 0
|| value > maximumCostCoverageCount) {
return null
}
result[field] = value
}
return result
}
// `coverage` and `provenance` describe how trustworthy the top-level cost
// figures are. Preserve only the official bounded contract and translate the
// provenance wire enum before the snapshot becomes QML state; older payloads
// and records whose two axes are both unusable remain quiet.
function normalizeCostTrustMetadata(rawCostRecord) {
if (!isCliRecord(rawCostRecord)) {
return null
}
var coverage = hasOwnKey(rawCostRecord, "coverage")
? normalizedCostCoverage(rawCostRecord.coverage)
: null
var sourceKind = ""
if (hasOwnKey(rawCostRecord, "provenance")
&& typeof rawCostRecord.provenance === "string") {
switch (rawCostRecord.provenance) {
case "listPriceEstimate":
sourceKind = "listPrice"
break
case "vendorMetered":
sourceKind = "vendor"
break
case "mixed":
case "unknown":
sourceKind = rawCostRecord.provenance
break
}
}
return coverage !== null || sourceKind.length > 0
? { coverage: coverage, sourceKind: sourceKind }
: null
}
function costRecordHasError(item) {
return isCliRecord(item)
&& hasOwnKey(item, "error")
&& item.error !== null
&& item.error !== undefined
}
function normalizeCostEnvelope(payload) {
var items
if (Array.isArray(payload)) {
if (payload.length === 0) {
return []
}
items = payload
} else if (isCliRecord(payload)) {
items = [payload]
} else {
return null
}
var result = []
var itemLimit = Math.min(items.length, maximumCostSnapshots)
for (var i = 0; i < itemLimit; i++) {
var item = items[i]
if (!isCliRecord(item)) {
continue
}
var providerID = normalizedProviderID(item.provider)
var itemHasError = costRecordHasError(item)
if (providerID.length === 0 && itemHasError) {
return null
}
if (providerID.length === 0) {
continue
}
var snapshot = copyObject(item)
snapshot.provider = providerID
result.push(snapshot)
}
return result.length > 0 ? result : null
}
// A partial cost response is a complete fresh snapshot except for the providers
// named by error records. Carry only those providers forward; copying the whole
// previous map would retain providers that the CLI no longer reports.
function mergeCostSnapshotsAfterPartialFailure(previousSnapshots, freshSnapshots,
failedProviderIDs) {
var merged = isCliRecord(freshSnapshots) ? copyObject(freshSnapshots) : ({})
if (!isCliRecord(previousSnapshots) || !Array.isArray(failedProviderIDs)) {
return merged
}
var itemLimit = Math.min(failedProviderIDs.length, maximumCostSnapshots)
for (var i = 0; i < itemLimit; i++) {
var providerID = normalizedProviderID(failedProviderIDs[i])
if (providerID.length > 0
&& !hasOwnKey(merged, providerID)
&& hasOwnKey(previousSnapshots, providerID)) {
merged[providerID] = previousSnapshots[providerID]
}
}
return merged
}
// CLI 0.56.2 projects carry local paths and nested source records. Retain only
// the display name and range totals; project names are labels, never map keys.
function normalizeCostProjects(items, currency) {
var result = { rows: [], truncated: false }
if (!Array.isArray(items)) {
return result
}
var limit = Math.min(items.length, maximumCostProjects)
result.truncated = items.length > limit
for (var i = 0; i < limit; i++) {
var item = items[i]
if (!isCliRecord(item) || !hasOwnKey(item, "name")
|| typeof item.name !== "string") {
continue
}
var label = boundedDisplayText(item.name, 120)
var cost = hasOwnKey(item, "totalCost") ? strictFiniteNumber(item.totalCost) : NaN
var tokens = hasOwnKey(item, "totalTokens") ? strictFiniteNumber(item.totalTokens) : NaN
if (label.length === 0 || (!isFinite(cost) && !isFinite(tokens))) {
continue
}
result.rows.push({
label: label,
cost: isFinite(cost) ? Math.max(0, cost) : null,
tokens: isFinite(tokens) ? Math.max(0, tokens) : null,
currency: boundedDisplayText(currency || "USD", 12)
})
}
return result
}
function normalizeCostDaily(items, currency, days, updatedAt) {
var result = []
if (!items || !Array.isArray(items)) {
return result
}
var historyDays = Math.floor(boundedHistoryDays(days))
var blockedDateKeys = ({})
var inspectedItems = 0
for (var i = items.length - 1; i >= 0
&& inspectedItems < maximumCostHistoryScanItems; i--) {
inspectedItems++
var item = isCliRecord(items[i]) ? items[i] : null
if (!item) {
continue
}
var label = boundedDisplayText(item.date || item.day || item.dayKey || "", 120)
var cost = firstStrictFiniteNumber(item.totalCost, item.costUSD)
var tokens = firstStrictFiniteNumber(item.totalTokens, item.tokens)
var inputTokens = strictFiniteNumber(item.inputTokens)
var outputTokens = strictFiniteNumber(item.outputTokens)
var cacheReadTokens = strictFiniteNumber(item.cacheReadTokens)
var cacheCreationTokens = firstStrictFiniteNumber(
item.cacheCreationTokens, item.cacheWriteTokens)
var hasTokenParts = (isFinite(inputTokens) && inputTokens >= 0)
|| (isFinite(outputTokens) && outputTokens >= 0)
|| (isFinite(cacheReadTokens) && cacheReadTokens >= 0)
|| (isFinite(cacheCreationTokens) && cacheCreationTokens >= 0)
if (!isFinite(tokens)) {
tokens = sumTokenParts(inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens)
}
if (!isFinite(cost) && !isFinite(tokens) && !hasTokenParts) {
var blockedDate = parsedCalendarDateKey(label)
if (blockedDate) {
blockedDateKeys[blockedDate.key] = true
}
continue
}
var modelSummary = costModelSummary([item], currency)
result.unshift({
label: label,
cost: isFinite(cost) ? Math.max(0, cost) : null,
tokens: isFinite(tokens) ? Math.max(0, tokens) : null,
inputTokens: isFinite(inputTokens) ? Math.max(0, inputTokens) : 0,
outputTokens: isFinite(outputTokens) ? Math.max(0, outputTokens) : 0,
cacheReadTokens: isFinite(cacheReadTokens) ? Math.max(0, cacheReadTokens) : 0,
cacheCreationTokens: isFinite(cacheCreationTokens) ? Math.max(0, cacheCreationTokens) : 0,
currency: boundedDisplayText(currency || "USD", 12),
models: modelSummary.rows,
modelsTruncated: modelSummary.truncated
})
}
// Inspect beyond the display limit: earlier records may contain valid or
// malformed days inside the window, so they must be known before filling.
if (i >= 0 && inspectedItems >= maximumCostHistoryScanItems) {
return result.slice(-historyDays)
}
return fillMissingCostDays(result, currency, historyDays, updatedAt, blockedDateKeys)
.slice(-historyDays)
}
function normalizeCostTotals(totals, fallbackCost, fallbackTokens, currency) {
var source = totals || ({})
var cost = firstStrictFiniteNumber(source.totalCost, fallbackCost)
var tokens = firstStrictFiniteNumber(source.totalTokens, fallbackTokens)
var inputTokens = strictFiniteNumber(source.inputTokens)
var outputTokens = strictFiniteNumber(source.outputTokens)
var cacheReadTokens = strictFiniteNumber(source.cacheReadTokens)
var cacheCreationTokens = firstStrictFiniteNumber(
source.cacheCreationTokens, source.cacheWriteTokens)
if (!isFinite(tokens)) {
tokens = sumTokenParts(inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens)
}
return {
cost: isFinite(cost) ? Math.max(0, cost) : null,
tokens: isFinite(tokens) ? Math.max(0, tokens) : null,
inputTokens: isFinite(inputTokens) ? Math.max(0, inputTokens) : 0,
outputTokens: isFinite(outputTokens) ? Math.max(0, outputTokens) : 0,
cacheReadTokens: isFinite(cacheReadTokens) ? Math.max(0, cacheReadTokens) : 0,
cacheCreationTokens: isFinite(cacheCreationTokens) ? Math.max(0, cacheCreationTokens) : 0,
currency: boundedDisplayText(currency || "USD", 12)
}
}
// CodexBar 0.56.2 reports zero cost for Antigravity when today has no usage
// or the whole history is empty, even though the provider has no pricing.
function normalizeProviderCostAmount(providerID, amount) {
var cost = strictFiniteNumber(amount)
return normalizedProviderID(providerID) !== "antigravity" && isFinite(cost)
? cost : null
}
function normalizeProviderCostTotals(providerID, totals, fallbackCost,
fallbackTokens, currency) {
var result = normalizeCostTotals(
totals, fallbackCost, fallbackTokens, currency)
result.cost = normalizeProviderCostAmount(providerID, result.cost)
return result
}
function normalizeCostModels(items, currency, days, updatedAt) {
if (!items || !Array.isArray(items)) {
return { rows: [], truncated: false }
}
// Match the daily history's whole-day budget, including the legacy tail.
var historyDays = Math.floor(boundedHistoryDays(days))
var firstItem = Math.max(0, items.length - historyDays)
var window = costHistoryCalendarWindow(historyDays, updatedAt)
var firstInspectedItem = window
? Math.max(0, items.length - maximumCostHistoryScanItems) : firstItem
var modelDays = []
for (var itemIndex = items.length - 1; itemIndex >= firstInspectedItem
&& modelDays.length < historyDays; itemIndex--) {
var item = isCliRecord(items[itemIndex]) ? items[itemIndex] : null
if (!item) {
continue
}
if (window) {
var parsed = parsedCalendarDateKey(boundedDisplayText(
item.date || item.day || item.dayKey || "", 120))
if (parsed && (parsed.timestampMs < window.firstTimestampMs
|| parsed.timestampMs > window.lastTimestampMs)) {
continue
}
// Undated legacy records retain the original bounded tail behavior.
if (!parsed && itemIndex < firstItem) {
continue
}
}
modelDays.unshift(item)
}
return costModelSummary(modelDays, currency)
}
function costModelSummary(modelDays, currency) {
var byName = ({})
var truncated = false
for (var i = 0; i < modelDays.length; i++) {
var breakdowns = hasOwnKey(modelDays[i], "modelBreakdowns")
&& Array.isArray(modelDays[i].modelBreakdowns)
? modelDays[i].modelBreakdowns
: []
var breakdownLimit = Math.min(breakdowns.length, maximumModelBreakdownsPerDay)
truncated = truncated || breakdowns.length > breakdownLimit
for (var j = 0; j < breakdownLimit; j++) {
var breakdown = breakdowns[j]
if (!isCliRecord(breakdown)) {
continue
}
var rawName = hasOwnKey(breakdown, "modelName") ? breakdown.modelName : ""
if ((typeof rawName !== "string" || rawName.length === 0)
&& hasOwnKey(breakdown, "model")) {
rawName = breakdown.model
}
var name = typeof rawName === "string" ? boundedDisplayText(rawName, 120) : ""
if (name.length === 0 || isUnsafeObjectKey(name)) {
continue
}
var cost = firstStrictFiniteNumber(
hasOwnKey(breakdown, "cost") ? breakdown.cost : undefined,
hasOwnKey(breakdown, "totalCost") ? breakdown.totalCost : undefined)
var tokens = firstStrictFiniteNumber(
hasOwnKey(breakdown, "totalTokens") ? breakdown.totalTokens : undefined,
hasOwnKey(breakdown, "tokens") ? breakdown.tokens : undefined)
if (!isFinite(cost) && !isFinite(tokens)) {
continue
}
if (!hasOwnKey(byName, name)) {
byName[name] = {
label: name,
cost: null,
tokens: null,
currency: boundedDisplayText(currency || "USD", 12),
costOverflow: false,
tokensOverflow: false
}
}
var aggregate = byName[name]
// Later finite rows must not revive an overflowed sum as a partial total.
if (isFinite(cost) && !aggregate.costOverflow) {
var totalCost = (aggregate.cost === null ? 0 : aggregate.cost) + Math.max(0, cost)
aggregate.costOverflow = !isFinite(totalCost)
aggregate.cost = aggregate.costOverflow ? null : totalCost
}
if (isFinite(tokens) && !aggregate.tokensOverflow) {
var totalTokens = (aggregate.tokens === null ? 0 : aggregate.tokens) + Math.max(0, tokens)
aggregate.tokensOverflow = !isFinite(totalTokens)
aggregate.tokens = aggregate.tokensOverflow ? null : totalTokens
}
}
}
var rows = []
for (var modelName in byName) {
if (!hasOwnKey(byName, modelName)) {
continue
}
var model = byName[modelName]
rows.push({ label: model.label, cost: model.cost, tokens: model.tokens, currency: model.currency })
}
rows.sort(function(a, b) {
var aCost = a.cost === null ? 0 : a.cost
var bCost = b.cost === null ? 0 : b.cost
if (bCost !== aCost) {
return bCost > aCost ? 1 : -1
}
var aTokens = a.tokens === null ? 0 : a.tokens
var bTokens = b.tokens === null ? 0 : b.tokens
if (bTokens !== aTokens) {
return bTokens > aTokens ? 1 : -1
}
return a.label === b.label ? 0 : (a.label < b.label ? -1 : 1)
})
return {
rows: rows.slice(0, maximumCostModelRows),
truncated: truncated || rows.length > maximumCostModelRows
}
}