import { c as normalizeOptionalString } from "./string-coerce-DW4mBlAt.js";
import { r as truncateUtf16Safe } from "./utf16-slice-lH-m0h6-.js";
import { s as asFiniteNumber } from "./number-coercion-CJQ8TR--.js";
import { r as formatErrorMessage } from "./errors-DMsu67n4.js";
import { t as createSubsystemLogger } from "./subsystem-X_t1gG1U.js";
import { Y as executeSqliteQuerySync, Z as getNodeSqliteKysely } from "./openclaw-state-db-BcDMx5Fu.js";
import { n as normalizeAgentId } from "./agent-id-DDgUze4y.js";
import { t as runTasksWithConcurrency } from "./run-with-concurrency-BHgpSCM6.js";
import { _ as runOpenClawAgentWriteTransaction, h as openOpenClawAgentDatabase, v as resolveOpenClawAgentSqlitePath } from "./openclaw-agent-db-C4OFXhEu.js";
import { Dt as listSessionTranscriptInstances, E as loadTranscriptEventRowsAfterSeqSync, M as readTranscriptEventAtSeqSync, N as readTranscriptStatsSync, O as loadTranscriptEventsSync, Rt as normalizeUsage } from "./session-accessor-DqjaXZhO.js";
import { b as parseSessionArchiveTimestamp, c as resolveSessionTranscriptsDirForAgent, g as isSessionArchiveArtifactName, h as isPrimarySessionTranscriptFileName, n as resolveDefaultSessionStorePath, r as resolveSessionFilePath, w as materializeSessionArchiveForRead, x as parseUsageCountedSessionIdFromFileName, y as isUsageCountedSessionTranscriptFileName } from "./paths-CPV1Wjwt.js";
import { t as resolveSqliteTargetFromSessionStorePath } from "./session-sqlite-target-DlWfqE9y.js";
import { n as parseSqliteSessionFileMarker, t as formatSqliteSessionFileMarker } from "./sqlite-marker-BejbySI1.js";
import { n as isSessionTranscriptLeafControl, s as scanSessionTranscriptTree, t as isCanonicalSessionTranscriptEntry } from "./transcript-tree-DuZTyiYZ.js";
import { p as selectVisibleTranscriptEvents } from "./session-transcript-index-CTRief9v.js";
import { r as stripInboundMetadata } from "./strip-inbound-meta-CbJ4Y6Dq.js";
import { n as extractToolCallNames, t as countToolResults } from "./transcript-tools-CZ1jNJJI.js";
import { n as stripMessageIdHints, t as stripEnvelope } from "./chat-envelope-br4jVgj4.js";
import { a as resolveModelCostConfigFingerprint, i as resolveModelCostConfig, t as estimateUsageCost } from "./usage-format-DdluABsY.js";
import { t as createTimeZoneDayKeyFormatter } from "./format-datetime-Bp7Mn3G9.js";
import { n as cloneCostUsageTotals, r as createEmptyCostUsageTotals, t as addCostUsageTotals } from "./session-cost-usage-totals-D4e-85ui.js";
import { createHash } from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import readline from "node:readline";
//#region src/infra/session-cost-usage-cache.sqlite.ts
const LEGACY_CACHE_SCOPE = "session-cost-usage";
const LEGACY_CACHE_KEY = "cache";
const REFRESH_LOCK_KEY = "refresh-lock";
const ROLLUP_SCOPE = "session-cost-usage-rollup-v1";
function openCacheDatabase(agentId, databasePath) {
	return openOpenClawAgentDatabase({
		agentId: normalizeAgentId(agentId),
		...databasePath ? { path: databasePath } : {}
	});
}
function readCacheValue(agentId, scope, key, databasePath) {
	const database = openCacheDatabase(agentId, databasePath);
	const kysely = getNodeSqliteKysely(database.db);
	return executeSqliteQuerySync(database.db, kysely.selectFrom("cache_entries").select("value_json").where("scope", "=", scope).where("key", "=", key).limit(1)).rows[0]?.value_json ?? null;
}
function deleteCacheValueIfUnchanged(params) {
	runOpenClawAgentWriteTransaction((database) => {
		const kysely = getNodeSqliteKysely(database.db);
		executeSqliteQuerySync(database.db, kysely.deleteFrom("cache_entries").where("scope", "=", params.scope).where("key", "=", params.key).where("value_json", "=", params.valueJson));
	}, {
		agentId: normalizeAgentId(params.agentId),
		...params.databasePath ? { path: params.databasePath } : {}
	}, { operationLabel: `session-cost-usage.${params.key}.delete` });
}
function readSessionCostUsageRollupRows(agentId, databasePath) {
	const database = openCacheDatabase(agentId, databasePath);
	const kysely = getNodeSqliteKysely(database.db);
	return executeSqliteQuerySync(database.db, kysely.selectFrom("cache_entries").select([
		"key",
		"value_json",
		"updated_at"
	]).where("scope", "=", ROLLUP_SCOPE)).rows.flatMap((row) => row.value_json === null ? [] : [{
		key: row.key,
		valueJson: row.value_json,
		updatedAt: row.updated_at
	}]);
}
function writeSessionCostUsageRollup(params) {
	return runOpenClawAgentWriteTransaction((database) => {
		const kysely = getNodeSqliteKysely(database.db);
		if ((executeSqliteQuerySync(database.db, kysely.selectFrom("cache_entries").select("value_json").where("scope", "=", ROLLUP_SCOPE).where("key", "=", params.rollupId).limit(1)).rows[0]?.value_json ?? null) !== params.previousValueJson) return false;
		executeSqliteQuerySync(database.db, kysely.insertInto("cache_entries").values({
			scope: ROLLUP_SCOPE,
			key: params.rollupId,
			value_json: params.valueJson,
			blob: null,
			expires_at: null,
			updated_at: params.updatedAt
		}).onConflict((conflict) => conflict.columns(["scope", "key"]).doUpdateSet({
			value_json: params.valueJson,
			blob: null,
			expires_at: null,
			updated_at: params.updatedAt
		})));
		return true;
	}, {
		agentId: normalizeAgentId(params.agentId),
		...params.databasePath ? { path: params.databasePath } : {}
	}, { operationLabel: "session-cost-usage.rollup.write" });
}
function deleteSessionCostUsageRollupsExcept(params) {
	const existing = readSessionCostUsageRollupRows(params.agentId, params.databasePath).map((row) => row.key).filter((key) => !params.liveKeys.has(key));
	runOpenClawAgentWriteTransaction((database) => {
		const kysely = getNodeSqliteKysely(database.db);
		for (const key of existing) executeSqliteQuerySync(database.db, kysely.deleteFrom("cache_entries").where("scope", "=", ROLLUP_SCOPE).where("key", "=", key));
		executeSqliteQuerySync(database.db, kysely.deleteFrom("cache_entries").where("scope", "=", LEGACY_CACHE_SCOPE).where("key", "=", LEGACY_CACHE_KEY));
	}, {
		agentId: normalizeAgentId(params.agentId),
		...params.databasePath ? { path: params.databasePath } : {}
	}, { operationLabel: "session-cost-usage.rollup.prune" });
}
function parseRefreshLock(raw) {
	if (!raw) return null;
	try {
		const value = JSON.parse(raw);
		if (!value || typeof value.pid !== "number" || !Number.isInteger(value.pid) || value.pid <= 0 || typeof value.startedAt !== "number" || !Number.isFinite(value.startedAt) || typeof value.ownerNonce !== "string" || !value.ownerNonce) return null;
		return {
			pid: value.pid,
			startedAt: value.startedAt,
			ownerNonce: value.ownerNonce
		};
	} catch {
		return null;
	}
}
function isProcessRunning(pid) {
	try {
		process.kill(pid, 0);
		return true;
	} catch (error) {
		return error.code === "EPERM";
	}
}
function isSessionCostUsageRefreshRunning(agentId, databasePath) {
	const raw = readCacheValue(agentId, LEGACY_CACHE_SCOPE, REFRESH_LOCK_KEY, databasePath);
	const lock = parseRefreshLock(raw);
	if (lock && isProcessRunning(lock.pid)) return true;
	if (raw !== null) deleteCacheValueIfUnchanged({
		agentId,
		databasePath,
		scope: LEGACY_CACHE_SCOPE,
		key: REFRESH_LOCK_KEY,
		valueJson: raw
	});
	return false;
}
function acquireSessionCostUsageRefreshLock(agentId, databasePath) {
	const previousRaw = readCacheValue(agentId, LEGACY_CACHE_SCOPE, REFRESH_LOCK_KEY, databasePath);
	const previousLock = parseRefreshLock(previousRaw);
	const previousOwnerIsRunning = previousLock ? isProcessRunning(previousLock.pid) : false;
	const lock = {
		pid: process.pid,
		startedAt: Date.now(),
		ownerNonce: `${process.pid}:${Date.now()}:${process.hrtime.bigint()}`
	};
	const lockJson = JSON.stringify(lock);
	const acquired = runOpenClawAgentWriteTransaction((database) => {
		const kysely = getNodeSqliteKysely(database.db);
		if ((executeSqliteQuerySync(database.db, kysely.selectFrom("cache_entries").select("value_json").where("scope", "=", LEGACY_CACHE_SCOPE).where("key", "=", REFRESH_LOCK_KEY).limit(1)).rows[0]?.value_json ?? null) !== previousRaw || previousOwnerIsRunning) return false;
		executeSqliteQuerySync(database.db, kysely.insertInto("cache_entries").values({
			scope: LEGACY_CACHE_SCOPE,
			key: REFRESH_LOCK_KEY,
			value_json: lockJson,
			blob: null,
			expires_at: null,
			updated_at: lock.startedAt
		}).onConflict((conflict) => conflict.columns(["scope", "key"]).doUpdateSet({
			value_json: lockJson,
			blob: null,
			expires_at: null,
			updated_at: lock.startedAt
		})));
		return true;
	}, {
		agentId: normalizeAgentId(agentId),
		...databasePath ? { path: databasePath } : {}
	}, { operationLabel: "session-cost-usage.refresh-lock.acquire" });
	return {
		acquired,
		release: () => {
			if (acquired) deleteCacheValueIfUnchanged({
				agentId,
				databasePath,
				scope: LEGACY_CACHE_SCOPE,
				key: REFRESH_LOCK_KEY,
				valueJson: lockJson
			});
		}
	};
}
//#endregion
//#region src/infra/session-cost-usage-rollup.ts
const MAX_LATENCY_MS = 720 * 60 * 1e3;
const MAX_LATENCY_CENTROIDS = 64;
const ERROR_STOP_REASONS = /* @__PURE__ */ new Set([
	"error",
	"aborted",
	"timeout"
]);
function emptyMessageCounts() {
	return {
		total: 0,
		user: 0,
		assistant: 0,
		toolCalls: 0,
		toolResults: 0,
		errors: 0
	};
}
function createLatencyAggregate() {
	return {
		centroids: [],
		count: 0,
		max: 0,
		sum: 0
	};
}
function compressLatencyCentroids(aggregate) {
	while (aggregate.centroids.length > MAX_LATENCY_CENTROIDS) {
		aggregate.centroids.sort((a, b) => a.value - b.value);
		let mergeIndex = 0;
		let smallestGap = Number.POSITIVE_INFINITY;
		for (let index = 1; index < aggregate.centroids.length; index += 1) {
			const gap = (aggregate.centroids[index]?.value ?? 0) - (aggregate.centroids[index - 1]?.value ?? 0);
			if (gap < smallestGap) {
				smallestGap = gap;
				mergeIndex = index - 1;
			}
		}
		const left = aggregate.centroids[mergeIndex];
		const right = aggregate.centroids[mergeIndex + 1];
		if (!left || !right) break;
		const count = left.count + right.count;
		aggregate.centroids.splice(mergeIndex, 2, {
			count,
			value: (left.value * left.count + right.value * right.count) / count
		});
	}
}
function addLatencyValue(aggregate, value) {
	const wasEmpty = aggregate.count === 0;
	aggregate.count += 1;
	aggregate.sum += value;
	aggregate.min = wasEmpty ? value : Math.min(aggregate.min ?? value, value);
	aggregate.max = Math.max(aggregate.max, value);
	aggregate.centroids.push({
		count: 1,
		value
	});
	compressLatencyCentroids(aggregate);
}
function mergeLatencyAggregate(target, source) {
	if (source.count === 0) return;
	const targetWasEmpty = target.count === 0;
	const sourceMin = source.min ?? source.max;
	target.count += source.count;
	target.sum += source.sum;
	target.min = targetWasEmpty ? sourceMin : Math.min(target.min ?? target.max, sourceMin);
	target.max = Math.max(target.max, source.max);
	target.centroids.push(...source.centroids.map((centroid) => ({
		count: centroid.count,
		value: centroid.value
	})));
	compressLatencyCentroids(target);
}
function createUntimestampedRollup() {
	return {
		totals: createEmptyCostUsageTotals(),
		messageCounts: emptyMessageCounts(),
		tools: [],
		models: []
	};
}
function createSessionUsageRollupData() {
	return {
		buckets: {},
		untimestamped: createUntimestampedRollup()
	};
}
function incrementTool(tools, name) {
	const existing = tools.find((entry) => entry.name === name);
	if (existing) existing.count += 1;
	else tools.push({
		name,
		count: 1
	});
}
function mergeTools(target, tools) {
	for (const tool of tools) target.set(tool.name, (target.get(tool.name) ?? 0) + tool.count);
}
function modelKey(provider, model) {
	return `${provider ?? "unknown"}\0${model ?? "unknown"}`;
}
function addModelUsage(models, provider, model, totals) {
	if (!provider && !model) return;
	const modelRef = modelKey(provider, model);
	let existing = models.find((entry) => modelKey(entry.provider, entry.model) === modelRef);
	if (!existing) {
		existing = {
			provider,
			model,
			count: 0,
			totals: createEmptyCostUsageTotals()
		};
		models.push(existing);
	}
	existing.count += 1;
	addCostUsageTotals(existing.totals, totals);
}
function mergeModels(target, models) {
	for (const model of models) {
		const modelRef = modelKey(model.provider, model.model);
		const existing = target.get(modelRef) ?? {
			provider: model.provider,
			model: model.model,
			count: 0,
			totals: createEmptyCostUsageTotals()
		};
		existing.count += model.count;
		addCostUsageTotals(existing.totals, model.totals);
		target.set(modelRef, existing);
	}
}
function addMessageContribution(target, contribution) {
	if (contribution.role === "user") {
		target.user += 1;
		target.total += 1;
	} else if (contribution.role === "assistant") {
		target.assistant += 1;
		target.total += 1;
	}
	target.toolCalls += contribution.toolNames.length;
	target.toolResults += contribution.toolResultCounts.total;
	target.errors += contribution.toolResultCounts.errors;
	if (contribution.stopReason && ERROR_STOP_REASONS.has(contribution.stopReason)) target.errors += 1;
}
function createBucket(timestampMs) {
	return {
		timestampMs,
		totals: createEmptyCostUsageTotals(),
		messageCounts: emptyMessageCounts(),
		tools: [],
		models: [],
		latency: createLatencyAggregate()
	};
}
function appendSessionUsageRollupContribution(rollup, contribution) {
	const timestamp = contribution.timestamp;
	if (timestamp === void 0) {
		addMessageContribution(rollup.untimestamped.messageCounts, contribution);
		for (const toolName of contribution.toolNames) incrementTool(rollup.untimestamped.tools, toolName);
		if (contribution.usageTotals) {
			addCostUsageTotals(rollup.untimestamped.totals, contribution.usageTotals);
			addModelUsage(rollup.untimestamped.models, contribution.provider, contribution.model, contribution.usageTotals);
		}
		return;
	}
	const bucket = rollup.buckets[String(timestamp)] ??= createBucket(timestamp);
	addMessageContribution(bucket.messageCounts, contribution);
	for (const toolName of contribution.toolNames) incrementTool(bucket.tools, toolName);
	if (contribution.usageTotals) {
		addCostUsageTotals(bucket.totals, contribution.usageTotals);
		addModelUsage(bucket.models, contribution.provider, contribution.model, contribution.usageTotals);
	}
	if (contribution.role === "assistant") {
		const sourceUserTimestamp = contribution.durationMs === void 0 ? rollup.lastUserTimestamp : void 0;
		const latencyMs = contribution.durationMs ?? (sourceUserTimestamp !== void 0 ? Math.max(0, timestamp - sourceUserTimestamp) : void 0);
		if (latencyMs !== void 0 && Number.isFinite(latencyMs) && latencyMs <= MAX_LATENCY_MS) addLatencyValue(bucket.latency, latencyMs);
	}
	if (contribution.role === "user") rollup.lastUserTimestamp = timestamp;
}
function computeLatencyStats(aggregate) {
	if (aggregate.count === 0) return;
	const targetCount = Math.ceil(aggregate.count * .95);
	let seen = 0;
	let p95Ms = aggregate.max;
	for (const centroid of aggregate.centroids.toSorted((a, b) => a.value - b.value)) {
		seen += centroid.count;
		if (seen >= targetCount) {
			p95Ms = centroid.value;
			break;
		}
	}
	return {
		count: aggregate.count,
		avgMs: aggregate.sum / aggregate.count,
		p95Ms,
		minMs: aggregate.min ?? aggregate.max,
		maxMs: aggregate.max
	};
}
function getUtcQuarterHourBucketKey(date) {
	const dateKey = `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, "0")}-${String(date.getUTCDate()).padStart(2, "0")}`;
	const quarterIndex = Math.floor((date.getUTCHours() * 60 + date.getUTCMinutes()) / 15);
	return {
		date: dateKey,
		quarterIndex,
		bucketId: `${dateKey}\0${quarterIndex}`
	};
}
function addMessageCounts(target, source) {
	target.total += source.total;
	target.user += source.user;
	target.assistant += source.assistant;
	target.toolCalls += source.toolCalls;
	target.toolResults += source.toolResults;
	target.errors += source.errors;
}
function sortedModelUsage(models) {
	if (models.size === 0) return;
	return Array.from(models.values()).toSorted((a, b) => {
		return b.totals.totalCost - a.totals.totalCost || b.totals.totalTokens - a.totals.totalTokens;
	});
}
function buildToolUsage(tools) {
	if (tools.size === 0) return;
	const entries = Array.from(tools.entries()).map(([name, count]) => ({
		name,
		count
	})).toSorted((a, b) => b.count - a.count || a.name.localeCompare(b.name));
	return {
		totalCalls: entries.reduce((sum, entry) => sum + entry.count, 0),
		uniqueTools: entries.length,
		tools: entries
	};
}
function usageBucketsInRange(rollup, startMs, endMs) {
	return Object.values(rollup.buckets).filter((bucket) => bucket.timestampMs >= startMs && bucket.timestampMs <= endMs).toSorted((a, b) => a.timestampMs - b.timestampMs);
}
function buildSessionCostSummaryFromRollup(params) {
	const totals = createEmptyCostUsageTotals();
	const messageCounts = emptyMessageCounts();
	const tools = /* @__PURE__ */ new Map();
	const models = /* @__PURE__ */ new Map();
	const activityDates = /* @__PURE__ */ new Set();
	const dailyUsage = /* @__PURE__ */ new Map();
	const dailyMessages = /* @__PURE__ */ new Map();
	const quarterMessages = /* @__PURE__ */ new Map();
	const quarterTokens = /* @__PURE__ */ new Map();
	const dailyLatencies = /* @__PURE__ */ new Map();
	const dailyModels = /* @__PURE__ */ new Map();
	const allLatencies = createLatencyAggregate();
	let firstActivity;
	let lastActivity;
	const mergeBucket = (bucket) => {
		const date = new Date(bucket.timestampMs);
		const dayKey = params.formatDay(date);
		const quarter = getUtcQuarterHourBucketKey(date);
		firstActivity = firstActivity === void 0 ? bucket.timestampMs : Math.min(firstActivity, bucket.timestampMs);
		lastActivity = lastActivity === void 0 ? bucket.timestampMs : Math.max(lastActivity, bucket.timestampMs);
		activityDates.add(dayKey);
		addCostUsageTotals(totals, bucket.totals);
		addMessageCounts(messageCounts, bucket.messageCounts);
		mergeTools(tools, bucket.tools);
		mergeModels(models, bucket.models);
		const daily = dailyUsage.get(dayKey) ?? {
			tokens: 0,
			cost: 0
		};
		daily.tokens += bucket.totals.totalTokens;
		daily.cost += bucket.totals.totalCost;
		dailyUsage.set(dayKey, daily);
		const dailyMessage = dailyMessages.get(dayKey) ?? {
			date: dayKey,
			...emptyMessageCounts()
		};
		addMessageCounts(dailyMessage, bucket.messageCounts);
		dailyMessages.set(dayKey, dailyMessage);
		const quarterMessage = quarterMessages.get(quarter.bucketId) ?? {
			date: quarter.date,
			quarterIndex: quarter.quarterIndex,
			...emptyMessageCounts()
		};
		addMessageCounts(quarterMessage, bucket.messageCounts);
		quarterMessages.set(quarter.bucketId, quarterMessage);
		const quarterUsage = quarterTokens.get(quarter.bucketId) ?? {
			date: quarter.date,
			quarterIndex: quarter.quarterIndex,
			input: 0,
			output: 0,
			cacheRead: 0,
			cacheWrite: 0,
			totalTokens: 0,
			totalCost: 0
		};
		quarterUsage.input += bucket.totals.input;
		quarterUsage.output += bucket.totals.output;
		quarterUsage.cacheRead += bucket.totals.cacheRead;
		quarterUsage.cacheWrite += bucket.totals.cacheWrite;
		quarterUsage.totalTokens += bucket.totals.totalTokens;
		quarterUsage.totalCost += bucket.totals.totalCost;
		quarterTokens.set(quarter.bucketId, quarterUsage);
		for (const model of bucket.models) {
			const modelBucketId = `${dayKey}\0${modelKey(model.provider, model.model)}`;
			const existing = dailyModels.get(modelBucketId) ?? {
				date: dayKey,
				provider: model.provider,
				model: model.model,
				tokens: 0,
				cost: 0,
				count: 0
			};
			existing.tokens += model.totals.totalTokens;
			existing.cost += model.totals.totalCost;
			existing.count += model.count;
			dailyModels.set(modelBucketId, existing);
		}
		mergeLatencyAggregate(allLatencies, bucket.latency);
		const dailyLatency = dailyLatencies.get(dayKey) ?? createLatencyAggregate();
		mergeLatencyAggregate(dailyLatency, bucket.latency);
		dailyLatencies.set(dayKey, dailyLatency);
	};
	for (const bucket of usageBucketsInRange(params.rollup, params.startMs, params.endMs)) mergeBucket(bucket);
	if (params.includeUntimestamped) {
		addCostUsageTotals(totals, params.rollup.untimestamped.totals);
		addMessageCounts(messageCounts, params.rollup.untimestamped.messageCounts);
		mergeTools(tools, params.rollup.untimestamped.tools);
		mergeModels(models, params.rollup.untimestamped.models);
	}
	const dailyLatency = Array.from(dailyLatencies.entries()).map(([date, aggregate]) => {
		const stats = computeLatencyStats(aggregate);
		return stats ? Object.assign({ date }, stats) : null;
	}).filter((entry) => entry !== null).toSorted((a, b) => a.date.localeCompare(b.date));
	const utcQuarterHourMessageCounts = Array.from(quarterMessages.values()).toSorted((a, b) => a.date.localeCompare(b.date) || a.quarterIndex - b.quarterIndex);
	const utcQuarterHourTokenUsage = Array.from(quarterTokens.values()).toSorted((a, b) => a.date.localeCompare(b.date) || a.quarterIndex - b.quarterIndex);
	return {
		sessionId: params.sessionId,
		sessionFile: params.sessionFile,
		firstActivity,
		lastActivity,
		durationMs: firstActivity !== void 0 && lastActivity !== void 0 ? Math.max(0, lastActivity - firstActivity) : void 0,
		activityDates: Array.from(activityDates).toSorted(),
		dailyBreakdown: Array.from(dailyUsage.entries()).map(([date, usage]) => Object.assign({ date }, usage)).toSorted((a, b) => a.date.localeCompare(b.date)),
		dailyMessageCounts: Array.from(dailyMessages.values()).toSorted((a, b) => a.date.localeCompare(b.date)),
		utcQuarterHourMessageCounts: utcQuarterHourMessageCounts.length ? utcQuarterHourMessageCounts : void 0,
		utcQuarterHourTokenUsage: utcQuarterHourTokenUsage.length ? utcQuarterHourTokenUsage : void 0,
		dailyLatency: dailyLatency.length ? dailyLatency : void 0,
		dailyModelUsage: dailyModels.size ? Array.from(dailyModels.values()).toSorted((a, b) => a.date.localeCompare(b.date) || b.cost - a.cost) : void 0,
		messageCounts,
		toolUsage: buildToolUsage(tools),
		modelUsage: sortedModelUsage(models),
		latency: computeLatencyStats(allLatencies),
		...totals
	};
}
function addRollupToCostUsageSummary(params) {
	for (const bucket of usageBucketsInRange(params.rollup, params.startMs, params.endMs)) {
		const dayKey = params.formatDay(new Date(bucket.timestampMs));
		const daily = params.daily.get(dayKey) ?? createEmptyCostUsageTotals();
		addCostUsageTotals(daily, bucket.totals);
		params.daily.set(dayKey, daily);
		addCostUsageTotals(params.totals, bucket.totals);
	}
}
function cloneSessionUsageRollupData(rollup) {
	return {
		buckets: Object.fromEntries(Object.entries(rollup.buckets).map(([bucketId, bucket]) => [bucketId, {
			...bucket,
			totals: cloneCostUsageTotals(bucket.totals),
			messageCounts: { ...bucket.messageCounts },
			tools: bucket.tools.map((tool) => ({ ...tool })),
			models: bucket.models.map((model) => ({
				...model,
				totals: cloneCostUsageTotals(model.totals)
			})),
			latency: {
				count: bucket.latency.count,
				max: bucket.latency.max,
				sum: bucket.latency.sum,
				...bucket.latency.min !== void 0 ? { min: bucket.latency.min } : {},
				centroids: bucket.latency.centroids.map((centroid) => ({
					count: centroid.count,
					value: centroid.value
				}))
			}
		}])),
		...rollup.lastUserTimestamp !== void 0 ? { lastUserTimestamp: rollup.lastUserTimestamp } : {},
		untimestamped: {
			totals: cloneCostUsageTotals(rollup.untimestamped.totals),
			messageCounts: { ...rollup.untimestamped.messageCounts },
			tools: rollup.untimestamped.tools.map((tool) => ({ ...tool })),
			models: rollup.untimestamped.models.map((model) => ({
				...model,
				totals: cloneCostUsageTotals(model.totals)
			}))
		}
	};
}
//#endregion
//#region src/infra/session-cost-usage.ts
const USAGE_COST_ROLLUP_VERSION = 2;
const USAGE_COST_TRANSCRIPT_STAT_CONCURRENCY = 32;
const USAGE_COST_FILE_ANCHOR_BYTES = 4096;
const USAGE_COST_DIRECT_REFRESH_RETRY_MS = 25;
const logger = createSubsystemLogger("usage-cost-cache");
const usageCostRefreshes = /* @__PURE__ */ new Map();
function resolveUsageCostCacheDatabasePath(agentId) {
	return resolveOpenClawAgentSqlitePath({ agentId: normalizeAgentId(agentId) });
}
function resolveUsageCostPricingFingerprint(config) {
	return resolveModelCostConfigFingerprint(config);
}
function resolveUsageCostSessionStorePath(params) {
	return params?.sessionsDir ? path.join(params.sessionsDir, "sessions.json") : resolveDefaultSessionStorePath(params?.agentId);
}
function normalizeUsageCostRollup(raw, pricingFingerprint) {
	if (!raw || typeof raw !== "object") return;
	const record = raw;
	if (record.version !== USAGE_COST_ROLLUP_VERSION || record.pricingFingerprint !== pricingFingerprint || !record.checkpoint || !record.rollup || typeof record.scannedAt !== "number" || typeof record.parsedRecords !== "number" || typeof record.countedRecords !== "number") return;
	return record;
}
function readUsageCostRollups(agentId, pricingFingerprint, databasePath) {
	const result = /* @__PURE__ */ new Map();
	for (const row of readSessionCostUsageRollupRows(agentId, databasePath)) try {
		const entry = normalizeUsageCostRollup(JSON.parse(row.valueJson), pricingFingerprint);
		if (entry) result.set(row.key, {
			entry,
			valueJson: row.valueJson
		});
	} catch {}
	return result;
}
async function listUsageCountedTranscriptFileStats(agentId, params) {
	const sessionsDir = params?.sessionsDir ?? resolveSessionTranscriptsDirForAgent(agentId);
	let entries;
	try {
		entries = await fs.promises.readdir(sessionsDir, { withFileTypes: true });
	} catch (error) {
		if (error.code === "ENOENT") return [];
		throw error;
	}
	const { firstError, hasError, results } = await runTasksWithConcurrency({
		tasks: entries.filter((entry) => entry.isFile() && isUsageCountedSessionTranscriptFileName(entry.name)).map((entry) => async () => {
			const filePath = path.join(sessionsDir, entry.name);
			let stats;
			try {
				stats = await fs.promises.stat(filePath);
			} catch (error) {
				if (error.code === "ENOENT") return;
				throw error;
			}
			if (params?.minMtimeMs !== void 0 && stats.mtimeMs < params.minMtimeMs) return;
			if (filePath.endsWith(".zst")) try {
				const materialized = materializeSessionArchiveForRead(filePath);
				const materializedStats = await fs.promises.stat(materialized);
				return {
					filePath: materialized,
					kind: "jsonl",
					size: materializedStats.size,
					mtimeMs: stats.mtimeMs,
					device: materializedStats.dev,
					inode: materializedStats.ino
				};
			} catch (error) {
				if (error.code === "ENOENT") return;
				throw error;
			}
			return {
				filePath,
				kind: "jsonl",
				size: stats.size,
				mtimeMs: stats.mtimeMs,
				device: stats.dev,
				inode: stats.ino
			};
		}),
		limit: USAGE_COST_TRANSCRIPT_STAT_CONCURRENCY
	});
	if (hasError) throw firstError;
	return results.filter((file) => Boolean(file));
}
function listUsageCountedSqliteTranscriptStats(agentId, params) {
	const storePath = resolveUsageCostSessionStorePath({
		agentId,
		...params?.sessionsDir ? { sessionsDir: params.sessionsDir } : {}
	});
	const files = [];
	for (const instance of listSessionTranscriptInstances({
		agentId,
		storePath
	})) {
		const marker = parseSqliteSessionFileMarker(instance.entry.sessionFile);
		if (!marker) continue;
		const mtimeMs = instance.updatedAtMs;
		if (params?.minMtimeMs !== void 0 && mtimeMs < params.minMtimeMs) continue;
		const stats = readTranscriptStatsSync({
			agentId: marker.agentId,
			sessionId: marker.sessionId,
			storePath: marker.storePath
		});
		files.push({
			filePath: formatCanonicalUsageCostSqliteMarker(marker),
			kind: "sqlite",
			mtimeMs,
			sessionId: marker.sessionId,
			size: stats.sizeBytes,
			eventCount: stats.eventCount,
			maxSeq: stats.maxSeq
		});
	}
	return files;
}
function formatCanonicalUsageCostSqliteMarker(marker) {
	const storePath = resolveSqliteTargetFromSessionStorePath(marker.storePath, { agentId: marker.agentId }).path ?? resolveOpenClawAgentSqlitePath({ agentId: marker.agentId });
	return formatSqliteSessionFileMarker({
		...marker,
		storePath
	});
}
async function listUsageCountedTranscriptFiles(agentId, params) {
	return await listUsageCountedTranscriptStats(agentId, params);
}
async function listUsageCountedTranscriptStats(agentId, params) {
	const fileBacked = await listUsageCountedTranscriptFileStats(agentId, params);
	const sqliteBacked = listUsageCountedSqliteTranscriptStats(agentId, params);
	const sqliteSessionIds = new Set(sqliteBacked.map((file) => file.sessionId).filter(Boolean));
	return [...fileBacked.filter((file) => {
		const sessionId = parseUsageCountedSessionIdFromFileName(path.basename(file.filePath));
		return !sessionId || !sqliteSessionIds.has(sessionId);
	}), ...sqliteBacked];
}
async function resolveUsageCostTranscriptFile(sessionFile) {
	const marker = parseSqliteSessionFileMarker(sessionFile);
	if (marker) {
		const stats = readTranscriptStatsSync({
			agentId: marker.agentId,
			sessionId: marker.sessionId,
			storePath: marker.storePath
		});
		return {
			filePath: formatCanonicalUsageCostSqliteMarker(marker),
			kind: "sqlite",
			mtimeMs: stats.lastMutationAtMs ?? 0,
			sessionId: marker.sessionId,
			size: stats.sizeBytes,
			eventCount: stats.eventCount,
			maxSeq: stats.maxSeq
		};
	}
	if (sessionFile.endsWith(".zst")) try {
		const archiveStats = await fs.promises.stat(sessionFile);
		const materialized = materializeSessionArchiveForRead(sessionFile);
		const materializedStats = await fs.promises.stat(materialized);
		return {
			filePath: materialized,
			kind: "jsonl",
			size: materializedStats.size,
			mtimeMs: archiveStats.mtimeMs,
			device: materializedStats.dev,
			inode: materializedStats.ino
		};
	} catch {
		return;
	}
	const stats = await fs.promises.stat(sessionFile).catch(() => null);
	return stats ? {
		filePath: sessionFile,
		kind: "jsonl",
		size: stats.size,
		mtimeMs: stats.mtimeMs,
		device: stats.dev,
		inode: stats.ino
	} : void 0;
}
const normalizeUsageCostTotalOrigin = (value) => value === "provider-billed" ? value : void 0;
const extractCostBreakdown = (usageRaw) => {
	if (!usageRaw || typeof usageRaw !== "object") return;
	const cost = usageRaw.cost;
	if (!cost) return;
	const total = asFiniteNumber(cost.total);
	if (total === void 0 || total < 0) return;
	return {
		total,
		input: asFiniteNumber(cost.input),
		output: asFiniteNumber(cost.output),
		cacheRead: asFiniteNumber(cost.cacheRead),
		cacheWrite: asFiniteNumber(cost.cacheWrite),
		totalOrigin: normalizeUsageCostTotalOrigin(cost.totalOrigin)
	};
};
const parseTimestamp = (entry) => {
	const message = entry.message;
	const messageTimestamp = asFiniteNumber(message?.timestamp);
	if (messageTimestamp !== void 0) {
		const parsed = new Date(messageTimestamp);
		if (!Number.isNaN(parsed.valueOf())) return parsed;
	}
	const raw = entry.timestamp;
	if (typeof raw === "string") {
		const parsed = new Date(raw);
		if (!Number.isNaN(parsed.valueOf())) return parsed;
	}
};
const parseTranscriptEntry = (entry) => {
	const message = entry.message;
	if (!message || typeof message !== "object") return null;
	const roleRaw = message.role;
	const role = roleRaw === "user" || roleRaw === "assistant" ? roleRaw : void 0;
	const isStandaloneToolResult = roleRaw === "tool" || roleRaw === "toolResult";
	if (!role && !isStandaloneToolResult) return null;
	const usageRaw = message.usage ?? entry.usage;
	const usage = usageRaw ? normalizeUsage(usageRaw) ?? void 0 : void 0;
	const provider = (typeof message.provider === "string" ? message.provider : void 0) ?? (typeof entry.provider === "string" ? entry.provider : void 0);
	const model = (typeof message.model === "string" ? message.model : void 0) ?? (typeof entry.model === "string" ? entry.model : void 0);
	const costBreakdown = extractCostBreakdown(usageRaw);
	const stopReason = typeof message.stopReason === "string" ? message.stopReason : void 0;
	const durationMs = asFiniteNumber(message.durationMs ?? entry.durationMs);
	return {
		message,
		role,
		timestamp: parseTimestamp(entry),
		durationMs,
		usage,
		costTotal: costBreakdown?.total,
		costBreakdown,
		provider,
		model,
		stopReason,
		toolNames: isStandaloneToolResult ? [] : extractToolCallNames(message),
		toolResultCounts: isStandaloneToolResult ? {
			total: 1,
			errors: message.isError === true || message.is_error === true ? 1 : 0
		} : countToolResults(message)
	};
};
const formatUtcDayKey = (date) => `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, "0")}-${String(date.getUTCDate()).padStart(2, "0")}`;
const createUsageDayKeyFormatter = (dayBucket) => {
	if (dayBucket?.mode === "utc-offset") return (date) => formatUtcDayKey(new Date(date.getTime() + dayBucket.utcOffsetMinutes * 60 * 1e3));
	return createTimeZoneDayKeyFormatter(dayBucket?.mode === "time-zone" ? dayBucket.timeZone : Intl.DateTimeFormat().resolvedOptions().timeZone);
};
/**
* Maximum window (in days) for which we will zero-fill missing calendar
* days. Bounded ranges from the UI's range filter top out at 90 days for
* the explicit picker and "All" is the wildcard escape hatch — anything
* wider than this threshold is treated as an all-time / open-ended range
* and falls back to sparse behavior (only days with activity), since a
* dense series at that scale would produce tens of thousands of zero
* buckets (e.g. a 1970-based startMs → ~20k entries) without any user
* value. 366 days covers a full year + leap-day cushion.
*/
const MAX_ZERO_FILL_DAYS = 366;
/**
* Parse a `YYYY-MM-DD` day key into its UTC calendar-day timestamp. The
* timestamp is only used to enumerate calendar labels; usage timestamps stay
* in their requested timezone bucket.
*/
const parseDayKeyToUtcMs = (dayKey) => {
	const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(dayKey);
	if (!match) return null;
	const year = Number(match[1]);
	const monthIdx = Number(match[2]) - 1;
	const day = Number(match[3]);
	const dayMs = Date.UTC(year, monthIdx, day);
	const date = new Date(dayMs);
	return date.getUTCFullYear() === year && date.getUTCMonth() === monthIdx && date.getUTCDate() === day ? dayMs : null;
};
/**
* Ensure the daily map has an entry for every calendar day in [startMs, endMs].
* Days without activity are inserted with a zero-valued totals bucket so the
* resulting `daily` series matches the requested range length (one bar per
* calendar day) instead of only covering days with recorded usage.
*
* Day keys must use the same calendar zone as the request range. Otherwise a
* remote Gateway can return local-date labels for UTC/browser-local ranges,
* which drops boundary usage when the UI compares calendar windows.
*/
const fillMissingDays = (dailyMap, startMs, endMs, formatDayKey) => {
	if (!Number.isFinite(startMs) || !Number.isFinite(endMs) || endMs < startMs) return;
	const dayMs = 1440 * 60 * 1e3;
	const startKey = formatDayKey(new Date(startMs));
	const endKey = formatDayKey(new Date(endMs));
	const startDayMs = parseDayKeyToUtcMs(startKey);
	const endDayMs = parseDayKeyToUtcMs(endKey);
	if (startDayMs === null || endDayMs === null) {
		if (!dailyMap.has(startKey)) dailyMap.set(startKey, createEmptyCostUsageTotals());
		if (!dailyMap.has(endKey)) dailyMap.set(endKey, createEmptyCostUsageTotals());
		return;
	}
	if (Math.floor((endDayMs - startDayMs) / dayMs) + 1 > MAX_ZERO_FILL_DAYS) return;
	const maxIterations = 367;
	for (let cursorMs = startDayMs, i = 0; cursorMs <= endDayMs && i < maxIterations; i += 1) {
		const key = formatUtcDayKey(new Date(cursorMs));
		if (!dailyMap.has(key)) dailyMap.set(key, createEmptyCostUsageTotals());
		cursorMs += dayMs;
	}
	if (!dailyMap.has(endKey)) dailyMap.set(endKey, createEmptyCostUsageTotals());
};
const countCalendarDays = (startMs, endMs, formatDayKey) => {
	const startDayMs = parseDayKeyToUtcMs(formatDayKey(new Date(startMs)));
	const endDayMs = parseDayKeyToUtcMs(formatDayKey(new Date(endMs)));
	if (startDayMs === null || endDayMs === null || endDayMs < startDayMs) return Math.ceil((endMs - startMs) / (1440 * 60 * 1e3)) + 1;
	return Math.floor((endDayMs - startDayMs) / (1440 * 60 * 1e3)) + 1;
};
function isUsageCostRollupFresh(params) {
	const checkpoint = params.stored?.entry.checkpoint;
	if (!checkpoint || checkpoint.kind !== params.file.kind) return false;
	if (checkpoint.kind === "jsonl") return checkpoint.observedSize === params.file.size && checkpoint.observedMtimeMs === params.file.mtimeMs && checkpoint.device === params.file.device && checkpoint.inode === params.file.inode;
	return checkpoint.size === params.file.size && checkpoint.mtimeMs === params.file.mtimeMs && checkpoint.eventCount === params.file.eventCount && checkpoint.maxSeq === params.file.maxSeq;
}
function canUseUsageCostRollupForPartial(params) {
	const checkpoint = params.stored?.entry.checkpoint;
	if (!checkpoint || checkpoint.kind !== params.file.kind) return false;
	if (checkpoint.kind === "jsonl") return checkpoint.parsedOffset <= params.file.size && checkpoint.device === params.file.device && checkpoint.inode === params.file.inode;
	return checkpoint.maxSeq <= (params.file.maxSeq ?? 0);
}
function getUsageCostStaleRollupFiles(params) {
	return params.files.filter((file) => !isUsageCostRollupFresh({
		stored: params.rollups.get(file.filePath),
		file
	}));
}
function countUsableUsageCostRollups(params) {
	return params.files.reduce((count, file) => count + (canUseUsageCostRollupForPartial({
		stored: params.rollups.get(file.filePath),
		file
	}) ? 1 : 0), 0);
}
function latestUsageCostRollupScan(rollups) {
	let latest = 0;
	for (const { entry } of rollups.values()) latest = Math.max(latest, entry.scannedAt);
	return latest || void 0;
}
function buildCostUsageSummaryFromRollups(params) {
	const dailyMap = /* @__PURE__ */ new Map();
	const totals = createEmptyCostUsageTotals();
	const dayFormatter = createUsageDayKeyFormatter(params.dayBucket);
	const staleFiles = getUsageCostStaleRollupFiles(params);
	const cachedFiles = countUsableUsageCostRollups(params);
	for (const file of params.files) {
		const stored = params.rollups.get(file.filePath);
		if (!canUseUsageCostRollupForPartial({
			stored,
			file
		}) || !stored) continue;
		addRollupToCostUsageSummary({
			rollup: stored.entry.rollup,
			startMs: params.startMs,
			endMs: params.endMs,
			formatDay: dayFormatter,
			daily: dailyMap,
			totals
		});
	}
	fillMissingDays(dailyMap, params.startMs, params.endMs, dayFormatter);
	const status = params.refreshing ? "refreshing" : staleFiles.length > 0 ? cachedFiles > 0 ? "partial" : "stale" : "fresh";
	return {
		updatedAt: Date.now(),
		days: countCalendarDays(params.startMs, params.endMs, dayFormatter),
		daily: Array.from(dailyMap.entries()).map(([date, bucket]) => Object.assign({ date }, bucket)).toSorted((a, b) => a.date.localeCompare(b.date)),
		totals,
		cacheStatus: {
			status,
			cachedFiles,
			pendingFiles: staleFiles.length,
			staleFiles: staleFiles.length,
			refreshedAt: latestUsageCostRollupScan(params.rollups)
		}
	};
}
const computeUsageTokenTotals = (usage) => {
	const input = usage.input ?? 0;
	const output = usage.output ?? 0;
	const cacheRead = usage.cacheRead ?? 0;
	const cacheWrite = usage.cacheWrite ?? 0;
	const componentTotal = input + output + cacheRead + cacheWrite;
	return {
		input,
		output,
		cacheRead,
		cacheWrite,
		componentTotal,
		totalTokens: usage.total ?? componentTotal
	};
};
const applyUsageTotals = (totals, usage) => {
	const usageTotals = computeUsageTokenTotals(usage);
	totals.input += usageTotals.input;
	totals.output += usageTotals.output;
	totals.cacheRead += usageTotals.cacheRead;
	totals.cacheWrite += usageTotals.cacheWrite;
	totals.totalTokens += usageTotals.totalTokens;
};
const applyCostBreakdown = (totals, costBreakdown) => {
	if (costBreakdown === void 0 || costBreakdown.total === void 0) return;
	totals.totalCost += costBreakdown.total;
	totals.inputCost += costBreakdown.input ?? 0;
	totals.outputCost += costBreakdown.output ?? 0;
	totals.cacheReadCost += costBreakdown.cacheRead ?? 0;
	totals.cacheWriteCost += costBreakdown.cacheWrite ?? 0;
};
const applyCostTotal = (totals, costTotal, provider, model) => {
	if (costTotal === void 0) {
		totals.missingCostEntries += 1;
		const modelKey = `${normalizeOptionalString(provider) ?? "unknown"}/${normalizeOptionalString(model) ?? "unknown"}`;
		totals.missingCostByModel ??= {};
		totals.missingCostByModel[modelKey] = (totals.missingCostByModel[modelKey] ?? 0) + 1;
		return;
	}
	totals.totalCost += costTotal;
};
const isModelPricingKnown = (cost) => {
	if (!cost) return false;
	if (cost.tieredPricing && cost.tieredPricing.length > 0) return true;
	return cost.input > 0 || cost.output > 0 || cost.cacheRead > 0 || cost.cacheWrite > 0;
};
const shouldPreserveRecordedZeroCost = (costBreakdown) => costBreakdown?.total === 0 && (costBreakdown.totalOrigin === "provider-billed" || [
	costBreakdown.input,
	costBreakdown.output,
	costBreakdown.cacheRead,
	costBreakdown.cacheWrite
].some((value) => value !== void 0 && value !== 0));
const shouldRecomputeRecordedZeroCost = (params) => params.costTotal === 0 && !shouldPreserveRecordedZeroCost(params.costBreakdown) && isModelPricingKnown(params.cost) && computeUsageTokenTotals(params.usage).totalTokens > 0;
function createUsageCostResolver(config) {
	const cache = /* @__PURE__ */ new Map();
	return ({ provider, model }) => {
		const key = `${provider ?? ""}\0${model ?? ""}`;
		if (cache.has(key)) return cache.get(key);
		const cost = resolveModelCostConfig({
			provider,
			model,
			config
		});
		cache.set(key, cost);
		return cost;
	};
}
function hashUsageCostCheckpoint(value) {
	return createHash("sha256").update(value).digest("base64url");
}
async function readJsonlAnchorHash(filePath, offset) {
	const start = Math.max(0, offset - USAGE_COST_FILE_ANCHOR_BYTES);
	const length = offset - start;
	if (length === 0) return hashUsageCostCheckpoint("");
	const handle = await fs.promises.open(filePath, "r").catch(() => null);
	if (!handle) return;
	try {
		const buffer = Buffer.alloc(length);
		const { bytesRead } = await handle.read(buffer, 0, length, start);
		return bytesRead === length ? hashUsageCostCheckpoint(buffer) : void 0;
	} finally {
		await handle.close().catch(() => void 0);
	}
}
function parseJsonlRecord(line) {
	const text = line.toString("utf8").trim();
	if (!text) return;
	try {
		const parsed = JSON.parse(text);
		return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
	} catch {
		return;
	}
}
async function scanJsonlRange(params) {
	if (params.endOffset <= params.startOffset) return params.startOffset;
	const stream = fs.createReadStream(params.filePath, {
		start: params.startOffset,
		end: params.endOffset - 1
	});
	let carry = Buffer.alloc(0);
	let carryStart = params.startOffset;
	let processedOffset = params.startOffset;
	try {
		for await (const chunk of stream) {
			const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
			const data = carry.length === 0 ? bytes : Buffer.concat([carry, bytes]);
			let lineStart = 0;
			for (let newline = data.indexOf(10); newline >= 0; newline = data.indexOf(10, lineStart)) {
				const record = parseJsonlRecord(data.subarray(lineStart, newline));
				if (record) params.onRecord(record);
				processedOffset = carryStart + newline + 1;
				lineStart = newline + 1;
			}
			carry = data.subarray(lineStart);
			carryStart = processedOffset;
		}
		if (carry.length > 0) {
			const record = parseJsonlRecord(carry);
			if (record) {
				params.onRecord(record);
				processedOffset = params.endOffset;
			}
		}
		return processedOffset;
	} finally {
		stream.destroy();
	}
}
async function* readJsonlRecords(filePath, startOffset = 0, endOffset) {
	if (endOffset !== void 0 && endOffset <= startOffset) return;
	const streamOptions = {
		encoding: "utf-8",
		start: Math.max(0, startOffset)
	};
	if (endOffset !== void 0) streamOptions.end = endOffset - 1;
	const fileStream = fs.createReadStream(filePath, streamOptions);
	const rl = readline.createInterface({
		input: fileStream,
		crlfDelay: Infinity
	});
	try {
		for await (const line of rl) {
			const trimmed = line.trim();
			if (!trimmed) continue;
			try {
				const parsed = JSON.parse(trimmed);
				if (!parsed || typeof parsed !== "object") continue;
				yield parsed;
			} catch {}
		}
	} finally {
		rl.close();
		fileStream.destroy();
	}
}
function loadSqliteUsageTranscriptEvents(marker) {
	return selectVisibleTranscriptEvents(loadTranscriptEventsSync({
		agentId: marker.agentId,
		sessionId: marker.sessionId,
		storePath: marker.storePath
	})).filter((event) => Boolean(event) && typeof event === "object" && !Array.isArray(event));
}
async function* readTranscriptRecords(filePath, startOffset = 0, endOffset) {
	const marker = parseSqliteSessionFileMarker(filePath);
	if (marker) {
		for (const event of loadSqliteUsageTranscriptEvents(marker)) yield event;
		return;
	}
	if (filePath.endsWith(".zst")) {
		yield* readJsonlRecords(materializeSessionArchiveForRead(filePath), startOffset, endOffset);
		return;
	}
	yield* readJsonlRecords(filePath, startOffset, endOffset);
}
async function* readTranscriptRecordsBestEffort(filePath) {
	try {
		yield* readTranscriptRecords(filePath);
	} catch {}
}
function parseUsageCostTranscriptEntry(parsed, resolveCost) {
	const entry = parseTranscriptEntry(parsed);
	if (!entry?.usage) return entry;
	const cost = resolveCost({
		provider: entry.provider,
		model: entry.model
	});
	const usageTotals = computeUsageTokenTotals(entry.usage);
	const pricingKnown = isModelPricingKnown(cost);
	const preserveRecordedZeroCost = shouldPreserveRecordedZeroCost(entry.costBreakdown);
	if (cost?.tieredPricing && cost.tieredPricing.length > 0 && !preserveRecordedZeroCost) {
		entry.costTotal = estimateUsageCost({
			usage: entry.usage,
			cost
		});
		entry.costBreakdown = void 0;
	} else if (!pricingKnown && !preserveRecordedZeroCost && (entry.costTotal === void 0 || entry.costTotal === 0) && usageTotals.totalTokens > 0) {
		entry.costTotal = void 0;
		entry.costBreakdown = void 0;
	} else if (entry.costTotal === void 0 || shouldRecomputeRecordedZeroCost({
		usage: entry.usage,
		cost,
		costBreakdown: entry.costBreakdown,
		costTotal: entry.costTotal
	})) {
		entry.costTotal = estimateUsageCost({
			usage: entry.usage,
			cost
		});
		entry.costBreakdown = void 0;
	}
	return entry;
}
async function scanTranscriptFile(params) {
	const resolveCost = params.resolveCost ?? createUsageCostResolver(params.config);
	for await (const parsed of readTranscriptRecords(params.filePath, params.startOffset, params.endOffset)) {
		const entry = parseUsageCostTranscriptEntry(parsed, resolveCost);
		if (!entry) continue;
		params.onEntry(entry);
	}
}
async function scanUsageFile(params) {
	await scanTranscriptFile({
		filePath: params.filePath,
		config: params.config,
		resolveCost: params.resolveCost,
		startOffset: params.startOffset,
		endOffset: params.endOffset,
		onEntry: (entry) => {
			if (!entry.usage) return;
			params.onEntry({
				usage: entry.usage,
				costTotal: entry.costTotal,
				costBreakdown: entry.costBreakdown,
				provider: entry.provider,
				model: entry.model,
				timestamp: entry.timestamp
			});
		}
	});
}
function resolveExistingUsageSessionFile(params) {
	const sessionId = params.sessionId?.trim();
	const entryMarker = parseSqliteSessionFileMarker(params.sessionEntry?.sessionFile);
	const explicitMarker = parseSqliteSessionFileMarker(params.sessionFile);
	const sqliteMarker = entryMarker ?? explicitMarker;
	if (sqliteMarker) {
		if (sessionId && sqliteMarker.sessionId !== sessionId) return;
		return formatSqliteSessionFileMarker(sqliteMarker);
	}
	const candidate = params.sessionFile ?? (sessionId ? resolveSessionFilePath(sessionId, params.sessionEntry, { agentId: params.agentId }) : void 0);
	if (candidate && fs.existsSync(candidate)) return candidate;
	if (!sessionId) return candidate;
	try {
		const sessionsDir = candidate ? path.dirname(candidate) : resolveSessionTranscriptsDirForAgent(params.agentId);
		const baseFileName = `${sessionId}.jsonl`;
		const entries = fs.readdirSync(sessionsDir, { withFileTypes: true }).filter((entry) => {
			return entry.isFile() && (entry.name === baseFileName || entry.name.startsWith(`${baseFileName}.reset.`) || entry.name.startsWith(`${baseFileName}.deleted.`));
		});
		const primary = entries.find((entry) => entry.name === baseFileName);
		if (primary) return path.join(sessionsDir, primary.name);
		const latestArchive = entries.filter((entry) => isSessionArchiveArtifactName(entry.name)).map((entry) => entry.name).toSorted((a, b) => {
			const tsA = parseSessionArchiveTimestamp(a, "deleted") ?? parseSessionArchiveTimestamp(a, "reset") ?? 0;
			return (parseSessionArchiveTimestamp(b, "deleted") ?? parseSessionArchiveTimestamp(b, "reset") ?? 0) - tsA || b.localeCompare(a);
		})[0];
		return latestArchive ? path.join(sessionsDir, latestArchive) : candidate;
	} catch {
		return candidate;
	}
}
async function loadCostUsageSummary(params) {
	const now = Date.now();
	const defaultStart = new Date(now);
	defaultStart.setDate(defaultStart.getDate() - 29);
	const startMs = params?.startMs ?? defaultStart.getTime();
	const endMs = params?.endMs ?? now;
	const databasePath = resolveUsageCostCacheDatabasePath(params?.agentId);
	const result = await refreshCostUsageCacheForAgent({
		config: params?.config,
		agentId: params?.agentId,
		databasePath
	});
	const pricingFingerprint = resolveUsageCostPricingFingerprint(params?.config);
	return buildCostUsageSummaryFromRollups({
		rollups: readUsageCostRollups(params?.agentId, pricingFingerprint, databasePath),
		files: await listUsageCountedTranscriptFiles(params?.agentId),
		startMs,
		endMs,
		dayBucket: params?.dayBucket,
		refreshing: result === "busy" || usageCostRefreshes.has(databasePath) || isSessionCostUsageRefreshRunning(params?.agentId, databasePath)
	});
}
function appendParsedEntryToRollup(rollup, entry) {
	let usageTotals;
	if (entry.usage) {
		usageTotals = createEmptyCostUsageTotals();
		applyUsageTotals(usageTotals, entry.usage);
		if (entry.costBreakdown?.total !== void 0) applyCostBreakdown(usageTotals, entry.costBreakdown);
		else applyCostTotal(usageTotals, entry.costTotal, entry.provider, entry.model);
	}
	const timestamp = entry.timestamp?.getTime();
	appendSessionUsageRollupContribution(rollup, {
		timestamp,
		role: entry.role,
		durationMs: entry.durationMs,
		provider: entry.provider,
		model: entry.model,
		stopReason: entry.stopReason,
		toolNames: entry.toolNames,
		toolResultCounts: entry.toolResultCounts,
		usageTotals
	});
	return {
		parsedRecord: Boolean(entry.usage),
		countedRecord: Boolean(entry.usage && timestamp)
	};
}
function scanRecordsIntoRollup(params) {
	let countedRecords = 0;
	let parsedRecords = 0;
	for (const record of params.records) {
		const entry = parseUsageCostTranscriptEntry(record, params.resolveCost);
		if (!entry) continue;
		const counted = appendParsedEntryToRollup(params.rollup, entry);
		countedRecords += counted.countedRecord ? 1 : 0;
		parsedRecords += counted.parsedRecord ? 1 : 0;
	}
	return {
		countedRecords,
		parsedRecords
	};
}
async function scanJsonlUsageRollup(params) {
	const previousCheckpoint = params.previous?.entry.checkpoint.kind === "jsonl" ? params.previous.entry.checkpoint : void 0;
	const identityMatches = previousCheckpoint && previousCheckpoint.device === params.file.device && previousCheckpoint.inode === params.file.inode && previousCheckpoint.parsedOffset <= params.file.size && params.file.size > previousCheckpoint.observedSize;
	const previousAnchor = identityMatches ? await readJsonlAnchorHash(params.file.filePath, previousCheckpoint.parsedOffset) : void 0;
	const appendOnly = Boolean(identityMatches && previousAnchor === previousCheckpoint?.anchorHash && params.previous);
	const startOffset = appendOnly ? previousCheckpoint?.parsedOffset ?? 0 : 0;
	const rollup = appendOnly && params.previous ? cloneSessionUsageRollupData(params.previous.entry.rollup) : createSessionUsageRollupData();
	let countedRecords = 0;
	let parsedRecords = 0;
	const processedOffset = await scanJsonlRange({
		filePath: params.file.filePath,
		startOffset,
		endOffset: params.file.size,
		onRecord: (record) => {
			const entry = parseUsageCostTranscriptEntry(record, params.resolveCost);
			if (!entry) return;
			const counted = appendParsedEntryToRollup(rollup, entry);
			countedRecords += counted.countedRecord ? 1 : 0;
			parsedRecords += counted.parsedRecord ? 1 : 0;
		}
	});
	const postStats = await fs.promises.stat(params.file.filePath);
	if (postStats.dev !== params.file.device || postStats.ino !== params.file.inode || postStats.size < params.file.size) throw new Error(`transcript changed identity while scanning: ${params.file.filePath}`);
	const anchorHash = await readJsonlAnchorHash(params.file.filePath, processedOffset);
	if (!anchorHash) throw new Error(`transcript checkpoint unavailable: ${params.file.filePath}`);
	return {
		version: USAGE_COST_ROLLUP_VERSION,
		pricingFingerprint: params.pricingFingerprint,
		checkpoint: {
			kind: "jsonl",
			parsedOffset: processedOffset,
			observedSize: params.file.size,
			observedMtimeMs: params.file.mtimeMs,
			device: params.file.device ?? 0,
			inode: params.file.inode ?? 0,
			anchorHash
		},
		scannedAt: Date.now(),
		parsedRecords: (appendOnly ? params.previous?.entry.parsedRecords ?? 0 : 0) + parsedRecords,
		countedRecords: (appendOnly ? params.previous?.entry.countedRecords ?? 0 : 0) + countedRecords,
		rollup
	};
}
function selectIncrementalSqliteRecords(records, previousLeafId) {
	let visibleLeafId = previousLeafId;
	const visible = [];
	for (const record of records) {
		if (isSessionTranscriptLeafControl(record) || record.appendMode === "side") return;
		if (!isCanonicalSessionTranscriptEntry(record)) continue;
		const id = typeof record.id === "string" && record.id ? record.id : void 0;
		if (!id) return;
		if (Object.hasOwn(record, "parentId")) {
			if ((record.parentId === null ? void 0 : record.parentId) !== visibleLeafId) return;
		}
		visible.push(record);
		visibleLeafId = id;
	}
	return {
		records: visible,
		...visibleLeafId ? { visibleLeafId } : {}
	};
}
function sqliteCheckpointAnchorHash(event) {
	return hashUsageCostCheckpoint(JSON.stringify(event));
}
async function scanSqliteUsageRollup(params) {
	const marker = parseSqliteSessionFileMarker(params.file.filePath);
	if (!marker) throw new Error(`invalid SQLite transcript marker: ${params.file.filePath}`);
	const maxSeq = params.file.maxSeq ?? 0;
	const eventCount = params.file.eventCount ?? 0;
	const scope = {
		agentId: marker.agentId,
		sessionId: marker.sessionId,
		storePath: marker.storePath
	};
	const snapshotLastRow = maxSeq > 0 ? readTranscriptEventAtSeqSync(scope, maxSeq) : void 0;
	if (maxSeq > 0 && !snapshotLastRow) throw new Error(`SQLite transcript checkpoint unavailable: ${params.file.filePath}`);
	const snapshotAnchorHash = snapshotLastRow ? sqliteCheckpointAnchorHash(snapshotLastRow.event) : hashUsageCostCheckpoint("");
	const previousCheckpoint = params.previous?.entry.checkpoint.kind === "sqlite" ? params.previous.entry.checkpoint : void 0;
	const previousAnchor = previousCheckpoint?.maxSeq ? readTranscriptEventAtSeqSync(scope, previousCheckpoint.maxSeq) : void 0;
	const anchorMatches = previousCheckpoint?.maxSeq === 0 || previousAnchor && sqliteCheckpointAnchorHash(previousAnchor.event) === previousCheckpoint?.anchorHash;
	const appendCandidate = Boolean(params.previous && previousCheckpoint && previousCheckpoint.maxSeq < maxSeq && previousCheckpoint.eventCount < eventCount && anchorMatches);
	const rows = loadTranscriptEventRowsAfterSeqSync(scope, appendCandidate ? previousCheckpoint?.maxSeq ?? 0 : 0, maxSeq);
	const rawRecords = rows.flatMap((row) => row.event && typeof row.event === "object" && !Array.isArray(row.event) ? [row.event] : []);
	const incremental = appendCandidate ? selectIncrementalSqliteRecords(rawRecords, previousCheckpoint?.visibleLeafId) : void 0;
	const appendOnly = Boolean(incremental && params.previous);
	const allRows = appendOnly ? rows : loadTranscriptEventRowsAfterSeqSync(scope, 0, maxSeq);
	const allRecords = appendOnly ? incremental?.records ?? [] : selectVisibleTranscriptEvents(allRows.map((row) => row.event)).flatMap((event) => event && typeof event === "object" && !Array.isArray(event) ? [event] : []);
	const rollup = appendOnly && params.previous ? cloneSessionUsageRollupData(params.previous.entry.rollup) : createSessionUsageRollupData();
	const counts = scanRecordsIntoRollup({
		records: allRecords,
		rollup,
		resolveCost: params.resolveCost
	});
	const postFile = await resolveUsageCostTranscriptFile(params.file.filePath);
	if (!postFile || (postFile.maxSeq ?? 0) < maxSeq || (postFile.eventCount ?? 0) < eventCount) throw new Error(`SQLite transcript changed while scanning: ${params.file.filePath}`);
	const currentLastRow = maxSeq > 0 ? readTranscriptEventAtSeqSync(scope, maxSeq) : void 0;
	if (maxSeq > 0 && !currentLastRow || currentLastRow && sqliteCheckpointAnchorHash(currentLastRow.event) !== snapshotAnchorHash) throw new Error(`SQLite transcript changed while scanning: ${params.file.filePath}`);
	const visibleLeafId = appendOnly ? incremental?.visibleLeafId : scanSessionTranscriptTree(allRows.map((row) => row.event)).leafId ?? void 0;
	return {
		version: USAGE_COST_ROLLUP_VERSION,
		pricingFingerprint: params.pricingFingerprint,
		checkpoint: {
			kind: "sqlite",
			maxSeq,
			eventCount,
			size: params.file.size,
			mtimeMs: params.file.mtimeMs,
			anchorHash: snapshotAnchorHash,
			...visibleLeafId ? { visibleLeafId } : {}
		},
		scannedAt: Date.now(),
		parsedRecords: (appendOnly ? params.previous?.entry.parsedRecords ?? 0 : 0) + counts.parsedRecords,
		countedRecords: (appendOnly ? params.previous?.entry.countedRecords ?? 0 : 0) + counts.countedRecords,
		rollup
	};
}
async function scanUsageFileForRollup(params) {
	return params.file.kind === "sqlite" ? await scanSqliteUsageRollup(params) : await scanJsonlUsageRollup(params);
}
async function refreshCostUsageCacheForAgent(params) {
	const databasePath = params?.databasePath ?? resolveUsageCostCacheDatabasePath(params?.agentId);
	const lock = acquireSessionCostUsageRefreshLock(params?.agentId, databasePath);
	if (!lock.acquired) return "busy";
	try {
		const pricingFingerprint = resolveUsageCostPricingFingerprint(params?.config);
		const rows = readSessionCostUsageRollupRows(params?.agentId, databasePath);
		const rawValues = new Map(rows.map((row) => [row.key, row.valueJson]));
		const rollups = readUsageCostRollups(params?.agentId, pricingFingerprint, databasePath);
		const discoveredFiles = await listUsageCountedTranscriptFiles(params?.agentId, params?.sessionsDir ? { sessionsDir: params.sessionsDir } : void 0);
		const requestedFiles = [];
		for (const requested of params?.sessionFiles ?? []) {
			const resolved = await resolveUsageCostTranscriptFile(requested);
			if (resolved) requestedFiles.push(resolved);
		}
		const filesByPath = new Map(discoveredFiles.map((file) => [file.filePath, file]));
		for (const file of requestedFiles) filesByPath.set(file.filePath, file);
		const files = [...filesByPath.values()];
		deleteSessionCostUsageRollupsExcept({
			agentId: params?.agentId,
			databasePath,
			liveKeys: new Set(files.map((file) => file.filePath))
		});
		const requestedPaths = /* @__PURE__ */ new Set();
		for (const file of requestedFiles) requestedPaths.add(file.filePath);
		const refreshFiles = requestedPaths.size > 0 ? files.filter((file) => requestedPaths.has(file.filePath)) : params?.startMs === void 0 ? files : files.filter((file) => file.mtimeMs >= params.startMs);
		const maxFiles = params?.maxFiles !== void 0 && Number.isFinite(params.maxFiles) && params.maxFiles > 0 ? Math.floor(params.maxFiles) : void 0;
		const staleFiles = getUsageCostStaleRollupFiles({
			rollups,
			files: refreshFiles
		}).toSorted((a, b) => a.size - b.size || a.filePath.localeCompare(b.filePath)).slice(0, maxFiles);
		const resolveCost = createUsageCostResolver(params?.config);
		for (const file of staleFiles) {
			const entry = await scanUsageFileForRollup({
				file,
				previous: rollups.get(file.filePath),
				pricingFingerprint,
				resolveCost
			});
			const valueJson = JSON.stringify(entry);
			if (!writeSessionCostUsageRollup({
				agentId: params?.agentId,
				databasePath,
				rollupId: file.filePath,
				previousValueJson: rawValues.get(file.filePath) ?? null,
				valueJson,
				updatedAt: entry.scannedAt
			})) throw new Error(`usage rollup changed while refreshing: ${file.filePath}`);
			rollups.set(file.filePath, {
				entry,
				valueJson
			});
			rawValues.set(file.filePath, valueJson);
		}
		return "refreshed";
	} finally {
		lock.release();
	}
}
async function refreshCostUsageCache(params) {
	return await refreshCostUsageCacheForAgent(params);
}
async function loadCostUsageSummaryFromCache(params) {
	const databasePath = resolveUsageCostCacheDatabasePath(params.agentId);
	const pricingFingerprint = resolveUsageCostPricingFingerprint(params.config);
	let rollups = readUsageCostRollups(params.agentId, pricingFingerprint, databasePath);
	let files = await listUsageCountedTranscriptFiles(params.agentId);
	const staleFiles = getUsageCostStaleRollupFiles({
		rollups,
		files
	});
	if (params.requestRefresh !== false && staleFiles.length > 0) {
		const cachedFiles = countUsableUsageCostRollups({
			rollups,
			files
		});
		if (params.refreshMode === "sync-when-empty" && cachedFiles === 0) {
			const result = await refreshCostUsageCache({
				config: params.config,
				agentId: params.agentId,
				startMs: params.startMs
			});
			rollups = readUsageCostRollups(params.agentId, pricingFingerprint, databasePath);
			files = await listUsageCountedTranscriptFiles(params.agentId);
			if (result === "refreshed" && getUsageCostStaleRollupFiles({
				rollups,
				files
			}).length > 0) requestCostUsageCacheRefresh({
				config: params.config,
				agentId: params.agentId
			});
		} else requestCostUsageCacheRefresh({
			config: params.config,
			agentId: params.agentId
		});
	}
	return buildCostUsageSummaryFromRollups({
		rollups,
		files,
		startMs: params.startMs,
		endMs: params.endMs,
		dayBucket: params.dayBucket,
		refreshing: usageCostRefreshes.has(databasePath) || isSessionCostUsageRefreshRunning(params.agentId, databasePath)
	});
}
async function loadSessionCostSummariesFromCache(params) {
	const databasePath = resolveUsageCostCacheDatabasePath(params.agentId);
	const pricingFingerprint = resolveUsageCostPricingFingerprint(params.config);
	const rollups = readUsageCostRollups(params.agentId, pricingFingerprint, databasePath);
	const { results: files } = await runTasksWithConcurrency({
		tasks: params.sessions.map((session) => async () => await resolveUsageCostTranscriptFile(session.sessionFile)),
		limit: USAGE_COST_TRANSCRIPT_STAT_CONCURRENCY
	});
	const staleFiles = /* @__PURE__ */ new Set();
	let cachedFiles = 0;
	const hasExplicitRange = params.startMs !== void 0 || params.endMs !== void 0;
	const startMs = params.startMs ?? Number.NEGATIVE_INFINITY;
	const endMs = params.endMs ?? Number.POSITIVE_INFINITY;
	const dayFormatter = createUsageDayKeyFormatter(params.dayBucket);
	const summaries = params.sessions.map((session, index) => {
		const file = files[index];
		const stored = file ? rollups.get(file.filePath) : void 0;
		if (!file || !stored || !isUsageCostRollupFresh({
			stored,
			file
		})) {
			staleFiles.add(file?.filePath ?? session.sessionFile);
			return null;
		}
		cachedFiles += 1;
		return buildSessionCostSummaryFromRollup({
			rollup: stored.entry.rollup,
			sessionId: session.sessionId,
			sessionFile: session.sessionFile,
			startMs,
			endMs,
			includeUntimestamped: params.includeUntimestamped === true || !hasExplicitRange,
			formatDay: dayFormatter
		});
	});
	const refreshRequested = params.requestRefresh !== false && staleFiles.size > 0;
	if (refreshRequested) requestCostUsageCacheRefresh({
		config: params.config,
		agentId: params.agentId,
		sessionFiles: [...staleFiles]
	});
	const refreshRunning = isSessionCostUsageRefreshRunning(params.agentId, databasePath);
	return {
		summaries,
		cacheStatus: {
			status: staleFiles.size === 0 ? "fresh" : refreshRunning || refreshRequested ? "refreshing" : cachedFiles > 0 ? "partial" : "stale",
			cachedFiles,
			pendingFiles: staleFiles.size,
			staleFiles: staleFiles.size,
			refreshedAt: latestUsageCostRollupScan(rollups)
		}
	};
}
function requestCostUsageCacheRefresh(params) {
	const databasePath = resolveUsageCostCacheDatabasePath(params?.agentId);
	const refreshKey = databasePath;
	const existing = usageCostRefreshes.get(refreshKey);
	if (existing) {
		mergeUsageCostRefreshRequest(existing, params);
		return;
	}
	const state = {
		agentId: params?.agentId,
		config: params?.config,
		databasePath,
		fullRefreshRequested: false,
		pendingSessionFiles: /* @__PURE__ */ new Set(),
		running: false,
		sessionsDir: resolveSessionTranscriptsDirForAgent(params?.agentId)
	};
	mergeUsageCostRefreshRequest(state, params);
	usageCostRefreshes.set(refreshKey, state);
	scheduleUsageCostRefresh(refreshKey, state);
}
function mergeUsageCostRefreshRequest(state, params) {
	if (params?.config) state.config = params.config;
	if (params?.agentId) state.agentId = params.agentId;
	if (!params?.sessionFiles) {
		state.fullRefreshRequested = true;
		return;
	}
	for (const sessionFile of params.sessionFiles) state.pendingSessionFiles.add(sessionFile);
}
function scheduleUsageCostRefresh(refreshKey, state, delayMs = 0) {
	if (state.running || state.timer) return;
	const timer = setTimeout(() => {
		state.timer = void 0;
		runQueuedUsageCostRefresh(refreshKey, state);
	}, delayMs);
	timer.unref?.();
	state.timer = timer;
}
async function runQueuedUsageCostRefresh(refreshKey, state) {
	state.running = true;
	let retryDelayMs = 0;
	try {
		while (state.fullRefreshRequested || state.pendingSessionFiles.size > 0) {
			const fullRefreshRequested = state.fullRefreshRequested;
			const sessionFiles = fullRefreshRequested ? [] : [...state.pendingSessionFiles];
			if (!fullRefreshRequested) state.pendingSessionFiles.clear();
			state.fullRefreshRequested = false;
			if (await refreshCostUsageCacheForAgent({
				config: state.config,
				agentId: state.agentId,
				databasePath: state.databasePath,
				sessionsDir: state.sessionsDir,
				sessionFiles: fullRefreshRequested ? void 0 : sessionFiles
			}) === "busy") {
				if (fullRefreshRequested) state.fullRefreshRequested = true;
				else for (const sessionFile of sessionFiles) state.pendingSessionFiles.add(sessionFile);
				retryDelayMs = 50;
				break;
			}
		}
	} catch (error) {
		logger.warn(`background refresh failed: ${formatErrorMessage(error)}`, { error });
	} finally {
		state.running = false;
		if (state.fullRefreshRequested || state.pendingSessionFiles.size > 0) scheduleUsageCostRefresh(refreshKey, state, retryDelayMs);
		else usageCostRefreshes.delete(refreshKey);
	}
}
/**
* Scan all transcript files to discover sessions not in the session store.
* Returns basic metadata for each discovered session.
*/
async function discoverAllSessions(params) {
	const files = await listUsageCountedTranscriptStats(params?.agentId, { minMtimeMs: params?.startMs });
	const discovered = /* @__PURE__ */ new Map();
	for (const file of files) {
		const filePath = file.filePath;
		const fileName = path.basename(filePath);
		const sqliteMarker = parseSqliteSessionFileMarker(filePath);
		const sessionId = sqliteMarker?.sessionId ?? parseUsageCountedSessionIdFromFileName(fileName);
		if (!sessionId) continue;
		const isPrimaryTranscript = sqliteMarker ? true : isPrimarySessionTranscriptFileName(fileName);
		let firstUserMessage;
		if (params?.includeFirstUserMessage !== false) try {
			for await (const parsed of readTranscriptRecords(filePath)) try {
				const message = parsed.message;
				if (message?.role === "user") {
					const content = message.content;
					if (typeof content === "string") firstUserMessage = truncateUtf16Safe(content, 100);
					else if (Array.isArray(content)) {
						for (const block of content) if (typeof block === "object" && block && block.type === "text") {
							const text = block.text;
							if (typeof text === "string") firstUserMessage = truncateUtf16Safe(text, 100);
							break;
						}
					}
					break;
				}
			} catch {}
		} catch {}
		const existing = discovered.get(sessionId);
		const existingIsPrimary = existing ? isPrimarySessionTranscriptFileName(path.basename(existing.sessionFile)) : false;
		if (!existing || isPrimaryTranscript && !existingIsPrimary || isPrimaryTranscript === existingIsPrimary && file.mtimeMs >= existing.mtime) {
			discovered.set(sessionId, {
				sessionId,
				sessionFile: filePath,
				mtime: file.mtimeMs,
				firstUserMessage: firstUserMessage ?? existing?.firstUserMessage
			});
			continue;
		}
		if (!existing.firstUserMessage && firstUserMessage) {
			existing.firstUserMessage = firstUserMessage;
			discovered.set(sessionId, existing);
		}
	}
	return Array.from(discovered.values()).toSorted((a, b) => b.mtime - a.mtime);
}
async function loadSessionCostSummary(params) {
	const sessionFile = resolveExistingUsageSessionFile(params);
	if (!sessionFile) return null;
	if (!await resolveUsageCostTranscriptFile(sessionFile)) return null;
	const databasePath = resolveUsageCostCacheDatabasePath(params.agentId);
	while (await refreshCostUsageCacheForAgent({
		config: params.config,
		agentId: params.agentId,
		databasePath,
		sessionFiles: [sessionFile]
	}) === "busy") await new Promise((resolve) => {
		setTimeout(resolve, USAGE_COST_DIRECT_REFRESH_RETRY_MS);
	});
	const currentFile = await resolveUsageCostTranscriptFile(sessionFile);
	if (!currentFile) return null;
	const pricingFingerprint = resolveUsageCostPricingFingerprint(params.config);
	const stored = readUsageCostRollups(params.agentId, pricingFingerprint, databasePath).get(currentFile.filePath);
	if (!stored || !isUsageCostRollupFresh({
		stored,
		file: currentFile
	})) return null;
	const hasExplicitRange = params.startMs !== void 0 || params.endMs !== void 0;
	return buildSessionCostSummaryFromRollup({
		rollup: stored.entry.rollup,
		sessionId: params.sessionId,
		sessionFile,
		startMs: params.startMs ?? Number.NEGATIVE_INFINITY,
		endMs: params.endMs ?? Number.POSITIVE_INFINITY,
		includeUntimestamped: params.includeUntimestamped === true || !hasExplicitRange,
		formatDay: createUsageDayKeyFormatter(params.dayBucket)
	});
}
async function loadSessionUsageTimeSeries(params) {
	const sessionFile = resolveExistingUsageSessionFile(params);
	if (!sessionFile) return null;
	if (!parseSqliteSessionFileMarker(sessionFile) && !fs.existsSync(sessionFile)) return null;
	if (params.maxPoints !== void 0 && params.maxPoints !== null) {
		if (!Number.isFinite(params.maxPoints) || params.maxPoints <= 0) return {
			sessionId: params.sessionId,
			points: []
		};
	}
	const points = [];
	const resolveCost = createUsageCostResolver(params.config);
	await scanUsageFile({
		filePath: sessionFile,
		config: params.config,
		resolveCost,
		onEntry: (entry) => {
			const ts = entry.timestamp?.getTime();
			if (!ts) return;
			const { input, output, cacheRead, cacheWrite, totalTokens } = computeUsageTokenTotals(entry.usage);
			const cost = entry.costTotal ?? 0;
			points.push({
				timestamp: ts,
				input,
				output,
				cacheRead,
				cacheWrite,
				totalTokens,
				cost
			});
		}
	});
	let cumulativeTokens = 0;
	let cumulativeCost = 0;
	const sortedPoints = points.toSorted((a, b) => a.timestamp - b.timestamp).map((point) => {
		cumulativeTokens += point.totalTokens;
		cumulativeCost += point.cost;
		return Object.assign(point, {
			cumulativeTokens,
			cumulativeCost
		});
	});
	const maxPoints = params.maxPoints ?? 100;
	if (sortedPoints.length > maxPoints) {
		const step = Math.ceil(sortedPoints.length / maxPoints);
		const downsampled = [];
		let downsampledCumulativeTokens = 0;
		let downsampledCumulativeCost = 0;
		for (let i = 0; i < sortedPoints.length; i += step) {
			const bucket = sortedPoints.slice(i, i + step);
			const bucketLast = bucket[bucket.length - 1];
			if (!bucketLast) continue;
			let bucketInput = 0;
			let bucketOutput = 0;
			let bucketCacheRead = 0;
			let bucketCacheWrite = 0;
			let bucketTotalTokens = 0;
			let bucketCost = 0;
			for (const point of bucket) {
				bucketInput += point.input;
				bucketOutput += point.output;
				bucketCacheRead += point.cacheRead;
				bucketCacheWrite += point.cacheWrite;
				bucketTotalTokens += point.totalTokens;
				bucketCost += point.cost;
			}
			downsampledCumulativeTokens += bucketTotalTokens;
			downsampledCumulativeCost += bucketCost;
			downsampled.push({
				timestamp: bucketLast.timestamp,
				input: bucketInput,
				output: bucketOutput,
				cacheRead: bucketCacheRead,
				cacheWrite: bucketCacheWrite,
				totalTokens: bucketTotalTokens,
				cost: bucketCost,
				cumulativeTokens: downsampledCumulativeTokens,
				cumulativeCost: downsampledCumulativeCost
			});
		}
		return {
			sessionId: params.sessionId,
			points: downsampled
		};
	}
	return {
		sessionId: params.sessionId,
		points: sortedPoints
	};
}
async function loadSessionLogs(params) {
	const sessionFile = resolveExistingUsageSessionFile(params);
	if (!sessionFile) return null;
	if (!parseSqliteSessionFileMarker(sessionFile) && !fs.existsSync(sessionFile)) return null;
	const logs = [];
	if (params.limit !== void 0 && params.limit !== null) {
		if (!Number.isFinite(params.limit) || params.limit <= 0) return [];
	}
	const limit = params.limit ?? 50;
	const boundedLimit = Number.isInteger(limit);
	const retentionLimit = limit * 2;
	const resolveCost = createUsageCostResolver(params.config);
	for await (const parsed of readTranscriptRecordsBestEffort(sessionFile)) try {
		const message = parsed.message;
		if (!message) continue;
		const role = message.role;
		if (role !== "user" && role !== "assistant" && role !== "tool" && role !== "toolResult") continue;
		const contentParts = [];
		const toolName = normalizeOptionalString(message.toolName ?? message.tool_name ?? message.name ?? message.tool);
		if (role === "tool" || role === "toolResult") {
			contentParts.push(`[Tool: ${toolName ?? "tool"}]`);
			contentParts.push("[Tool Result]");
		}
		const rawContent = message.content;
		if (typeof rawContent === "string") contentParts.push(rawContent);
		else if (Array.isArray(rawContent)) {
			const contentText = rawContent.map((block) => {
				if (typeof block === "string") return block;
				const b = block;
				if (b.type === "text" && typeof b.text === "string") return b.text;
				if (b.type === "tool_use") return `[Tool: ${typeof b.name === "string" ? b.name : "unknown"}]`;
				if (b.type === "tool_result") return `[Tool Result]`;
				return "";
			}).filter(Boolean).join("\n");
			if (contentText) contentParts.push(contentText);
		}
		const rawToolCalls = message.tool_calls ?? message.toolCalls ?? message.function_call ?? message.functionCall;
		const toolCalls = Array.isArray(rawToolCalls) ? rawToolCalls : rawToolCalls ? [rawToolCalls] : [];
		if (toolCalls.length > 0) for (const call of toolCalls) {
			const callObj = call;
			const directName = typeof callObj.name === "string" ? callObj.name : void 0;
			const fn = callObj.function;
			const fnName = typeof fn?.name === "string" ? fn.name : void 0;
			const name = directName ?? fnName ?? "unknown";
			contentParts.push(`[Tool: ${name}]`);
		}
		let content = contentParts.join("\n").trim();
		if (!content) continue;
		content = stripInboundMetadata(content);
		if (role === "user") content = stripMessageIdHints(stripEnvelope(content)).trim();
		if (!content) continue;
		const maxLen = 2e3;
		if (content.length > maxLen) content = truncateUtf16Safe(content, maxLen) + "…";
		const timestamp = parseTimestamp(parsed)?.getTime() ?? 0;
		let tokens;
		let cost;
		if (role === "assistant") {
			const usageRaw = message.usage;
			const usage = normalizeUsage(usageRaw);
			if (usage) {
				tokens = usage.total ?? (usage.input ?? 0) + (usage.output ?? 0) + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0);
				const breakdown = extractCostBreakdown(usageRaw);
				const costConfig = resolveCost({
					provider: (typeof message.provider === "string" ? message.provider : void 0) ?? (typeof parsed.provider === "string" ? parsed.provider : void 0),
					model: (typeof message.model === "string" ? message.model : void 0) ?? (typeof parsed.model === "string" ? parsed.model : void 0)
				});
				if (breakdown?.total !== void 0 && !shouldRecomputeRecordedZeroCost({
					usage,
					cost: costConfig,
					costBreakdown: breakdown,
					costTotal: breakdown.total
				})) cost = breakdown.total;
				else cost = estimateUsageCost({
					usage,
					cost: costConfig
				});
			}
		}
		logs.push({
			timestamp,
			role,
			content,
			tokens,
			cost
		});
		if (boundedLimit && logs.length > retentionLimit) {
			logs.sort((a, b) => a.timestamp - b.timestamp);
			logs.splice(0, logs.length - limit);
		}
	} catch {}
	if (boundedLimit) {
		logs.sort((a, b) => a.timestamp - b.timestamp);
		return logs.length > limit ? logs.slice(-limit) : logs;
	}
	const sortedLogs = logs.toSorted((a, b) => a.timestamp - b.timestamp);
	if (sortedLogs.length > limit) return sortedLogs.slice(-limit);
	return sortedLogs;
}
//#endregion
export { loadSessionCostSummary as a, resolveExistingUsageSessionFile as c, loadSessionCostSummariesFromCache as i, loadCostUsageSummary as n, loadSessionLogs as o, loadCostUsageSummaryFromCache as r, loadSessionUsageTimeSeries as s, discoverAllSessions as t };
