@roberttlange/agentlens 0.2.3 → 0.3.0
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/dist/browser.js +154 -20
- package/dist/browser.js.map +1 -1
- package/dist/main.test.js +138 -1
- package/dist/main.test.js.map +1 -1
- package/node_modules/@agentlens/contracts/dist/index.d.ts +109 -0
- package/node_modules/@agentlens/core/dist/__tests__/config.test.js +18 -0
- package/node_modules/@agentlens/core/dist/__tests__/config.test.js.map +1 -1
- package/node_modules/@agentlens/core/dist/__tests__/index.test.js +370 -2
- package/node_modules/@agentlens/core/dist/__tests__/index.test.js.map +1 -1
- package/node_modules/@agentlens/core/dist/config.js +33 -0
- package/node_modules/@agentlens/core/dist/config.js.map +1 -1
- package/node_modules/@agentlens/core/dist/metrics.d.ts +13 -0
- package/node_modules/@agentlens/core/dist/metrics.js +98 -2
- package/node_modules/@agentlens/core/dist/metrics.js.map +1 -1
- package/node_modules/@agentlens/core/dist/sourceProfiles.js +4 -0
- package/node_modules/@agentlens/core/dist/sourceProfiles.js.map +1 -1
- package/node_modules/@agentlens/core/dist/traceIndex.d.ts +22 -1
- package/node_modules/@agentlens/core/dist/traceIndex.js +322 -21
- package/node_modules/@agentlens/core/dist/traceIndex.js.map +1 -1
- package/node_modules/@agentlens/server/dist/activity-cache.d.ts +6 -3
- package/node_modules/@agentlens/server/dist/activity-cache.js +6 -0
- package/node_modules/@agentlens/server/dist/activity-cache.js.map +1 -1
- package/node_modules/@agentlens/server/dist/activity-cache.test.js +86 -0
- package/node_modules/@agentlens/server/dist/activity-cache.test.js.map +1 -1
- package/node_modules/@agentlens/server/dist/activity.d.ts +20 -1
- package/node_modules/@agentlens/server/dist/activity.js +482 -6
- package/node_modules/@agentlens/server/dist/activity.js.map +1 -1
- package/node_modules/@agentlens/server/dist/app.d.ts +4 -2
- package/node_modules/@agentlens/server/dist/app.js +242 -5
- package/node_modules/@agentlens/server/dist/app.js.map +1 -1
- package/node_modules/@agentlens/server/dist/app.test.js +669 -8
- package/node_modules/@agentlens/server/dist/app.test.js.map +1 -1
- package/node_modules/@agentlens/server/dist/web/assets/index-CTFOBaBt.css +1 -0
- package/node_modules/@agentlens/server/dist/web/assets/index-CVf00w06.js +52 -0
- package/node_modules/@agentlens/server/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/node_modules/@agentlens/server/dist/web/assets/index-DjwZvHl6.css +0 -1
- package/node_modules/@agentlens/server/dist/web/assets/index-Ei_qfgA9.js +0 -52
|
@@ -13,6 +13,8 @@ const MAX_WEEK_DAY_COUNT = 366;
|
|
|
13
13
|
const DEFAULT_WEEK_SLOT_MINUTES = 30;
|
|
14
14
|
const DEFAULT_WEEK_HOUR_START_LOCAL = 0;
|
|
15
15
|
const DEFAULT_WEEK_HOUR_END_LOCAL = 24;
|
|
16
|
+
const DEFAULT_YEAR_SLOT_MINUTES = 30;
|
|
17
|
+
const DEFAULT_YEAR_HOUR_START_LOCAL = 7;
|
|
16
18
|
const MIN_TZ_OFFSET_MINUTES = -14 * 60;
|
|
17
19
|
const MAX_TZ_OFFSET_MINUTES = 14 * 60;
|
|
18
20
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
@@ -106,6 +108,184 @@ function createEmptyEventKindCounts() {
|
|
|
106
108
|
meta: 0,
|
|
107
109
|
};
|
|
108
110
|
}
|
|
111
|
+
function createUsageSummaryRow(agent) {
|
|
112
|
+
return {
|
|
113
|
+
agent,
|
|
114
|
+
sessionHours: 0,
|
|
115
|
+
sessionSharePct: 0,
|
|
116
|
+
uniqueSessions: 0,
|
|
117
|
+
activeSlots: 0,
|
|
118
|
+
activeDays: 0,
|
|
119
|
+
peakConcurrentSessions: 0,
|
|
120
|
+
inputTokens: 0,
|
|
121
|
+
cacheTokens: 0,
|
|
122
|
+
outputTokens: 0,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function createUsageAccumulator() {
|
|
126
|
+
const usageByAgent = new Map();
|
|
127
|
+
const uniqueSessionIdsByAgent = new Map();
|
|
128
|
+
for (const agent of AGENT_KIND_KEYS) {
|
|
129
|
+
usageByAgent.set(agent, createUsageSummaryRow(agent));
|
|
130
|
+
uniqueSessionIdsByAgent.set(agent, new Set());
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
usageByAgent,
|
|
134
|
+
uniqueSessionIdsByAgent,
|
|
135
|
+
totalUniqueSessionIds: new Set(),
|
|
136
|
+
peakAllAgentConcurrency: 0,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function createEmptyHeatmapMetricValues() {
|
|
140
|
+
return {
|
|
141
|
+
sessions: 0,
|
|
142
|
+
output_tokens: 0,
|
|
143
|
+
total_cost_usd: 0,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function sanitizeTokenValue(value) {
|
|
147
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
|
|
148
|
+
}
|
|
149
|
+
function sanitizeCostValue(value) {
|
|
150
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
|
|
151
|
+
}
|
|
152
|
+
function normalizeHexColor(value) {
|
|
153
|
+
const trimmed = value.trim();
|
|
154
|
+
const shortMatch = /^#([0-9a-fA-F]{3})$/.exec(trimmed);
|
|
155
|
+
if (shortMatch) {
|
|
156
|
+
const digits = shortMatch[1] ?? "";
|
|
157
|
+
return `#${digits
|
|
158
|
+
.split("")
|
|
159
|
+
.map((digit) => `${digit}${digit}`)
|
|
160
|
+
.join("")
|
|
161
|
+
.toLowerCase()}`;
|
|
162
|
+
}
|
|
163
|
+
const longMatch = /^#([0-9a-fA-F]{6})$/.exec(trimmed);
|
|
164
|
+
if (longMatch) {
|
|
165
|
+
return `#${(longMatch[1] ?? "").toLowerCase()}`;
|
|
166
|
+
}
|
|
167
|
+
return "#dc2626";
|
|
168
|
+
}
|
|
169
|
+
function hexChannel(color, start) {
|
|
170
|
+
return Number.parseInt(color.slice(start, start + 2), 16);
|
|
171
|
+
}
|
|
172
|
+
function toHexChannel(value) {
|
|
173
|
+
return Math.round(clamp(value, 0, 255))
|
|
174
|
+
.toString(16)
|
|
175
|
+
.padStart(2, "0");
|
|
176
|
+
}
|
|
177
|
+
function mixHexColor(color, colorWeight) {
|
|
178
|
+
const normalized = normalizeHexColor(color);
|
|
179
|
+
const baseWeight = clamp(colorWeight, 0, 1);
|
|
180
|
+
const whiteWeight = 1 - baseWeight;
|
|
181
|
+
const red = hexChannel(normalized, 1) * baseWeight + 255 * whiteWeight;
|
|
182
|
+
const green = hexChannel(normalized, 3) * baseWeight + 255 * whiteWeight;
|
|
183
|
+
const blue = hexChannel(normalized, 5) * baseWeight + 255 * whiteWeight;
|
|
184
|
+
return `#${toHexChannel(red)}${toHexChannel(green)}${toHexChannel(blue)}`;
|
|
185
|
+
}
|
|
186
|
+
function buildHeatmapPresentation(metric, color) {
|
|
187
|
+
const normalizedColor = normalizeHexColor(color);
|
|
188
|
+
return {
|
|
189
|
+
metric,
|
|
190
|
+
color: normalizedColor,
|
|
191
|
+
palette: [
|
|
192
|
+
"#ffffff",
|
|
193
|
+
mixHexColor(normalizedColor, 0.18),
|
|
194
|
+
mixHexColor(normalizedColor, 0.38),
|
|
195
|
+
mixHexColor(normalizedColor, 0.62),
|
|
196
|
+
mixHexColor(normalizedColor, 0.82),
|
|
197
|
+
],
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function sumHeatmapMetricForBins(bins, metric) {
|
|
201
|
+
return bins.reduce((sum, bin) => {
|
|
202
|
+
if (metric === "sessions")
|
|
203
|
+
return sum + (bin.heatmapValues?.sessions ?? bin.activeSessionCount);
|
|
204
|
+
if (metric === "output_tokens")
|
|
205
|
+
return sum + (bin.heatmapValues?.output_tokens ?? 0);
|
|
206
|
+
return sum + (bin.heatmapValues?.total_cost_usd ?? 0);
|
|
207
|
+
}, 0);
|
|
208
|
+
}
|
|
209
|
+
function totalEventCountForBins(bins) {
|
|
210
|
+
return bins.reduce((sum, bin) => sum + bin.eventCount, 0);
|
|
211
|
+
}
|
|
212
|
+
function finalizeUsageSummary(accumulator) {
|
|
213
|
+
for (const agent of AGENT_KIND_KEYS) {
|
|
214
|
+
const row = accumulator.usageByAgent.get(agent);
|
|
215
|
+
const sessions = accumulator.uniqueSessionIdsByAgent.get(agent);
|
|
216
|
+
if (!row || !sessions)
|
|
217
|
+
continue;
|
|
218
|
+
row.uniqueSessions = sessions.size;
|
|
219
|
+
}
|
|
220
|
+
const totalSessionHours = AGENT_KIND_KEYS.reduce((sum, agent) => sum + (accumulator.usageByAgent.get(agent)?.sessionHours ?? 0), 0);
|
|
221
|
+
const rows = AGENT_KIND_KEYS.map((agent) => accumulator.usageByAgent.get(agent))
|
|
222
|
+
.filter((row) => row.sessionHours > 0 || row.uniqueSessions > 0 || row.activeSlots > 0)
|
|
223
|
+
.map((row) => ({
|
|
224
|
+
...row,
|
|
225
|
+
sessionSharePct: totalSessionHours > 0 ? (row.sessionHours / totalSessionHours) * 100 : 0,
|
|
226
|
+
}))
|
|
227
|
+
.sort((left, right) => right.sessionHours - left.sessionHours || right.uniqueSessions - left.uniqueSessions || left.agent.localeCompare(right.agent));
|
|
228
|
+
return {
|
|
229
|
+
rows,
|
|
230
|
+
totals: {
|
|
231
|
+
totalUniqueSessions: accumulator.totalUniqueSessionIds.size,
|
|
232
|
+
totalSessionHours,
|
|
233
|
+
peakAllAgentConcurrency: accumulator.peakAllAgentConcurrency,
|
|
234
|
+
mostUsedAgent: rows[0]?.agent ?? null,
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function applyUsagePointToRow(row, point) {
|
|
239
|
+
row.inputTokens += sanitizeTokenValue(point.inputTokens);
|
|
240
|
+
row.cacheTokens += sanitizeTokenValue(point.cachedReadTokens) + sanitizeTokenValue(point.cachedCreateTokens);
|
|
241
|
+
row.outputTokens += sanitizeTokenValue(point.outputTokens);
|
|
242
|
+
}
|
|
243
|
+
function buildWeeklyUsageSummaryFromDays(traceIndex, days) {
|
|
244
|
+
const accumulator = createUsageAccumulator();
|
|
245
|
+
const summaryById = new Map(traceIndex.getSummaries().map((summary) => [summary.id, summary]));
|
|
246
|
+
for (const day of days) {
|
|
247
|
+
const activeAgentsToday = new Set();
|
|
248
|
+
for (const bin of day.bins) {
|
|
249
|
+
accumulator.peakAllAgentConcurrency = Math.max(accumulator.peakAllAgentConcurrency, bin.activeSessionCount);
|
|
250
|
+
const binHours = Math.max(0, bin.endMs - bin.startMs) / 3_600_000;
|
|
251
|
+
for (const agent of AGENT_KIND_KEYS) {
|
|
252
|
+
const agentSessionsInBin = bin.activeByAgent[agent] ?? 0;
|
|
253
|
+
if (agentSessionsInBin <= 0)
|
|
254
|
+
continue;
|
|
255
|
+
const row = accumulator.usageByAgent.get(agent);
|
|
256
|
+
if (!row)
|
|
257
|
+
continue;
|
|
258
|
+
row.sessionHours += agentSessionsInBin * binHours;
|
|
259
|
+
row.activeSlots += 1;
|
|
260
|
+
row.peakConcurrentSessions = Math.max(row.peakConcurrentSessions, agentSessionsInBin);
|
|
261
|
+
activeAgentsToday.add(agent);
|
|
262
|
+
}
|
|
263
|
+
for (const traceId of bin.activeTraceIds) {
|
|
264
|
+
accumulator.totalUniqueSessionIds.add(traceId);
|
|
265
|
+
const normalizedAgent = summaryById.get(traceId)?.agent ?? "unknown";
|
|
266
|
+
accumulator.uniqueSessionIdsByAgent.get(normalizedAgent)?.add(traceId);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
for (const agent of activeAgentsToday) {
|
|
270
|
+
const row = accumulator.usageByAgent.get(agent);
|
|
271
|
+
if (!row)
|
|
272
|
+
continue;
|
|
273
|
+
row.activeDays += 1;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
for (const summary of summaryById.values()) {
|
|
277
|
+
const usageArtifacts = traceIndex.getSessionUsageArtifacts(summary.id);
|
|
278
|
+
const row = accumulator.usageByAgent.get(summary.agent);
|
|
279
|
+
if (!row)
|
|
280
|
+
continue;
|
|
281
|
+
for (const point of usageArtifacts.usagePoints) {
|
|
282
|
+
if (!days.some((day) => point.timestampMs >= day.windowStartMs && point.timestampMs < day.windowEndMs))
|
|
283
|
+
continue;
|
|
284
|
+
applyUsagePointToRow(row, point);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return finalizeUsageSummary(accumulator);
|
|
288
|
+
}
|
|
109
289
|
function resolveSessionSpan(summary) {
|
|
110
290
|
const baseStart = summary.firstEventTs ?? summary.lastEventTs ?? summary.mtimeMs;
|
|
111
291
|
const baseEnd = summary.lastEventTs ?? summary.mtimeMs;
|
|
@@ -179,7 +359,7 @@ function applyBreakMarkers(bins, breakMinutes) {
|
|
|
179
359
|
}
|
|
180
360
|
markRunIfBreak(runStart, bins.length);
|
|
181
361
|
}
|
|
182
|
-
function computeWindowActivity(traceIndex, windowStartMs, windowEndMs, binMinutes, breakMinutes) {
|
|
362
|
+
function computeWindowActivity(traceIndex, windowStartMs, windowEndMs, binMinutes, breakMinutes, heatmapMetric) {
|
|
183
363
|
const binMs = binMinutes * 60_000;
|
|
184
364
|
const totalWindowMs = Math.max(0, windowEndMs - windowStartMs);
|
|
185
365
|
const binCount = totalWindowMs <= 0 ? 0 : Math.ceil(totalWindowMs / binMs);
|
|
@@ -191,6 +371,8 @@ function computeWindowActivity(traceIndex, windowStartMs, windowEndMs, binMinute
|
|
|
191
371
|
startMs,
|
|
192
372
|
endMs,
|
|
193
373
|
activeSessionCount: 0,
|
|
374
|
+
heatmapValue: 0,
|
|
375
|
+
heatmapValues: createEmptyHeatmapMetricValues(),
|
|
194
376
|
activeTraceIds: [],
|
|
195
377
|
primaryTraceId: "",
|
|
196
378
|
activeByAgent: createEmptyAgentCounts(),
|
|
@@ -234,6 +416,7 @@ function computeWindowActivity(traceIndex, windowStartMs, windowEndMs, binMinute
|
|
|
234
416
|
bin.activeTraceIds.push(summary.id);
|
|
235
417
|
bin.activeSessionCount += 1;
|
|
236
418
|
bin.activeByAgent[summary.agent] += 1;
|
|
419
|
+
bin.heatmapValues ??= createEmptyHeatmapMetricValues();
|
|
237
420
|
contributingSessionIds.add(summary.id);
|
|
238
421
|
}
|
|
239
422
|
}
|
|
@@ -249,6 +432,18 @@ function computeWindowActivity(traceIndex, windowStartMs, windowEndMs, binMinute
|
|
|
249
432
|
bin.eventCount += 1;
|
|
250
433
|
bin.eventKindCounts[event.eventKind] += 1;
|
|
251
434
|
}
|
|
435
|
+
const usageArtifacts = traceIndex.getSessionUsageArtifacts(summary.id);
|
|
436
|
+
for (const point of usageArtifacts.usagePoints) {
|
|
437
|
+
if (point.timestampMs < windowStartMs || point.timestampMs >= windowEndMs)
|
|
438
|
+
continue;
|
|
439
|
+
const binIndex = computeBinIndex(windowStartMs, binMs, binCount, point.timestampMs);
|
|
440
|
+
const bin = bins[binIndex];
|
|
441
|
+
if (!bin)
|
|
442
|
+
continue;
|
|
443
|
+
bin.heatmapValues ??= createEmptyHeatmapMetricValues();
|
|
444
|
+
bin.heatmapValues.output_tokens += sanitizeTokenValue(point.outputTokens);
|
|
445
|
+
bin.heatmapValues.total_cost_usd += sanitizeCostValue(point.costUsd);
|
|
446
|
+
}
|
|
252
447
|
}
|
|
253
448
|
}
|
|
254
449
|
let peakConcurrentSessions = 0;
|
|
@@ -264,6 +459,17 @@ function computeWindowActivity(traceIndex, windowStartMs, windowEndMs, binMinute
|
|
|
264
459
|
});
|
|
265
460
|
bin.primaryTraceId = bin.activeTraceIds[0] ?? "";
|
|
266
461
|
}
|
|
462
|
+
if (heatmapMetric === "sessions") {
|
|
463
|
+
bin.heatmapValue = bin.activeSessionCount;
|
|
464
|
+
}
|
|
465
|
+
bin.heatmapValues ??= createEmptyHeatmapMetricValues();
|
|
466
|
+
bin.heatmapValues.sessions = bin.activeSessionCount;
|
|
467
|
+
bin.heatmapValue =
|
|
468
|
+
heatmapMetric === "sessions"
|
|
469
|
+
? bin.heatmapValues.sessions
|
|
470
|
+
: heatmapMetric === "output_tokens"
|
|
471
|
+
? bin.heatmapValues.output_tokens
|
|
472
|
+
: bin.heatmapValues.total_cost_usd;
|
|
267
473
|
bin.dominantAgent = pickDominantAgent(bin.activeByAgent);
|
|
268
474
|
bin.dominantEventKind = pickDominantEventKind(bin.eventKindCounts);
|
|
269
475
|
if (bin.activeSessionCount > peakConcurrentSessions) {
|
|
@@ -279,16 +485,64 @@ function computeWindowActivity(traceIndex, windowStartMs, windowEndMs, binMinute
|
|
|
279
485
|
peakConcurrentAtMs,
|
|
280
486
|
};
|
|
281
487
|
}
|
|
282
|
-
function buildWindowActivity(traceIndex, windowStartMs, windowEndMs, binMinutes, breakMinutes, options) {
|
|
488
|
+
function buildWindowActivity(traceIndex, windowStartMs, windowEndMs, binMinutes, breakMinutes, options, heatmapMetric) {
|
|
283
489
|
if (!options.cache || options.cacheVersion === undefined) {
|
|
284
|
-
return computeWindowActivity(traceIndex, windowStartMs, windowEndMs, binMinutes, breakMinutes);
|
|
490
|
+
return computeWindowActivity(traceIndex, windowStartMs, windowEndMs, binMinutes, breakMinutes, heatmapMetric);
|
|
285
491
|
}
|
|
286
492
|
return options.cache.getOrBuildWindow(options.cacheVersion, options.nowMs, {
|
|
287
493
|
windowStartMs,
|
|
288
494
|
windowEndMs,
|
|
289
495
|
binMinutes,
|
|
290
496
|
breakMinutes,
|
|
291
|
-
|
|
497
|
+
heatmapMetric,
|
|
498
|
+
}, () => computeWindowActivity(traceIndex, windowStartMs, windowEndMs, binMinutes, breakMinutes, heatmapMetric));
|
|
499
|
+
}
|
|
500
|
+
export function resolveAgentActivityDayWindow(options = {}) {
|
|
501
|
+
const nowMs = typeof options.nowMs === "number" && Number.isFinite(options.nowMs) ? options.nowMs : Date.now();
|
|
502
|
+
const requestedTzOffset = validateInt(options.tzOffsetMinutes, "tz_offset_min");
|
|
503
|
+
const tzOffsetMinutes = clamp(requestedTzOffset ?? new Date(nowMs).getTimezoneOffset(), MIN_TZ_OFFSET_MINUTES, MAX_TZ_OFFSET_MINUTES);
|
|
504
|
+
const requestedDateLocal = options.dateLocal?.trim() ?? "";
|
|
505
|
+
const dateLocal = requestedDateLocal || toLocalDateString(nowMs, tzOffsetMinutes);
|
|
506
|
+
parseDateLocal(dateLocal);
|
|
507
|
+
const dayStartMs = windowStartMsForDateLocal(dateLocal, tzOffsetMinutes);
|
|
508
|
+
return {
|
|
509
|
+
windowStartMs: dayStartMs + DEFAULT_DAY_HOUR_START_LOCAL * 60 * 60_000,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
export function resolveAgentActivityWeekWindow(options = {}) {
|
|
513
|
+
const nowMs = typeof options.nowMs === "number" && Number.isFinite(options.nowMs) ? options.nowMs : Date.now();
|
|
514
|
+
const requestedTzOffset = validateInt(options.tzOffsetMinutes, "tz_offset_min");
|
|
515
|
+
const tzOffsetMinutes = clamp(requestedTzOffset ?? new Date(nowMs).getTimezoneOffset(), MIN_TZ_OFFSET_MINUTES, MAX_TZ_OFFSET_MINUTES);
|
|
516
|
+
const requestedEndDateLocal = options.endDateLocal?.trim() ?? "";
|
|
517
|
+
const endDateLocal = requestedEndDateLocal || toLocalDateString(nowMs, tzOffsetMinutes);
|
|
518
|
+
parseDateLocal(endDateLocal);
|
|
519
|
+
const requestedDayCount = validateInt(options.dayCount, "day_count");
|
|
520
|
+
const dayCount = clamp(requestedDayCount ?? DEFAULT_WEEK_DAY_COUNT, MIN_WEEK_DAY_COUNT, MAX_WEEK_DAY_COUNT);
|
|
521
|
+
const startDateLocal = shiftDateLocal(endDateLocal, -(dayCount - 1));
|
|
522
|
+
const requestedHourStart = validateInt(options.hourStartLocal, "hour_start");
|
|
523
|
+
const hourStartLocal = clamp(requestedHourStart ?? DEFAULT_WEEK_HOUR_START_LOCAL, 0, 23);
|
|
524
|
+
const dayStartMs = windowStartMsForDateLocal(startDateLocal, tzOffsetMinutes);
|
|
525
|
+
return {
|
|
526
|
+
windowStartMs: dayStartMs + hourStartLocal * 60 * 60_000,
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
export function resolveAgentActivityYearWindow(options = {}) {
|
|
530
|
+
const nowMs = typeof options.nowMs === "number" && Number.isFinite(options.nowMs) ? options.nowMs : Date.now();
|
|
531
|
+
const requestedTzOffset = validateInt(options.tzOffsetMinutes, "tz_offset_min");
|
|
532
|
+
const tzOffsetMinutes = clamp(requestedTzOffset ?? new Date(nowMs).getTimezoneOffset(), MIN_TZ_OFFSET_MINUTES, MAX_TZ_OFFSET_MINUTES);
|
|
533
|
+
const requestedEndDateLocal = options.endDateLocal?.trim() ?? "";
|
|
534
|
+
const endDateLocal = requestedEndDateLocal || toLocalDateString(nowMs, tzOffsetMinutes);
|
|
535
|
+
parseDateLocal(endDateLocal);
|
|
536
|
+
const requestedDayCount = validateInt(options.dayCount, "day_count");
|
|
537
|
+
const maxDayCount = clamp(requestedDayCount ?? MAX_WEEK_DAY_COUNT, MIN_WEEK_DAY_COUNT, MAX_WEEK_DAY_COUNT);
|
|
538
|
+
const yearStartDateLocal = `${endDateLocal.slice(0, 4)}-01-01`;
|
|
539
|
+
const nominalRangeDayCount = Math.floor((windowStartMsForDateLocal(endDateLocal, 0) - windowStartMsForDateLocal(yearStartDateLocal, 0)) / DAY_MS) + 1;
|
|
540
|
+
const dayCount = clamp(nominalRangeDayCount, MIN_WEEK_DAY_COUNT, Math.min(MAX_WEEK_DAY_COUNT, maxDayCount));
|
|
541
|
+
const startDateLocal = shiftDateLocal(endDateLocal, -(dayCount - 1));
|
|
542
|
+
const dayStartMs = windowStartMsForDateLocal(startDateLocal, tzOffsetMinutes);
|
|
543
|
+
return {
|
|
544
|
+
windowStartMs: dayStartMs + DEFAULT_YEAR_HOUR_START_LOCAL * 60 * 60_000,
|
|
545
|
+
};
|
|
292
546
|
}
|
|
293
547
|
export function buildAgentActivityDay(traceIndex, options = {}) {
|
|
294
548
|
const nowMs = typeof options.nowMs === "number" && Number.isFinite(options.nowMs) ? options.nowMs : Date.now();
|
|
@@ -324,7 +578,7 @@ export function buildAgentActivityDay(traceIndex, options = {}) {
|
|
|
324
578
|
const windowEndMs = dateLocal === todayLocal ? Math.max(windowStartMs, Math.min(windowEndOfDayMs, nowMs)) : windowEndOfDayMs;
|
|
325
579
|
const windowActivity = buildWindowActivity(traceIndex, windowStartMs, windowEndMs, binMinutes, breakMinutes, options.cache && options.cacheVersion !== undefined
|
|
326
580
|
? { cache: options.cache, cacheVersion: options.cacheVersion, nowMs }
|
|
327
|
-
: { nowMs });
|
|
581
|
+
: { nowMs }, "sessions");
|
|
328
582
|
return {
|
|
329
583
|
dateLocal,
|
|
330
584
|
tzOffsetMinutes,
|
|
@@ -335,6 +589,7 @@ export function buildAgentActivityDay(traceIndex, options = {}) {
|
|
|
335
589
|
totalSessionsInWindow: windowActivity.totalSessionsInWindow,
|
|
336
590
|
peakConcurrentSessions: windowActivity.peakConcurrentSessions,
|
|
337
591
|
peakConcurrentAtMs: windowActivity.peakConcurrentAtMs,
|
|
592
|
+
totalEventCount: totalEventCountForBins(windowActivity.bins),
|
|
338
593
|
bins: windowActivity.bins,
|
|
339
594
|
};
|
|
340
595
|
}
|
|
@@ -345,6 +600,8 @@ export function buildAgentActivityWeek(traceIndex, options = {}) {
|
|
|
345
600
|
const requestedEndDateLocal = options.endDateLocal?.trim() ?? "";
|
|
346
601
|
const endDateLocal = requestedEndDateLocal || toLocalDateString(nowMs, tzOffsetMinutes);
|
|
347
602
|
parseDateLocal(endDateLocal);
|
|
603
|
+
const heatmapMetric = options.heatmapMetric ?? traceIndex.getConfig().activityHeatmap.metric;
|
|
604
|
+
const heatmapColor = options.heatmapColor ?? traceIndex.getConfig().activityHeatmap.color;
|
|
348
605
|
const requestedDayCount = validateInt(options.dayCount, "day_count");
|
|
349
606
|
const dayCount = clamp(requestedDayCount ?? DEFAULT_WEEK_DAY_COUNT, MIN_WEEK_DAY_COUNT, MAX_WEEK_DAY_COUNT);
|
|
350
607
|
const startDateLocal = shiftDateLocal(endDateLocal, -(dayCount - 1));
|
|
@@ -370,6 +627,8 @@ export function buildAgentActivityWeek(traceIndex, options = {}) {
|
|
|
370
627
|
slotMinutes,
|
|
371
628
|
hourStartLocal,
|
|
372
629
|
hourEndLocal,
|
|
630
|
+
heatmapMetric,
|
|
631
|
+
heatmapColor,
|
|
373
632
|
}, () => buildAgentActivityWeek(traceIndex, {
|
|
374
633
|
...uncachedOptions,
|
|
375
634
|
nowMs,
|
|
@@ -379,6 +638,8 @@ export function buildAgentActivityWeek(traceIndex, options = {}) {
|
|
|
379
638
|
slotMinutes,
|
|
380
639
|
hourStartLocal,
|
|
381
640
|
hourEndLocal,
|
|
641
|
+
heatmapMetric,
|
|
642
|
+
heatmapColor,
|
|
382
643
|
}));
|
|
383
644
|
}
|
|
384
645
|
const windowDurationMs = windowMinutes * 60_000;
|
|
@@ -394,18 +655,28 @@ export function buildAgentActivityWeek(traceIndex, options = {}) {
|
|
|
394
655
|
: nominalWindowEndMs;
|
|
395
656
|
const windowActivity = buildWindowActivity(traceIndex, windowStartMs, windowEndMs, slotMinutes, DEFAULT_BREAK_MINUTES, options.cache && options.cacheVersion !== undefined
|
|
396
657
|
? { cache: options.cache, cacheVersion: options.cacheVersion, nowMs }
|
|
397
|
-
: { nowMs });
|
|
658
|
+
: { nowMs }, heatmapMetric);
|
|
398
659
|
days.push({
|
|
399
660
|
dateLocal,
|
|
400
661
|
windowStartMs,
|
|
401
662
|
windowEndMs,
|
|
402
663
|
totalSessionsInWindow: windowActivity.totalSessionsInWindow,
|
|
664
|
+
heatmapValue: heatmapMetric === "sessions"
|
|
665
|
+
? windowActivity.totalSessionsInWindow
|
|
666
|
+
: sumHeatmapMetricForBins(windowActivity.bins, heatmapMetric),
|
|
667
|
+
heatmapValues: {
|
|
668
|
+
sessions: windowActivity.totalSessionsInWindow,
|
|
669
|
+
output_tokens: sumHeatmapMetricForBins(windowActivity.bins, "output_tokens"),
|
|
670
|
+
total_cost_usd: sumHeatmapMetricForBins(windowActivity.bins, "total_cost_usd"),
|
|
671
|
+
},
|
|
403
672
|
peakConcurrentSessions: windowActivity.peakConcurrentSessions,
|
|
404
673
|
peakConcurrentAtMs: windowActivity.peakConcurrentAtMs,
|
|
674
|
+
totalEventCount: totalEventCountForBins(windowActivity.bins),
|
|
405
675
|
bins: windowActivity.bins,
|
|
406
676
|
});
|
|
407
677
|
}
|
|
408
678
|
return {
|
|
679
|
+
presentation: buildHeatmapPresentation(heatmapMetric, heatmapColor),
|
|
409
680
|
tzOffsetMinutes,
|
|
410
681
|
dayCount,
|
|
411
682
|
slotMinutes,
|
|
@@ -414,6 +685,211 @@ export function buildAgentActivityWeek(traceIndex, options = {}) {
|
|
|
414
685
|
startDateLocal,
|
|
415
686
|
endDateLocal,
|
|
416
687
|
days,
|
|
688
|
+
usageSummary: buildWeeklyUsageSummaryFromDays(traceIndex, days),
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
export function buildAgentActivityYear(traceIndex, options = {}) {
|
|
692
|
+
const nowMs = typeof options.nowMs === "number" && Number.isFinite(options.nowMs) ? options.nowMs : Date.now();
|
|
693
|
+
const requestedTzOffset = validateInt(options.tzOffsetMinutes, "tz_offset_min");
|
|
694
|
+
const tzOffsetMinutes = clamp(requestedTzOffset ?? new Date(nowMs).getTimezoneOffset(), MIN_TZ_OFFSET_MINUTES, MAX_TZ_OFFSET_MINUTES);
|
|
695
|
+
const requestedEndDateLocal = options.endDateLocal?.trim() ?? "";
|
|
696
|
+
const endDateLocal = requestedEndDateLocal || toLocalDateString(nowMs, tzOffsetMinutes);
|
|
697
|
+
parseDateLocal(endDateLocal);
|
|
698
|
+
const heatmapMetric = options.heatmapMetric ?? traceIndex.getConfig().activityHeatmap.metric;
|
|
699
|
+
const heatmapColor = options.heatmapColor ?? traceIndex.getConfig().activityHeatmap.color;
|
|
700
|
+
const requestedDayCount = validateInt(options.dayCount, "day_count");
|
|
701
|
+
const maxDayCount = clamp(requestedDayCount ?? MAX_WEEK_DAY_COUNT, MIN_WEEK_DAY_COUNT, MAX_WEEK_DAY_COUNT);
|
|
702
|
+
const yearStartDateLocal = `${endDateLocal.slice(0, 4)}-01-01`;
|
|
703
|
+
const todayLocal = toLocalDateString(nowMs, tzOffsetMinutes);
|
|
704
|
+
const nominalRangeDayCount = Math.floor((windowStartMsForDateLocal(endDateLocal, 0) - windowStartMsForDateLocal(yearStartDateLocal, 0)) / DAY_MS) + 1;
|
|
705
|
+
const dayCount = clamp(nominalRangeDayCount, MIN_WEEK_DAY_COUNT, Math.min(MAX_WEEK_DAY_COUNT, maxDayCount));
|
|
706
|
+
const provisionalStartDateLocal = shiftDateLocal(endDateLocal, -(dayCount - 1));
|
|
707
|
+
const summaryById = new Map(traceIndex.getSummaries().map((summary) => [summary.id, summary]));
|
|
708
|
+
let earliestActiveDateLocal = null;
|
|
709
|
+
for (const summary of summaryById.values()) {
|
|
710
|
+
const activeAtMs = Math.max(summary.firstEventTs ?? 0, 1);
|
|
711
|
+
if (activeAtMs <= 0)
|
|
712
|
+
continue;
|
|
713
|
+
const activeDateLocal = toLocalDateString(activeAtMs, tzOffsetMinutes);
|
|
714
|
+
if (activeDateLocal < provisionalStartDateLocal || activeDateLocal > endDateLocal)
|
|
715
|
+
continue;
|
|
716
|
+
if (earliestActiveDateLocal === null || activeDateLocal < earliestActiveDateLocal) {
|
|
717
|
+
earliestActiveDateLocal = activeDateLocal;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
const startDateLocal = earliestActiveDateLocal ?? provisionalStartDateLocal;
|
|
721
|
+
const effectiveDayCount = Math.floor((windowStartMsForDateLocal(endDateLocal, 0) - windowStartMsForDateLocal(startDateLocal, 0)) / DAY_MS) + 1;
|
|
722
|
+
if (options.cache && options.cacheVersion !== undefined) {
|
|
723
|
+
const { cache: _cache, cacheVersion: _cacheVersion, ...uncachedOptions } = options;
|
|
724
|
+
return options.cache.getOrBuildYear(options.cacheVersion, nowMs, {
|
|
725
|
+
endDateLocal,
|
|
726
|
+
tzOffsetMinutes,
|
|
727
|
+
dayCount: effectiveDayCount,
|
|
728
|
+
heatmapMetric,
|
|
729
|
+
heatmapColor,
|
|
730
|
+
}, () => buildAgentActivityYear(traceIndex, {
|
|
731
|
+
...uncachedOptions,
|
|
732
|
+
nowMs,
|
|
733
|
+
endDateLocal,
|
|
734
|
+
tzOffsetMinutes,
|
|
735
|
+
dayCount: effectiveDayCount,
|
|
736
|
+
heatmapMetric,
|
|
737
|
+
heatmapColor,
|
|
738
|
+
}));
|
|
739
|
+
}
|
|
740
|
+
const windowDurationMs = computeWeekWindowMinutes(DEFAULT_YEAR_HOUR_START_LOCAL, DEFAULT_YEAR_HOUR_START_LOCAL) * 60_000;
|
|
741
|
+
const slotMs = DEFAULT_YEAR_SLOT_MINUTES * 60_000;
|
|
742
|
+
const slotCount = Math.max(1, Math.ceil(windowDurationMs / slotMs));
|
|
743
|
+
const dayStates = [];
|
|
744
|
+
for (let offset = 0; offset < effectiveDayCount; offset += 1) {
|
|
745
|
+
const dateLocal = shiftDateLocal(startDateLocal, offset);
|
|
746
|
+
const dayStartMs = windowStartMsForDateLocal(dateLocal, tzOffsetMinutes);
|
|
747
|
+
const windowStartMs = dayStartMs + DEFAULT_YEAR_HOUR_START_LOCAL * 60 * 60_000;
|
|
748
|
+
const nominalWindowEndMs = windowStartMs + windowDurationMs;
|
|
749
|
+
const windowEndMs = dateLocal === todayLocal ? Math.max(windowStartMs, Math.min(nominalWindowEndMs, nowMs)) : nominalWindowEndMs;
|
|
750
|
+
dayStates.push({
|
|
751
|
+
dateLocal,
|
|
752
|
+
windowStartMs,
|
|
753
|
+
windowEndMs,
|
|
754
|
+
totalSessionIds: new Set(),
|
|
755
|
+
heatmapValue: 0,
|
|
756
|
+
heatmapValues: createEmptyHeatmapMetricValues(),
|
|
757
|
+
totalEventCount: 0,
|
|
758
|
+
boundaryEvents: [],
|
|
759
|
+
agentSlotCounts: Array.from({ length: slotCount }, () => createEmptyAgentCounts()),
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
const overallStartMs = dayStates[0]?.windowStartMs ?? 0;
|
|
763
|
+
const overallEndMs = dayStates[dayStates.length - 1]?.windowEndMs ?? overallStartMs;
|
|
764
|
+
const usageAccumulator = createUsageAccumulator();
|
|
765
|
+
for (const summary of summaryById.values()) {
|
|
766
|
+
const span = resolveSessionSpan(summary);
|
|
767
|
+
if (span.endMs < overallStartMs || span.startMs >= overallEndMs)
|
|
768
|
+
continue;
|
|
769
|
+
const activityArtifacts = traceIndex.getSessionActivityArtifacts(summary.id);
|
|
770
|
+
let summaryContributed = false;
|
|
771
|
+
for (const timestampMs of activityArtifacts.eventTimestamps) {
|
|
772
|
+
if (timestampMs < overallStartMs || timestampMs >= overallEndMs)
|
|
773
|
+
continue;
|
|
774
|
+
const dayIndex = Math.floor((timestampMs - overallStartMs) / DAY_MS);
|
|
775
|
+
const dayState = dayStates[dayIndex];
|
|
776
|
+
if (!dayState)
|
|
777
|
+
continue;
|
|
778
|
+
if (timestampMs >= dayState.windowEndMs)
|
|
779
|
+
continue;
|
|
780
|
+
dayState.totalEventCount += 1;
|
|
781
|
+
}
|
|
782
|
+
for (const segment of activityArtifacts.activeSegments) {
|
|
783
|
+
if (segment.endMs < overallStartMs || segment.startMs >= overallEndMs)
|
|
784
|
+
continue;
|
|
785
|
+
const clampedStartMs = Math.max(segment.startMs, overallStartMs);
|
|
786
|
+
const clampedEndExclusiveMs = Math.min(segment.endMs + 1, overallEndMs);
|
|
787
|
+
if (clampedEndExclusiveMs <= clampedStartMs)
|
|
788
|
+
continue;
|
|
789
|
+
summaryContributed = true;
|
|
790
|
+
const startDayIndex = Math.max(0, Math.floor((clampedStartMs - overallStartMs) / DAY_MS));
|
|
791
|
+
const endDayIndex = Math.max(0, Math.floor((clampedEndExclusiveMs - 1 - overallStartMs) / DAY_MS));
|
|
792
|
+
for (let dayIndex = startDayIndex; dayIndex <= endDayIndex; dayIndex += 1) {
|
|
793
|
+
const dayState = dayStates[dayIndex];
|
|
794
|
+
if (!dayState)
|
|
795
|
+
continue;
|
|
796
|
+
const overlapStartMs = Math.max(clampedStartMs, dayState.windowStartMs);
|
|
797
|
+
const overlapEndExclusiveMs = Math.min(clampedEndExclusiveMs, dayState.windowEndMs);
|
|
798
|
+
if (overlapEndExclusiveMs <= overlapStartMs)
|
|
799
|
+
continue;
|
|
800
|
+
dayState.totalSessionIds.add(summary.id);
|
|
801
|
+
dayState.boundaryEvents.push({ atMs: overlapStartMs, delta: 1 });
|
|
802
|
+
dayState.boundaryEvents.push({ atMs: overlapEndExclusiveMs, delta: -1 });
|
|
803
|
+
const startSlotIndex = computeBinIndex(dayState.windowStartMs, slotMs, slotCount, overlapStartMs);
|
|
804
|
+
const endSlotIndex = computeBinIndex(dayState.windowStartMs, slotMs, slotCount, Math.max(overlapStartMs, overlapEndExclusiveMs - 1));
|
|
805
|
+
for (let slotIndex = startSlotIndex; slotIndex <= endSlotIndex; slotIndex += 1) {
|
|
806
|
+
dayState.agentSlotCounts[slotIndex][summary.agent] += 1;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
if (!summaryContributed)
|
|
811
|
+
continue;
|
|
812
|
+
usageAccumulator.totalUniqueSessionIds.add(summary.id);
|
|
813
|
+
usageAccumulator.uniqueSessionIdsByAgent.get(summary.agent)?.add(summary.id);
|
|
814
|
+
}
|
|
815
|
+
for (const summary of summaryById.values()) {
|
|
816
|
+
const row = usageAccumulator.usageByAgent.get(summary.agent);
|
|
817
|
+
if (!row)
|
|
818
|
+
continue;
|
|
819
|
+
const usageArtifacts = traceIndex.getSessionUsageArtifacts(summary.id);
|
|
820
|
+
for (const point of usageArtifacts.usagePoints) {
|
|
821
|
+
if (point.timestampMs < overallStartMs || point.timestampMs >= overallEndMs)
|
|
822
|
+
continue;
|
|
823
|
+
const dayIndex = Math.floor((point.timestampMs - overallStartMs) / DAY_MS);
|
|
824
|
+
const dayState = dayStates[dayIndex];
|
|
825
|
+
if (!dayState)
|
|
826
|
+
continue;
|
|
827
|
+
if (point.timestampMs < dayState.windowStartMs || point.timestampMs >= dayState.windowEndMs)
|
|
828
|
+
continue;
|
|
829
|
+
dayState.heatmapValues.output_tokens += sanitizeTokenValue(point.outputTokens);
|
|
830
|
+
dayState.heatmapValues.total_cost_usd += sanitizeCostValue(point.costUsd);
|
|
831
|
+
applyUsagePointToRow(row, point);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
const days = dayStates.map((dayState) => {
|
|
835
|
+
let peakConcurrentSessions = 0;
|
|
836
|
+
let peakConcurrentAtMs = null;
|
|
837
|
+
let concurrentSessions = 0;
|
|
838
|
+
const sortedBoundaryEvents = [...dayState.boundaryEvents].sort((left, right) => left.atMs - right.atMs || left.delta - right.delta);
|
|
839
|
+
for (const boundaryEvent of sortedBoundaryEvents) {
|
|
840
|
+
concurrentSessions += boundaryEvent.delta;
|
|
841
|
+
if (concurrentSessions > peakConcurrentSessions) {
|
|
842
|
+
peakConcurrentSessions = concurrentSessions;
|
|
843
|
+
peakConcurrentAtMs = boundaryEvent.atMs;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
usageAccumulator.peakAllAgentConcurrency = Math.max(usageAccumulator.peakAllAgentConcurrency, peakConcurrentSessions);
|
|
847
|
+
for (const agent of AGENT_KIND_KEYS) {
|
|
848
|
+
const row = usageAccumulator.usageByAgent.get(agent);
|
|
849
|
+
if (!row)
|
|
850
|
+
continue;
|
|
851
|
+
let activeToday = false;
|
|
852
|
+
for (const slotCounts of dayState.agentSlotCounts) {
|
|
853
|
+
const concurrentAgentSessions = slotCounts[agent] ?? 0;
|
|
854
|
+
if (concurrentAgentSessions <= 0)
|
|
855
|
+
continue;
|
|
856
|
+
activeToday = true;
|
|
857
|
+
row.sessionHours += concurrentAgentSessions * (slotMs / 3_600_000);
|
|
858
|
+
row.activeSlots += 1;
|
|
859
|
+
row.peakConcurrentSessions = Math.max(row.peakConcurrentSessions, concurrentAgentSessions);
|
|
860
|
+
}
|
|
861
|
+
if (activeToday) {
|
|
862
|
+
row.activeDays += 1;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
return {
|
|
866
|
+
dateLocal: dayState.dateLocal,
|
|
867
|
+
windowStartMs: dayState.windowStartMs,
|
|
868
|
+
windowEndMs: dayState.windowEndMs,
|
|
869
|
+
totalSessionsInWindow: dayState.totalSessionIds.size,
|
|
870
|
+
heatmapValue: heatmapMetric === "sessions"
|
|
871
|
+
? dayState.totalSessionIds.size
|
|
872
|
+
: heatmapMetric === "output_tokens"
|
|
873
|
+
? dayState.heatmapValues.output_tokens
|
|
874
|
+
: dayState.heatmapValues.total_cost_usd,
|
|
875
|
+
heatmapValues: {
|
|
876
|
+
sessions: dayState.totalSessionIds.size,
|
|
877
|
+
output_tokens: dayState.heatmapValues.output_tokens,
|
|
878
|
+
total_cost_usd: dayState.heatmapValues.total_cost_usd,
|
|
879
|
+
},
|
|
880
|
+
peakConcurrentSessions,
|
|
881
|
+
peakConcurrentAtMs,
|
|
882
|
+
totalEventCount: dayState.totalEventCount,
|
|
883
|
+
};
|
|
884
|
+
});
|
|
885
|
+
return {
|
|
886
|
+
presentation: buildHeatmapPresentation(heatmapMetric, heatmapColor),
|
|
887
|
+
tzOffsetMinutes,
|
|
888
|
+
dayCount: effectiveDayCount,
|
|
889
|
+
startDateLocal,
|
|
890
|
+
endDateLocal,
|
|
891
|
+
days,
|
|
892
|
+
usageSummary: finalizeUsageSummary(usageAccumulator),
|
|
417
893
|
};
|
|
418
894
|
}
|
|
419
895
|
//# sourceMappingURL=activity.js.map
|