@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.
Files changed (38) hide show
  1. package/dist/browser.js +154 -20
  2. package/dist/browser.js.map +1 -1
  3. package/dist/main.test.js +138 -1
  4. package/dist/main.test.js.map +1 -1
  5. package/node_modules/@agentlens/contracts/dist/index.d.ts +109 -0
  6. package/node_modules/@agentlens/core/dist/__tests__/config.test.js +18 -0
  7. package/node_modules/@agentlens/core/dist/__tests__/config.test.js.map +1 -1
  8. package/node_modules/@agentlens/core/dist/__tests__/index.test.js +370 -2
  9. package/node_modules/@agentlens/core/dist/__tests__/index.test.js.map +1 -1
  10. package/node_modules/@agentlens/core/dist/config.js +33 -0
  11. package/node_modules/@agentlens/core/dist/config.js.map +1 -1
  12. package/node_modules/@agentlens/core/dist/metrics.d.ts +13 -0
  13. package/node_modules/@agentlens/core/dist/metrics.js +98 -2
  14. package/node_modules/@agentlens/core/dist/metrics.js.map +1 -1
  15. package/node_modules/@agentlens/core/dist/sourceProfiles.js +4 -0
  16. package/node_modules/@agentlens/core/dist/sourceProfiles.js.map +1 -1
  17. package/node_modules/@agentlens/core/dist/traceIndex.d.ts +22 -1
  18. package/node_modules/@agentlens/core/dist/traceIndex.js +322 -21
  19. package/node_modules/@agentlens/core/dist/traceIndex.js.map +1 -1
  20. package/node_modules/@agentlens/server/dist/activity-cache.d.ts +6 -3
  21. package/node_modules/@agentlens/server/dist/activity-cache.js +6 -0
  22. package/node_modules/@agentlens/server/dist/activity-cache.js.map +1 -1
  23. package/node_modules/@agentlens/server/dist/activity-cache.test.js +86 -0
  24. package/node_modules/@agentlens/server/dist/activity-cache.test.js.map +1 -1
  25. package/node_modules/@agentlens/server/dist/activity.d.ts +20 -1
  26. package/node_modules/@agentlens/server/dist/activity.js +482 -6
  27. package/node_modules/@agentlens/server/dist/activity.js.map +1 -1
  28. package/node_modules/@agentlens/server/dist/app.d.ts +4 -2
  29. package/node_modules/@agentlens/server/dist/app.js +242 -5
  30. package/node_modules/@agentlens/server/dist/app.js.map +1 -1
  31. package/node_modules/@agentlens/server/dist/app.test.js +669 -8
  32. package/node_modules/@agentlens/server/dist/app.test.js.map +1 -1
  33. package/node_modules/@agentlens/server/dist/web/assets/index-CTFOBaBt.css +1 -0
  34. package/node_modules/@agentlens/server/dist/web/assets/index-CVf00w06.js +52 -0
  35. package/node_modules/@agentlens/server/dist/web/index.html +2 -2
  36. package/package.json +1 -1
  37. package/node_modules/@agentlens/server/dist/web/assets/index-DjwZvHl6.css +0 -1
  38. 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
- }, () => computeWindowActivity(traceIndex, windowStartMs, windowEndMs, binMinutes, breakMinutes));
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