@gguf/claw
Version:
WhatsApp gateway CLI (Baileys web) with Pi RPC agent
692 lines (688 loc) • 25.3 kB
JavaScript
import { t as __exportAll } from "./rolldown-runtime-Cbj13DAv.js";
import { a as resolveSessionTranscriptsDirForAgent, n as resolveSessionFilePath } from "./paths-CTg8F3AE.js";
import { i as resolveModelCostConfig, t as estimateUsageCost } from "./usage-format-E3bMcUMV.js";
import path from "node:path";
import fs from "node:fs";
import readline from "node:readline";
//#region src/agents/usage.ts
const asFiniteNumber = (value) => {
if (typeof value !== "number") return;
if (!Number.isFinite(value)) return;
return value;
};
function hasNonzeroUsage(usage) {
if (!usage) return false;
return [
usage.input,
usage.output,
usage.cacheRead,
usage.cacheWrite,
usage.total
].some((v) => typeof v === "number" && Number.isFinite(v) && v > 0);
}
function normalizeUsage(raw) {
if (!raw) return;
const input = asFiniteNumber(raw.input ?? raw.inputTokens ?? raw.input_tokens ?? raw.promptTokens ?? raw.prompt_tokens);
const output = asFiniteNumber(raw.output ?? raw.outputTokens ?? raw.output_tokens ?? raw.completionTokens ?? raw.completion_tokens);
const cacheRead = asFiniteNumber(raw.cacheRead ?? raw.cache_read ?? raw.cache_read_input_tokens);
const cacheWrite = asFiniteNumber(raw.cacheWrite ?? raw.cache_write ?? raw.cache_creation_input_tokens);
const total = asFiniteNumber(raw.total ?? raw.totalTokens ?? raw.total_tokens);
if (input === void 0 && output === void 0 && cacheRead === void 0 && cacheWrite === void 0 && total === void 0) return;
return {
input,
output,
cacheRead,
cacheWrite,
total
};
}
function derivePromptTokens(usage) {
if (!usage) return;
const input = usage.input ?? 0;
const cacheRead = usage.cacheRead ?? 0;
const cacheWrite = usage.cacheWrite ?? 0;
const sum = input + cacheRead + cacheWrite;
return sum > 0 ? sum : void 0;
}
//#endregion
//#region src/utils/transcript-tools.ts
const TOOL_CALL_TYPES = new Set([
"tool_use",
"toolcall",
"tool_call"
]);
const TOOL_RESULT_TYPES = new Set(["tool_result", "tool_result_error"]);
const normalizeType = (value) => {
if (typeof value !== "string") return "";
return value.trim().toLowerCase();
};
const extractToolCallNames = (message) => {
const names = /* @__PURE__ */ new Set();
const toolNameRaw = message.toolName ?? message.tool_name;
if (typeof toolNameRaw === "string" && toolNameRaw.trim()) names.add(toolNameRaw.trim());
const content = message.content;
if (!Array.isArray(content)) return Array.from(names);
for (const entry of content) {
if (!entry || typeof entry !== "object") continue;
const block = entry;
const type = normalizeType(block.type);
if (!TOOL_CALL_TYPES.has(type)) continue;
const name = block.name;
if (typeof name === "string" && name.trim()) names.add(name.trim());
}
return Array.from(names);
};
const hasToolCall = (message) => extractToolCallNames(message).length > 0;
const countToolResults = (message) => {
const content = message.content;
if (!Array.isArray(content)) return {
total: 0,
errors: 0
};
let total = 0;
let errors = 0;
for (const entry of content) {
if (!entry || typeof entry !== "object") continue;
const block = entry;
const type = normalizeType(block.type);
if (!TOOL_RESULT_TYPES.has(type)) continue;
total += 1;
if (block.is_error === true) errors += 1;
}
return {
total,
errors
};
};
//#endregion
//#region src/infra/session-cost-usage.ts
var session_cost_usage_exports = /* @__PURE__ */ __exportAll({
discoverAllSessions: () => discoverAllSessions,
loadCostUsageSummary: () => loadCostUsageSummary,
loadSessionCostSummary: () => loadSessionCostSummary,
loadSessionLogs: () => loadSessionLogs,
loadSessionUsageTimeSeries: () => loadSessionUsageTimeSeries
});
const emptyTotals = () => ({
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
totalCost: 0,
inputCost: 0,
outputCost: 0,
cacheReadCost: 0,
cacheWriteCost: 0,
missingCostEntries: 0
});
const toFiniteNumber = (value) => {
if (typeof value !== "number") return;
if (!Number.isFinite(value)) return;
return value;
};
const extractCostBreakdown = (usageRaw) => {
if (!usageRaw || typeof usageRaw !== "object") return;
const cost = usageRaw.cost;
if (!cost) return;
const total = toFiniteNumber(cost.total);
if (total === void 0 || total < 0) return;
return {
total,
input: toFiniteNumber(cost.input),
output: toFiniteNumber(cost.output),
cacheRead: toFiniteNumber(cost.cacheRead),
cacheWrite: toFiniteNumber(cost.cacheWrite)
};
};
const parseTimestamp = (entry) => {
const raw = entry.timestamp;
if (typeof raw === "string") {
const parsed = new Date(raw);
if (!Number.isNaN(parsed.valueOf())) return parsed;
}
const message = entry.message;
const messageTimestamp = toFiniteNumber(message?.timestamp);
if (messageTimestamp !== void 0) {
const parsed = new Date(messageTimestamp);
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;
if (!role) 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 = toFiniteNumber(message.durationMs ?? entry.durationMs);
return {
message,
role,
timestamp: parseTimestamp(entry),
durationMs,
usage,
costTotal: costBreakdown?.total,
costBreakdown,
provider,
model,
stopReason,
toolNames: extractToolCallNames(message),
toolResultCounts: countToolResults(message)
};
};
const formatDayKey = (date) => date.toLocaleDateString("en-CA", { timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone });
const computeLatencyStats = (values) => {
if (!values.length) return;
const sorted = values.toSorted((a, b) => a - b);
const total = sorted.reduce((sum, v) => sum + v, 0);
const count = sorted.length;
const p95Index = Math.max(0, Math.ceil(count * .95) - 1);
return {
count,
avgMs: total / count,
p95Ms: sorted[p95Index] ?? sorted[count - 1],
minMs: sorted[0],
maxMs: sorted[count - 1]
};
};
const applyUsageTotals = (totals, usage) => {
totals.input += usage.input ?? 0;
totals.output += usage.output ?? 0;
totals.cacheRead += usage.cacheRead ?? 0;
totals.cacheWrite += usage.cacheWrite ?? 0;
const totalTokens = usage.total ?? (usage.input ?? 0) + (usage.output ?? 0) + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0);
totals.totalTokens += 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) => {
if (costTotal === void 0) {
totals.missingCostEntries += 1;
return;
}
totals.totalCost += costTotal;
};
async function scanTranscriptFile(params) {
const fileStream = fs.createReadStream(params.filePath, { encoding: "utf-8" });
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity
});
for await (const line of rl) {
const trimmed = line.trim();
if (!trimmed) continue;
try {
const entry = parseTranscriptEntry(JSON.parse(trimmed));
if (!entry) continue;
if (entry.usage && entry.costTotal === void 0) {
const cost = resolveModelCostConfig({
provider: entry.provider,
model: entry.model,
config: params.config
});
entry.costTotal = estimateUsageCost({
usage: entry.usage,
cost
});
}
params.onEntry(entry);
} catch {}
}
}
async function scanUsageFile(params) {
await scanTranscriptFile({
filePath: params.filePath,
config: params.config,
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
});
}
});
}
async function loadCostUsageSummary(params) {
const now = /* @__PURE__ */ new Date();
let sinceTime;
let untilTime;
if (params?.startMs !== void 0 && params?.endMs !== void 0) {
sinceTime = params.startMs;
untilTime = params.endMs;
} else {
const days = Math.max(1, Math.floor(params?.days ?? 30));
const since = new Date(now);
since.setDate(since.getDate() - (days - 1));
sinceTime = since.getTime();
untilTime = now.getTime();
}
const dailyMap = /* @__PURE__ */ new Map();
const totals = emptyTotals();
const sessionsDir = resolveSessionTranscriptsDirForAgent(params?.agentId);
const entries = await fs.promises.readdir(sessionsDir, { withFileTypes: true }).catch(() => []);
const files = (await Promise.all(entries.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map(async (entry) => {
const filePath = path.join(sessionsDir, entry.name);
const stats = await fs.promises.stat(filePath).catch(() => null);
if (!stats) return null;
if (stats.mtimeMs < sinceTime) return null;
return filePath;
}))).filter((filePath) => Boolean(filePath));
for (const filePath of files) await scanUsageFile({
filePath,
config: params?.config,
onEntry: (entry) => {
const ts = entry.timestamp?.getTime();
if (!ts || ts < sinceTime || ts > untilTime) return;
const dayKey = formatDayKey(entry.timestamp ?? now);
const bucket = dailyMap.get(dayKey) ?? emptyTotals();
applyUsageTotals(bucket, entry.usage);
if (entry.costBreakdown?.total !== void 0) applyCostBreakdown(bucket, entry.costBreakdown);
else applyCostTotal(bucket, entry.costTotal);
dailyMap.set(dayKey, bucket);
applyUsageTotals(totals, entry.usage);
if (entry.costBreakdown?.total !== void 0) applyCostBreakdown(totals, entry.costBreakdown);
else applyCostTotal(totals, entry.costTotal);
}
});
const daily = Array.from(dailyMap.entries()).map(([date, bucket]) => Object.assign({ date }, bucket)).toSorted((a, b) => a.date.localeCompare(b.date));
const days = Math.ceil((untilTime - sinceTime) / (1440 * 60 * 1e3)) + 1;
return {
updatedAt: Date.now(),
days,
daily,
totals
};
}
/**
* Scan all transcript files to discover sessions not in the session store.
* Returns basic metadata for each discovered session.
*/
async function discoverAllSessions(params) {
const sessionsDir = resolveSessionTranscriptsDirForAgent(params?.agentId);
const entries = await fs.promises.readdir(sessionsDir, { withFileTypes: true }).catch(() => []);
const discovered = [];
for (const entry of entries) {
if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
const filePath = path.join(sessionsDir, entry.name);
const stats = await fs.promises.stat(filePath).catch(() => null);
if (!stats) continue;
if (params?.startMs && stats.mtimeMs < params.startMs) continue;
const sessionId = entry.name.slice(0, -6);
let firstUserMessage;
try {
const fileStream = fs.createReadStream(filePath, { encoding: "utf-8" });
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity
});
for await (const line of rl) {
const trimmed = line.trim();
if (!trimmed) continue;
try {
const message = JSON.parse(trimmed).message;
if (message?.role === "user") {
const content = message.content;
if (typeof content === "string") firstUserMessage = content.slice(0, 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 = text.slice(0, 100);
break;
}
}
break;
}
} catch {}
}
rl.close();
fileStream.destroy();
} catch {}
discovered.push({
sessionId,
sessionFile: filePath,
mtime: stats.mtimeMs,
firstUserMessage
});
}
return discovered.toSorted((a, b) => b.mtime - a.mtime);
}
async function loadSessionCostSummary(params) {
const sessionFile = params.sessionFile ?? (params.sessionId ? resolveSessionFilePath(params.sessionId, params.sessionEntry) : void 0);
if (!sessionFile || !fs.existsSync(sessionFile)) return null;
const totals = emptyTotals();
let firstActivity;
let lastActivity;
const activityDatesSet = /* @__PURE__ */ new Set();
const dailyMap = /* @__PURE__ */ new Map();
const dailyMessageMap = /* @__PURE__ */ new Map();
const dailyLatencyMap = /* @__PURE__ */ new Map();
const dailyModelUsageMap = /* @__PURE__ */ new Map();
const messageCounts = {
total: 0,
user: 0,
assistant: 0,
toolCalls: 0,
toolResults: 0,
errors: 0
};
const toolUsageMap = /* @__PURE__ */ new Map();
const modelUsageMap = /* @__PURE__ */ new Map();
const errorStopReasons = new Set([
"error",
"aborted",
"timeout"
]);
const latencyValues = [];
let lastUserTimestamp;
const MAX_LATENCY_MS = 720 * 60 * 1e3;
await scanTranscriptFile({
filePath: sessionFile,
config: params.config,
onEntry: (entry) => {
const ts = entry.timestamp?.getTime();
if (params.startMs !== void 0 && ts !== void 0 && ts < params.startMs) return;
if (params.endMs !== void 0 && ts !== void 0 && ts > params.endMs) return;
if (ts !== void 0) {
if (!firstActivity || ts < firstActivity) firstActivity = ts;
if (!lastActivity || ts > lastActivity) lastActivity = ts;
}
if (entry.role === "user") {
messageCounts.user += 1;
messageCounts.total += 1;
if (entry.timestamp) lastUserTimestamp = entry.timestamp.getTime();
}
if (entry.role === "assistant") {
messageCounts.assistant += 1;
messageCounts.total += 1;
const ts = entry.timestamp?.getTime();
if (ts !== void 0) {
const latencyMs = entry.durationMs ?? (lastUserTimestamp !== void 0 ? Math.max(0, ts - lastUserTimestamp) : void 0);
if (latencyMs !== void 0 && Number.isFinite(latencyMs) && latencyMs <= MAX_LATENCY_MS) {
latencyValues.push(latencyMs);
const dayKey = formatDayKey(entry.timestamp ?? new Date(ts));
const dailyLatencies = dailyLatencyMap.get(dayKey) ?? [];
dailyLatencies.push(latencyMs);
dailyLatencyMap.set(dayKey, dailyLatencies);
}
}
}
if (entry.toolNames.length > 0) {
messageCounts.toolCalls += entry.toolNames.length;
for (const name of entry.toolNames) toolUsageMap.set(name, (toolUsageMap.get(name) ?? 0) + 1);
}
if (entry.toolResultCounts.total > 0) {
messageCounts.toolResults += entry.toolResultCounts.total;
messageCounts.errors += entry.toolResultCounts.errors;
}
if (entry.stopReason && errorStopReasons.has(entry.stopReason)) messageCounts.errors += 1;
if (entry.timestamp) {
const dayKey = formatDayKey(entry.timestamp);
activityDatesSet.add(dayKey);
const daily = dailyMessageMap.get(dayKey) ?? {
date: dayKey,
total: 0,
user: 0,
assistant: 0,
toolCalls: 0,
toolResults: 0,
errors: 0
};
daily.total += entry.role === "user" || entry.role === "assistant" ? 1 : 0;
if (entry.role === "user") daily.user += 1;
else if (entry.role === "assistant") daily.assistant += 1;
daily.toolCalls += entry.toolNames.length;
daily.toolResults += entry.toolResultCounts.total;
daily.errors += entry.toolResultCounts.errors;
if (entry.stopReason && errorStopReasons.has(entry.stopReason)) daily.errors += 1;
dailyMessageMap.set(dayKey, daily);
}
if (!entry.usage) return;
applyUsageTotals(totals, entry.usage);
if (entry.costBreakdown?.total !== void 0) applyCostBreakdown(totals, entry.costBreakdown);
else applyCostTotal(totals, entry.costTotal);
if (entry.timestamp) {
const dayKey = formatDayKey(entry.timestamp);
const entryTokens = (entry.usage.input ?? 0) + (entry.usage.output ?? 0) + (entry.usage.cacheRead ?? 0) + (entry.usage.cacheWrite ?? 0);
const entryCost = entry.costBreakdown?.total ?? (entry.costBreakdown ? (entry.costBreakdown.input ?? 0) + (entry.costBreakdown.output ?? 0) + (entry.costBreakdown.cacheRead ?? 0) + (entry.costBreakdown.cacheWrite ?? 0) : entry.costTotal ?? 0);
const existing = dailyMap.get(dayKey) ?? {
tokens: 0,
cost: 0
};
dailyMap.set(dayKey, {
tokens: existing.tokens + entryTokens,
cost: existing.cost + entryCost
});
if (entry.provider || entry.model) {
const modelKey = `${dayKey}::${entry.provider ?? "unknown"}::${entry.model ?? "unknown"}`;
const dailyModel = dailyModelUsageMap.get(modelKey) ?? {
date: dayKey,
provider: entry.provider,
model: entry.model,
tokens: 0,
cost: 0,
count: 0
};
dailyModel.tokens += entryTokens;
dailyModel.cost += entryCost;
dailyModel.count += 1;
dailyModelUsageMap.set(modelKey, dailyModel);
}
}
if (entry.provider || entry.model) {
const key = `${entry.provider ?? "unknown"}::${entry.model ?? "unknown"}`;
const existing = modelUsageMap.get(key) ?? {
provider: entry.provider,
model: entry.model,
count: 0,
totals: emptyTotals()
};
existing.count += 1;
applyUsageTotals(existing.totals, entry.usage);
if (entry.costBreakdown?.total !== void 0) applyCostBreakdown(existing.totals, entry.costBreakdown);
else applyCostTotal(existing.totals, entry.costTotal);
modelUsageMap.set(key, existing);
}
}
});
const dailyBreakdown = Array.from(dailyMap.entries()).map(([date, data]) => ({
date,
tokens: data.tokens,
cost: data.cost
})).toSorted((a, b) => a.date.localeCompare(b.date));
const dailyMessageCounts = Array.from(dailyMessageMap.values()).toSorted((a, b) => a.date.localeCompare(b.date));
const dailyLatency = Array.from(dailyLatencyMap.entries()).map(([date, values]) => {
const stats = computeLatencyStats(values);
if (!stats) return null;
return {
date,
...stats
};
}).filter((entry) => Boolean(entry)).toSorted((a, b) => a.date.localeCompare(b.date));
const dailyModelUsage = Array.from(dailyModelUsageMap.values()).toSorted((a, b) => a.date.localeCompare(b.date) || b.cost - a.cost);
const toolUsage = toolUsageMap.size ? {
totalCalls: Array.from(toolUsageMap.values()).reduce((sum, count) => sum + count, 0),
uniqueTools: toolUsageMap.size,
tools: Array.from(toolUsageMap.entries()).map(([name, count]) => ({
name,
count
})).toSorted((a, b) => b.count - a.count)
} : void 0;
const modelUsage = modelUsageMap.size ? Array.from(modelUsageMap.values()).toSorted((a, b) => {
const costDiff = b.totals.totalCost - a.totals.totalCost;
if (costDiff !== 0) return costDiff;
return b.totals.totalTokens - a.totals.totalTokens;
}) : void 0;
return {
sessionId: params.sessionId,
sessionFile,
firstActivity,
lastActivity,
durationMs: firstActivity !== void 0 && lastActivity !== void 0 ? Math.max(0, lastActivity - firstActivity) : void 0,
activityDates: Array.from(activityDatesSet).toSorted(),
dailyBreakdown,
dailyMessageCounts,
dailyLatency: dailyLatency.length ? dailyLatency : void 0,
dailyModelUsage: dailyModelUsage.length ? dailyModelUsage : void 0,
messageCounts,
toolUsage,
modelUsage,
latency: computeLatencyStats(latencyValues),
...totals
};
}
async function loadSessionUsageTimeSeries(params) {
const sessionFile = params.sessionFile ?? (params.sessionId ? resolveSessionFilePath(params.sessionId, params.sessionEntry) : void 0);
if (!sessionFile || !fs.existsSync(sessionFile)) return null;
const points = [];
let cumulativeTokens = 0;
let cumulativeCost = 0;
await scanUsageFile({
filePath: sessionFile,
config: params.config,
onEntry: (entry) => {
const ts = entry.timestamp?.getTime();
if (!ts) return;
const input = entry.usage.input ?? 0;
const output = entry.usage.output ?? 0;
const cacheRead = entry.usage.cacheRead ?? 0;
const cacheWrite = entry.usage.cacheWrite ?? 0;
const totalTokens = entry.usage.total ?? input + output + cacheRead + cacheWrite;
const cost = entry.costTotal ?? 0;
cumulativeTokens += totalTokens;
cumulativeCost += cost;
points.push({
timestamp: ts,
input,
output,
cacheRead,
cacheWrite,
totalTokens,
cost,
cumulativeTokens,
cumulativeCost
});
}
});
const sortedPoints = points.toSorted((a, b) => a.timestamp - b.timestamp);
const maxPoints = params.maxPoints ?? 100;
if (sortedPoints.length > maxPoints) {
const step = Math.ceil(sortedPoints.length / maxPoints);
const downsampled = [];
for (let i = 0; i < sortedPoints.length; i += step) downsampled.push(sortedPoints[i]);
if (downsampled[downsampled.length - 1] !== sortedPoints[sortedPoints.length - 1]) downsampled.push(sortedPoints[sortedPoints.length - 1]);
return {
sessionId: params.sessionId,
points: downsampled
};
}
return {
sessionId: params.sessionId,
points: sortedPoints
};
}
async function loadSessionLogs(params) {
const sessionFile = params.sessionFile ?? (params.sessionId ? resolveSessionFilePath(params.sessionId, params.sessionEntry) : void 0);
if (!sessionFile || !fs.existsSync(sessionFile)) return null;
const logs = [];
const limit = params.limit ?? 50;
const fileStream = fs.createReadStream(sessionFile, { encoding: "utf-8" });
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity
});
for await (const line of rl) {
const trimmed = line.trim();
if (!trimmed) continue;
try {
const parsed = JSON.parse(trimmed);
const message = parsed.message;
if (!message) continue;
const role = message.role;
if (role !== "user" && role !== "assistant" && role !== "tool" && role !== "toolResult") continue;
const contentParts = [];
const rawToolName = message.toolName ?? message.tool_name ?? message.name ?? message.tool;
const toolName = typeof rawToolName === "string" && rawToolName.trim() ? rawToolName.trim() : void 0;
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;
const maxLen = 2e3;
if (content.length > maxLen) content = content.slice(0, maxLen) + "…";
let timestamp = 0;
if (typeof parsed.timestamp === "string") timestamp = new Date(parsed.timestamp).getTime();
else if (typeof message.timestamp === "number") timestamp = message.timestamp;
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);
if (breakdown?.total !== void 0) cost = breakdown.total;
else cost = estimateUsageCost({
usage,
cost: resolveModelCostConfig({
provider: message.provider,
model: message.model,
config: params.config
})
});
}
}
logs.push({
timestamp,
role,
content,
tokens,
cost
});
} catch {}
}
const sortedLogs = logs.toSorted((a, b) => a.timestamp - b.timestamp);
if (sortedLogs.length > limit) return sortedLogs.slice(-limit);
return sortedLogs;
}
//#endregion
export { session_cost_usage_exports as a, derivePromptTokens as c, loadSessionUsageTimeSeries as i, hasNonzeroUsage as l, loadCostUsageSummary as n, extractToolCallNames as o, loadSessionCostSummary as r, hasToolCall as s, discoverAllSessions as t, normalizeUsage as u };