@chrysb/alphaclaw 0.7.2-beta.0 → 0.7.2-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/public/js/components/cron-tab/cron-run-history-panel.js +41 -22
- package/lib/public/js/components/usage-tab/constants.js +8 -0
- package/lib/public/js/components/usage-tab/formatters.js +13 -0
- package/lib/public/js/components/usage-tab/index.js +2 -0
- package/lib/public/js/components/usage-tab/overview-section.js +22 -4
- package/lib/public/js/components/usage-tab/use-usage-tab.js +51 -14
- package/lib/server/db/usage/summary.js +101 -1
- package/package.json +1 -1
|
@@ -87,11 +87,23 @@ const renderEntrySummaryRow = ({ runEntry = {}, variant = "overview" }) => {
|
|
|
87
87
|
</div>
|
|
88
88
|
`;
|
|
89
89
|
};
|
|
90
|
+
const getCollapsedGroupAggregates = (entries = []) =>
|
|
91
|
+
entries.reduce(
|
|
92
|
+
(accumulator, runEntry) => {
|
|
93
|
+
accumulator.totalTokens += getCronRunTotalTokens(runEntry);
|
|
94
|
+
const estimatedCost = getCronRunEstimatedCost(runEntry);
|
|
95
|
+
if (estimatedCost != null) {
|
|
96
|
+
accumulator.totalCost += estimatedCost;
|
|
97
|
+
accumulator.hasAnyCost = true;
|
|
98
|
+
}
|
|
99
|
+
return accumulator;
|
|
100
|
+
},
|
|
101
|
+
{ totalTokens: 0, totalCost: 0, hasAnyCost: false },
|
|
102
|
+
);
|
|
90
103
|
const renderCollapsedGroupRow = ({ row, rowIndex, onSelectJob = () => {} }) => {
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const timeRangeLabel = `[${formatOverviewTimestamp(row.oldestTs)} - ${formatOverviewTimestamp(row.newestTs)}]`;
|
|
104
|
+
const entries = Array.isArray(row?.entries) ? row.entries : [];
|
|
105
|
+
const { totalTokens, totalCost, hasAnyCost } = getCollapsedGroupAggregates(entries);
|
|
106
|
+
const timeRangeLabel = `${formatOverviewTimestamp(row.oldestTs)} - ${formatOverviewTimestamp(row.newestTs)}`;
|
|
95
107
|
return html`
|
|
96
108
|
<details
|
|
97
109
|
key=${`collapsed:${rowIndex}:${row.jobId}`}
|
|
@@ -101,30 +113,37 @@ const renderCollapsedGroupRow = ({ row, rowIndex, onSelectJob = () => {} }) => {
|
|
|
101
113
|
<div class="ac-history-summary-row">
|
|
102
114
|
<span class="inline-flex items-center gap-2 min-w-0">
|
|
103
115
|
<span class="ac-history-toggle shrink-0" aria-hidden="true">▸</span>
|
|
104
|
-
<span class="
|
|
105
|
-
|
|
106
|
-
|
|
116
|
+
<span class="inline-flex items-center gap-2 min-w-0">
|
|
117
|
+
<span class="truncate text-xs text-gray-300">
|
|
118
|
+
${row.jobName} - ${formatTokenCount(row.count)} runs
|
|
119
|
+
</span>
|
|
120
|
+
<span class="text-xs text-gray-500 shrink-0">${timeRangeLabel}</span>
|
|
121
|
+
</span>
|
|
122
|
+
</span>
|
|
123
|
+
<span class="inline-flex items-center gap-3 shrink-0 text-xs">
|
|
124
|
+
<span class="text-gray-400">${formatTokenCount(totalTokens)} tk</span>
|
|
125
|
+
<span class="text-gray-500">
|
|
126
|
+
${hasAnyCost ? `~${formatCost(totalCost)}` : "—"}
|
|
107
127
|
</span>
|
|
108
128
|
</span>
|
|
109
129
|
</div>
|
|
110
130
|
</summary>
|
|
111
131
|
<div class="ac-history-body space-y-2 text-xs">
|
|
112
|
-
|
|
113
|
-
${formatTokenCount(row.count)} consecutive runs collapsed
|
|
114
|
-
(${timeRangeLabel})
|
|
115
|
-
</div>
|
|
116
|
-
<div class="text-gray-500">Statuses: ${statusSummary}</div>
|
|
117
|
-
${Array.isArray(row?.entries) && row.entries.length > 0
|
|
132
|
+
${entries.length > 0
|
|
118
133
|
? html`
|
|
119
|
-
<div class="ac-
|
|
120
|
-
${
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
134
|
+
<div class="ac-history-list">
|
|
135
|
+
${entries.map((runEntry, entryIndex) =>
|
|
136
|
+
renderEntryRow({
|
|
137
|
+
row: {
|
|
138
|
+
type: "entry",
|
|
139
|
+
entry: runEntry,
|
|
140
|
+
},
|
|
141
|
+
rowIndex: `${rowIndex}:${entryIndex}`,
|
|
142
|
+
variant: "overview",
|
|
143
|
+
onSelectJob,
|
|
144
|
+
showOpenJobButton: false,
|
|
145
|
+
}),
|
|
146
|
+
)}
|
|
128
147
|
</div>
|
|
129
148
|
`
|
|
130
149
|
: null}
|
|
@@ -26,6 +26,14 @@ export const kRangeOptions = [
|
|
|
26
26
|
|
|
27
27
|
export const kDefaultUsageDays = 30;
|
|
28
28
|
export const kDefaultUsageMetric = "tokens";
|
|
29
|
+
export const kDefaultUsageBreakdown = "model";
|
|
29
30
|
export const kUsageDaysUiSettingKey = "usageDays";
|
|
30
31
|
export const kUsageMetricUiSettingKey = "usageMetric";
|
|
32
|
+
export const kUsageBreakdownUiSettingKey = "usageBreakdown";
|
|
31
33
|
export const kUsageSourceOrder = ["chat", "hooks", "cron"];
|
|
34
|
+
|
|
35
|
+
export const kUsageBreakdownOptions = [
|
|
36
|
+
{ label: "Model breakdown", value: "model" },
|
|
37
|
+
{ label: "Type breakdown", value: "source" },
|
|
38
|
+
{ label: "Agent breakdown", value: "agent" },
|
|
39
|
+
];
|
|
@@ -22,3 +22,16 @@ export const renderSourceLabel = (source) => {
|
|
|
22
22
|
if (source === "cron") return "Cron";
|
|
23
23
|
return "Chat";
|
|
24
24
|
};
|
|
25
|
+
|
|
26
|
+
export const renderBreakdownLabel = (value, breakdown) => {
|
|
27
|
+
const normalizedBreakdown = String(breakdown || "model");
|
|
28
|
+
const raw = String(value || "").trim();
|
|
29
|
+
if (!raw) return "Unknown";
|
|
30
|
+
if (normalizedBreakdown === "source") {
|
|
31
|
+
return renderSourceLabel(raw);
|
|
32
|
+
}
|
|
33
|
+
if (normalizedBreakdown === "agent") {
|
|
34
|
+
return raw === "unknown" ? "Unknown agent" : raw;
|
|
35
|
+
}
|
|
36
|
+
return raw;
|
|
37
|
+
};
|
|
@@ -53,10 +53,12 @@ export const UsageTab = ({ sessionId = "" }) => {
|
|
|
53
53
|
summary=${state.summary}
|
|
54
54
|
periodSummary=${state.periodSummary}
|
|
55
55
|
metric=${state.metric}
|
|
56
|
+
breakdown=${state.breakdown}
|
|
56
57
|
days=${state.days}
|
|
57
58
|
overviewCanvasRef=${state.overviewCanvasRef}
|
|
58
59
|
onDaysChange=${actions.setDays}
|
|
59
60
|
onMetricChange=${actions.setMetric}
|
|
61
|
+
onBreakdownChange=${actions.setBreakdown}
|
|
60
62
|
/>
|
|
61
63
|
`}
|
|
62
64
|
<${SessionsSection}
|
|
@@ -7,7 +7,11 @@ import {
|
|
|
7
7
|
formatUsd,
|
|
8
8
|
} from "../../lib/format.js";
|
|
9
9
|
import { SegmentedControl } from "../segmented-control.js";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
kRangeOptions,
|
|
12
|
+
kUsageBreakdownOptions,
|
|
13
|
+
kUsageSourceOrder,
|
|
14
|
+
} from "./constants.js";
|
|
11
15
|
import { renderSourceLabel } from "./formatters.js";
|
|
12
16
|
|
|
13
17
|
const html = htm.bind(h);
|
|
@@ -215,10 +219,12 @@ export const OverviewSection = ({
|
|
|
215
219
|
summary = null,
|
|
216
220
|
periodSummary,
|
|
217
221
|
metric = "tokens",
|
|
222
|
+
breakdown = "model",
|
|
218
223
|
days = 30,
|
|
219
224
|
overviewCanvasRef,
|
|
220
225
|
onDaysChange = () => {},
|
|
221
226
|
onMetricChange = () => {},
|
|
227
|
+
onBreakdownChange = () => {},
|
|
222
228
|
}) => {
|
|
223
229
|
const overviewMetrics = getOverviewMetrics(summary);
|
|
224
230
|
|
|
@@ -264,9 +270,21 @@ export const OverviewSection = ({
|
|
|
264
270
|
<div
|
|
265
271
|
class="flex flex-col sm:flex-row sm:items-center justify-between gap-2 mb-3"
|
|
266
272
|
>
|
|
267
|
-
<
|
|
268
|
-
|
|
269
|
-
|
|
273
|
+
<label class="inline-flex items-center gap-2">
|
|
274
|
+
<select
|
|
275
|
+
class="bg-black/30 border border-border rounded-lg text-xs px-2.5 py-1.5 text-gray-200 focus:border-gray-500"
|
|
276
|
+
value=${breakdown}
|
|
277
|
+
onChange=${(event) =>
|
|
278
|
+
onBreakdownChange(String(event.currentTarget?.value || "model"))}
|
|
279
|
+
aria-label="Usage chart breakdown"
|
|
280
|
+
>
|
|
281
|
+
${kUsageBreakdownOptions.map(
|
|
282
|
+
(option) => html`
|
|
283
|
+
<option value=${option.value}>${option.label}</option>
|
|
284
|
+
`,
|
|
285
|
+
)}
|
|
286
|
+
</select>
|
|
287
|
+
</label>
|
|
270
288
|
<div class="flex items-center gap-2">
|
|
271
289
|
<${SegmentedControl}
|
|
272
290
|
options=${kRangeOptions.map((option) => ({
|
|
@@ -13,12 +13,14 @@ import {
|
|
|
13
13
|
import { formatInteger, formatUsd } from "../../lib/format.js";
|
|
14
14
|
import { readUiSettings, writeUiSettings } from "../../lib/ui-settings.js";
|
|
15
15
|
import {
|
|
16
|
+
kDefaultUsageBreakdown,
|
|
16
17
|
kDefaultUsageDays,
|
|
17
18
|
kDefaultUsageMetric,
|
|
19
|
+
kUsageBreakdownUiSettingKey,
|
|
18
20
|
kUsageDaysUiSettingKey,
|
|
19
21
|
kUsageMetricUiSettingKey,
|
|
20
22
|
} from "./constants.js";
|
|
21
|
-
import { toChartColor, toLocalDayKey } from "./formatters.js";
|
|
23
|
+
import { renderBreakdownLabel, toChartColor, toLocalDayKey } from "./formatters.js";
|
|
22
24
|
|
|
23
25
|
export const useUsageTab = ({ sessionId = "" }) => {
|
|
24
26
|
const [days, setDays] = useState(() => {
|
|
@@ -35,6 +37,13 @@ export const useUsageTab = ({ sessionId = "" }) => {
|
|
|
35
37
|
? "cost"
|
|
36
38
|
: kDefaultUsageMetric;
|
|
37
39
|
});
|
|
40
|
+
const [breakdown, setBreakdown] = useState(() => {
|
|
41
|
+
const settings = readUiSettings();
|
|
42
|
+
const configured = String(settings[kUsageBreakdownUiSettingKey] || "").trim();
|
|
43
|
+
return configured === "source" || configured === "agent"
|
|
44
|
+
? configured
|
|
45
|
+
: kDefaultUsageBreakdown;
|
|
46
|
+
});
|
|
38
47
|
const [summary, setSummary] = useState(null);
|
|
39
48
|
const [sessions, setSessions] = useState([]);
|
|
40
49
|
const [sessionDetailById, setSessionDetailById] = useState({});
|
|
@@ -104,8 +113,9 @@ export const useUsageTab = ({ sessionId = "" }) => {
|
|
|
104
113
|
const settings = readUiSettings();
|
|
105
114
|
settings[kUsageDaysUiSettingKey] = days;
|
|
106
115
|
settings[kUsageMetricUiSettingKey] = metric;
|
|
116
|
+
settings[kUsageBreakdownUiSettingKey] = breakdown;
|
|
107
117
|
writeUiSettings(settings);
|
|
108
|
-
}, [days, metric]);
|
|
118
|
+
}, [days, metric, breakdown]);
|
|
109
119
|
|
|
110
120
|
useEffect(() => {
|
|
111
121
|
loadSessions();
|
|
@@ -166,28 +176,52 @@ export const useUsageTab = ({ sessionId = "" }) => {
|
|
|
166
176
|
|
|
167
177
|
const overviewDatasets = useMemo(() => {
|
|
168
178
|
const rows = Array.isArray(summary?.daily) ? summary.daily : [];
|
|
169
|
-
const
|
|
179
|
+
const allBreakdownKeys = new Set();
|
|
180
|
+
const totalsByBreakdownKey = new Map();
|
|
181
|
+
const breakdownRowKey =
|
|
182
|
+
breakdown === "source" ? "sources" : breakdown === "agent" ? "agents" : "models";
|
|
183
|
+
const breakdownValueKey =
|
|
184
|
+
breakdown === "source" ? "source" : breakdown === "agent" ? "agent" : "model";
|
|
170
185
|
for (const dayRow of rows) {
|
|
171
|
-
for (const
|
|
172
|
-
|
|
186
|
+
for (const breakdownRow of dayRow[breakdownRowKey] || []) {
|
|
187
|
+
const bucketKey = String(breakdownRow[breakdownValueKey] || "unknown");
|
|
188
|
+
allBreakdownKeys.add(bucketKey);
|
|
189
|
+
totalsByBreakdownKey.set(
|
|
190
|
+
bucketKey,
|
|
191
|
+
Number(totalsByBreakdownKey.get(bucketKey) || 0) +
|
|
192
|
+
Number(
|
|
193
|
+
metric === "cost"
|
|
194
|
+
? breakdownRow.totalCost || 0
|
|
195
|
+
: breakdownRow.totalTokens || 0,
|
|
196
|
+
),
|
|
197
|
+
);
|
|
173
198
|
}
|
|
174
199
|
}
|
|
175
200
|
const labels = rows.map((row) => String(row.date || ""));
|
|
176
|
-
const
|
|
177
|
-
|
|
201
|
+
const orderedBreakdownKeys = Array.from(allBreakdownKeys).sort(
|
|
202
|
+
(leftValue, rightValue) => {
|
|
203
|
+
const leftTotal = Number(totalsByBreakdownKey.get(leftValue) || 0);
|
|
204
|
+
const rightTotal = Number(totalsByBreakdownKey.get(rightValue) || 0);
|
|
205
|
+
if (rightTotal !== leftTotal) return rightTotal - leftTotal;
|
|
206
|
+
return leftValue.localeCompare(rightValue);
|
|
207
|
+
},
|
|
208
|
+
);
|
|
209
|
+
const datasets = orderedBreakdownKeys.map((bucketKey) => ({
|
|
210
|
+
label: bucketKey,
|
|
178
211
|
data: rows.map((row) => {
|
|
179
|
-
const found = (row
|
|
180
|
-
(
|
|
212
|
+
const found = (row[breakdownRowKey] || []).find(
|
|
213
|
+
(breakdownRow) =>
|
|
214
|
+
String(breakdownRow[breakdownValueKey] || "") === bucketKey,
|
|
181
215
|
);
|
|
182
216
|
if (!found) return 0;
|
|
183
217
|
return metric === "cost"
|
|
184
218
|
? Number(found.totalCost || 0)
|
|
185
219
|
: Number(found.totalTokens || 0);
|
|
186
220
|
}),
|
|
187
|
-
backgroundColor: toChartColor(
|
|
221
|
+
backgroundColor: toChartColor(`${breakdown}:${bucketKey}`),
|
|
188
222
|
}));
|
|
189
223
|
return { labels, datasets };
|
|
190
|
-
}, [summary, metric]);
|
|
224
|
+
}, [summary, metric, breakdown]);
|
|
191
225
|
|
|
192
226
|
useEffect(() => {
|
|
193
227
|
const canvas = overviewCanvasRef.current;
|
|
@@ -229,9 +263,10 @@ export const useUsageTab = ({ sessionId = "" }) => {
|
|
|
229
263
|
callbacks: {
|
|
230
264
|
label: (context) => {
|
|
231
265
|
const value = Number(context.parsed.y || 0);
|
|
266
|
+
const label = renderBreakdownLabel(context.dataset.label, breakdown);
|
|
232
267
|
return metric === "cost"
|
|
233
|
-
? `${
|
|
234
|
-
: `${
|
|
268
|
+
? `${label}: ${formatUsd(value)}`
|
|
269
|
+
: `${label}: ${formatInteger(value)} tokens`;
|
|
235
270
|
},
|
|
236
271
|
},
|
|
237
272
|
},
|
|
@@ -244,12 +279,13 @@ export const useUsageTab = ({ sessionId = "" }) => {
|
|
|
244
279
|
overviewChartRef.current = null;
|
|
245
280
|
}
|
|
246
281
|
};
|
|
247
|
-
}, [overviewDatasets, metric]);
|
|
282
|
+
}, [overviewDatasets, metric, breakdown]);
|
|
248
283
|
|
|
249
284
|
return {
|
|
250
285
|
state: {
|
|
251
286
|
days,
|
|
252
287
|
metric,
|
|
288
|
+
breakdown,
|
|
253
289
|
summary,
|
|
254
290
|
sessions,
|
|
255
291
|
sessionDetailById,
|
|
@@ -264,6 +300,7 @@ export const useUsageTab = ({ sessionId = "" }) => {
|
|
|
264
300
|
actions: {
|
|
265
301
|
setDays,
|
|
266
302
|
setMetric,
|
|
303
|
+
setBreakdown,
|
|
267
304
|
loadSummary,
|
|
268
305
|
loadSessionDetail,
|
|
269
306
|
setExpandedSessionIds,
|
|
@@ -153,12 +153,16 @@ const getDailySummary = ({
|
|
|
153
153
|
`)
|
|
154
154
|
.all({ $lookbackMs: lookbackMs });
|
|
155
155
|
const byDateModel = new Map();
|
|
156
|
+
const byDateSource = new Map();
|
|
157
|
+
const byDateAgent = new Map();
|
|
156
158
|
for (const eventRow of eventsRows) {
|
|
157
159
|
const timestamp = coerceInt(eventRow.timestamp);
|
|
158
160
|
const dayKey = normalizedTimeZone === kUtcTimeZone
|
|
159
161
|
? toDayKey(timestamp)
|
|
160
162
|
: toTimeZoneDayKey(timestamp, normalizedTimeZone);
|
|
161
163
|
if (dayKey < startDay) continue;
|
|
164
|
+
const sessionRef = String(eventRow.session_key || eventRow.session_id || "");
|
|
165
|
+
const { agent, source } = parseAgentAndSourceFromSessionRef(sessionRef);
|
|
162
166
|
const model = String(eventRow.model || "unknown");
|
|
163
167
|
const mapKey = `${dayKey}\u0000${model}`;
|
|
164
168
|
if (!byDateModel.has(mapKey)) {
|
|
@@ -197,6 +201,52 @@ const getDailySummary = ({
|
|
|
197
201
|
if (!aggregate.provider && eventRow.provider) {
|
|
198
202
|
aggregate.provider = String(eventRow.provider || "unknown");
|
|
199
203
|
}
|
|
204
|
+
|
|
205
|
+
const sourceMapKey = `${dayKey}\u0000${source}`;
|
|
206
|
+
if (!byDateSource.has(sourceMapKey)) {
|
|
207
|
+
byDateSource.set(sourceMapKey, {
|
|
208
|
+
source,
|
|
209
|
+
date: dayKey,
|
|
210
|
+
inputTokens: 0,
|
|
211
|
+
outputTokens: 0,
|
|
212
|
+
cacheReadTokens: 0,
|
|
213
|
+
cacheWriteTokens: 0,
|
|
214
|
+
totalTokens: 0,
|
|
215
|
+
turnCount: 0,
|
|
216
|
+
totalCost: 0,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
const sourceAggregate = byDateSource.get(sourceMapKey);
|
|
220
|
+
sourceAggregate.inputTokens += metrics.inputTokens;
|
|
221
|
+
sourceAggregate.outputTokens += metrics.outputTokens;
|
|
222
|
+
sourceAggregate.cacheReadTokens += metrics.cacheReadTokens;
|
|
223
|
+
sourceAggregate.cacheWriteTokens += metrics.cacheWriteTokens;
|
|
224
|
+
sourceAggregate.totalTokens += metrics.totalTokens;
|
|
225
|
+
sourceAggregate.turnCount += 1;
|
|
226
|
+
sourceAggregate.totalCost += metrics.totalCost;
|
|
227
|
+
|
|
228
|
+
const agentMapKey = `${dayKey}\u0000${agent}`;
|
|
229
|
+
if (!byDateAgent.has(agentMapKey)) {
|
|
230
|
+
byDateAgent.set(agentMapKey, {
|
|
231
|
+
agent,
|
|
232
|
+
date: dayKey,
|
|
233
|
+
inputTokens: 0,
|
|
234
|
+
outputTokens: 0,
|
|
235
|
+
cacheReadTokens: 0,
|
|
236
|
+
cacheWriteTokens: 0,
|
|
237
|
+
totalTokens: 0,
|
|
238
|
+
turnCount: 0,
|
|
239
|
+
totalCost: 0,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
const agentAggregate = byDateAgent.get(agentMapKey);
|
|
243
|
+
agentAggregate.inputTokens += metrics.inputTokens;
|
|
244
|
+
agentAggregate.outputTokens += metrics.outputTokens;
|
|
245
|
+
agentAggregate.cacheReadTokens += metrics.cacheReadTokens;
|
|
246
|
+
agentAggregate.cacheWriteTokens += metrics.cacheWriteTokens;
|
|
247
|
+
agentAggregate.totalTokens += metrics.totalTokens;
|
|
248
|
+
agentAggregate.turnCount += 1;
|
|
249
|
+
agentAggregate.totalCost += metrics.totalCost;
|
|
200
250
|
}
|
|
201
251
|
const enriched = Array.from(byDateModel.values()).sort((a, b) => {
|
|
202
252
|
if (a.date === b.date) return b.totalTokens - a.totalTokens;
|
|
@@ -227,6 +277,50 @@ const getDailySummary = ({
|
|
|
227
277
|
pricingFound: row.pricingFound,
|
|
228
278
|
});
|
|
229
279
|
}
|
|
280
|
+
const byDateSourceRows = new Map();
|
|
281
|
+
for (const row of byDateSource.values()) {
|
|
282
|
+
if (!byDateSourceRows.has(row.date)) byDateSourceRows.set(row.date, []);
|
|
283
|
+
byDateSourceRows.get(row.date).push({
|
|
284
|
+
source: row.source,
|
|
285
|
+
inputTokens: row.inputTokens,
|
|
286
|
+
outputTokens: row.outputTokens,
|
|
287
|
+
cacheReadTokens: row.cacheReadTokens,
|
|
288
|
+
cacheWriteTokens: row.cacheWriteTokens,
|
|
289
|
+
totalTokens: row.totalTokens,
|
|
290
|
+
turnCount: row.turnCount,
|
|
291
|
+
totalCost: row.totalCost,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
for (const rows of byDateSourceRows.values()) {
|
|
295
|
+
rows.sort((left, right) => {
|
|
296
|
+
if (right.totalTokens !== left.totalTokens) {
|
|
297
|
+
return right.totalTokens - left.totalTokens;
|
|
298
|
+
}
|
|
299
|
+
return String(left.source || "").localeCompare(String(right.source || ""));
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
const byDateAgentRows = new Map();
|
|
303
|
+
for (const row of byDateAgent.values()) {
|
|
304
|
+
if (!byDateAgentRows.has(row.date)) byDateAgentRows.set(row.date, []);
|
|
305
|
+
byDateAgentRows.get(row.date).push({
|
|
306
|
+
agent: row.agent,
|
|
307
|
+
inputTokens: row.inputTokens,
|
|
308
|
+
outputTokens: row.outputTokens,
|
|
309
|
+
cacheReadTokens: row.cacheReadTokens,
|
|
310
|
+
cacheWriteTokens: row.cacheWriteTokens,
|
|
311
|
+
totalTokens: row.totalTokens,
|
|
312
|
+
turnCount: row.turnCount,
|
|
313
|
+
totalCost: row.totalCost,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
for (const rows of byDateAgentRows.values()) {
|
|
317
|
+
rows.sort((left, right) => {
|
|
318
|
+
if (right.totalTokens !== left.totalTokens) {
|
|
319
|
+
return right.totalTokens - left.totalTokens;
|
|
320
|
+
}
|
|
321
|
+
return String(left.agent || "").localeCompare(String(right.agent || ""));
|
|
322
|
+
});
|
|
323
|
+
}
|
|
230
324
|
const daily = [];
|
|
231
325
|
const totals = {
|
|
232
326
|
inputTokens: 0,
|
|
@@ -259,7 +353,13 @@ const getDailySummary = ({
|
|
|
259
353
|
turnCount: 0,
|
|
260
354
|
},
|
|
261
355
|
);
|
|
262
|
-
daily.push({
|
|
356
|
+
daily.push({
|
|
357
|
+
date,
|
|
358
|
+
...aggregate,
|
|
359
|
+
models: modelRows,
|
|
360
|
+
sources: byDateSourceRows.get(date) || [],
|
|
361
|
+
agents: byDateAgentRows.get(date) || [],
|
|
362
|
+
});
|
|
263
363
|
totals.inputTokens += aggregate.inputTokens;
|
|
264
364
|
totals.outputTokens += aggregate.outputTokens;
|
|
265
365
|
totals.cacheReadTokens += aggregate.cacheReadTokens;
|