@roberttlange/agentlens 0.2.2 → 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 (48) 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 +120 -0
  6. package/node_modules/@agentlens/core/dist/__tests__/config.test.js +67 -2
  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 +590 -2
  9. package/node_modules/@agentlens/core/dist/__tests__/index.test.js.map +1 -1
  10. package/node_modules/@agentlens/core/dist/config.js +95 -5
  11. package/node_modules/@agentlens/core/dist/config.js.map +1 -1
  12. package/node_modules/@agentlens/core/dist/generatedPricing.d.ts +3 -0
  13. package/node_modules/@agentlens/core/dist/generatedPricing.js +131 -0
  14. package/node_modules/@agentlens/core/dist/generatedPricing.js.map +1 -0
  15. package/node_modules/@agentlens/core/dist/metrics.d.ts +13 -0
  16. package/node_modules/@agentlens/core/dist/metrics.js +227 -54
  17. package/node_modules/@agentlens/core/dist/metrics.js.map +1 -1
  18. package/node_modules/@agentlens/core/dist/pricing.d.ts +15 -0
  19. package/node_modules/@agentlens/core/dist/pricing.js +133 -0
  20. package/node_modules/@agentlens/core/dist/pricing.js.map +1 -0
  21. package/node_modules/@agentlens/core/dist/pricing.test.d.ts +1 -0
  22. package/node_modules/@agentlens/core/dist/pricing.test.js +109 -0
  23. package/node_modules/@agentlens/core/dist/pricing.test.js.map +1 -0
  24. package/node_modules/@agentlens/core/dist/sourceProfiles.js +7 -67
  25. package/node_modules/@agentlens/core/dist/sourceProfiles.js.map +1 -1
  26. package/node_modules/@agentlens/core/dist/traceIndex.d.ts +34 -1
  27. package/node_modules/@agentlens/core/dist/traceIndex.js +374 -15
  28. package/node_modules/@agentlens/core/dist/traceIndex.js.map +1 -1
  29. package/node_modules/@agentlens/server/dist/activity-cache.d.ts +32 -0
  30. package/node_modules/@agentlens/server/dist/activity-cache.js +63 -0
  31. package/node_modules/@agentlens/server/dist/activity-cache.js.map +1 -0
  32. package/node_modules/@agentlens/server/dist/activity-cache.test.d.ts +1 -0
  33. package/node_modules/@agentlens/server/dist/activity-cache.test.js +170 -0
  34. package/node_modules/@agentlens/server/dist/activity-cache.test.js.map +1 -0
  35. package/node_modules/@agentlens/server/dist/activity.d.ts +31 -1
  36. package/node_modules/@agentlens/server/dist/activity.js +532 -34
  37. package/node_modules/@agentlens/server/dist/activity.js.map +1 -1
  38. package/node_modules/@agentlens/server/dist/app.d.ts +4 -2
  39. package/node_modules/@agentlens/server/dist/app.js +248 -5
  40. package/node_modules/@agentlens/server/dist/app.js.map +1 -1
  41. package/node_modules/@agentlens/server/dist/app.test.js +670 -9
  42. package/node_modules/@agentlens/server/dist/app.test.js.map +1 -1
  43. package/node_modules/@agentlens/server/dist/web/assets/index-CTFOBaBt.css +1 -0
  44. package/node_modules/@agentlens/server/dist/web/assets/index-CVf00w06.js +52 -0
  45. package/node_modules/@agentlens/server/dist/web/index.html +2 -2
  46. package/package.json +1 -1
  47. package/node_modules/@agentlens/server/dist/web/assets/index-Ci8okH8M.js +0 -52
  48. package/node_modules/@agentlens/server/dist/web/assets/index-Cj3kmsFf.css +0 -1
@@ -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;
@@ -113,35 +293,6 @@ function resolveSessionSpan(summary) {
113
293
  const endMs = Math.max(baseStart, baseEnd);
114
294
  return { startMs, endMs };
115
295
  }
116
- function collectTimestampMs(events) {
117
- const timestamps = [];
118
- for (const event of events) {
119
- const timestampMs = event.timestampMs;
120
- if (timestampMs === null || !Number.isFinite(timestampMs) || timestampMs <= 0)
121
- continue;
122
- timestamps.push(timestampMs);
123
- }
124
- timestamps.sort((left, right) => left - right);
125
- return timestamps;
126
- }
127
- function buildActiveSegmentsFromEventTimestamps(eventTimestamps) {
128
- if (eventTimestamps.length === 0)
129
- return [];
130
- const segments = [];
131
- let segmentStartMs = eventTimestamps[0] ?? 0;
132
- let previousTsMs = segmentStartMs;
133
- for (let index = 1; index < eventTimestamps.length; index += 1) {
134
- const nextTsMs = eventTimestamps[index] ?? previousTsMs;
135
- const gapMs = Math.max(0, nextTsMs - previousTsMs);
136
- if (gapMs > ACTIVE_IDLE_GAP_MS) {
137
- segments.push({ startMs: segmentStartMs, endMs: previousTsMs });
138
- segmentStartMs = nextTsMs;
139
- }
140
- previousTsMs = nextTsMs;
141
- }
142
- segments.push({ startMs: segmentStartMs, endMs: previousTsMs });
143
- return segments;
144
- }
145
296
  function pickDominantAgent(counts) {
146
297
  let dominant = "none";
147
298
  let dominantCount = 0;
@@ -208,7 +359,7 @@ function applyBreakMarkers(bins, breakMinutes) {
208
359
  }
209
360
  markRunIfBreak(runStart, bins.length);
210
361
  }
211
- function buildWindowActivity(traceIndex, windowStartMs, windowEndMs, binMinutes, breakMinutes) {
362
+ function computeWindowActivity(traceIndex, windowStartMs, windowEndMs, binMinutes, breakMinutes, heatmapMetric) {
212
363
  const binMs = binMinutes * 60_000;
213
364
  const totalWindowMs = Math.max(0, windowEndMs - windowStartMs);
214
365
  const binCount = totalWindowMs <= 0 ? 0 : Math.ceil(totalWindowMs / binMs);
@@ -220,6 +371,8 @@ function buildWindowActivity(traceIndex, windowStartMs, windowEndMs, binMinutes,
220
371
  startMs,
221
372
  endMs,
222
373
  activeSessionCount: 0,
374
+ heatmapValue: 0,
375
+ heatmapValues: createEmptyHeatmapMetricValues(),
223
376
  activeTraceIds: [],
224
377
  primaryTraceId: "",
225
378
  activeByAgent: createEmptyAgentCounts(),
@@ -244,8 +397,8 @@ function buildWindowActivity(traceIndex, windowStartMs, windowEndMs, binMinutes,
244
397
  if (binCount > 0) {
245
398
  for (const summary of inWindowSummaries) {
246
399
  const detail = traceIndex.getSessionDetail(summary.id);
247
- const eventTimestamps = collectTimestampMs(detail.events);
248
- const activeSegments = buildActiveSegmentsFromEventTimestamps(eventTimestamps);
400
+ const activityArtifacts = traceIndex.getSessionActivityArtifacts(summary.id);
401
+ const activeSegments = activityArtifacts.activeSegments;
249
402
  for (const segment of activeSegments) {
250
403
  if (segment.endMs < windowStartMs || segment.startMs >= windowEndMs)
251
404
  continue;
@@ -263,6 +416,7 @@ function buildWindowActivity(traceIndex, windowStartMs, windowEndMs, binMinutes,
263
416
  bin.activeTraceIds.push(summary.id);
264
417
  bin.activeSessionCount += 1;
265
418
  bin.activeByAgent[summary.agent] += 1;
419
+ bin.heatmapValues ??= createEmptyHeatmapMetricValues();
266
420
  contributingSessionIds.add(summary.id);
267
421
  }
268
422
  }
@@ -278,6 +432,18 @@ function buildWindowActivity(traceIndex, windowStartMs, windowEndMs, binMinutes,
278
432
  bin.eventCount += 1;
279
433
  bin.eventKindCounts[event.eventKind] += 1;
280
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
+ }
281
447
  }
282
448
  }
283
449
  let peakConcurrentSessions = 0;
@@ -293,6 +459,17 @@ function buildWindowActivity(traceIndex, windowStartMs, windowEndMs, binMinutes,
293
459
  });
294
460
  bin.primaryTraceId = bin.activeTraceIds[0] ?? "";
295
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;
296
473
  bin.dominantAgent = pickDominantAgent(bin.activeByAgent);
297
474
  bin.dominantEventKind = pickDominantEventKind(bin.eventKindCounts);
298
475
  if (bin.activeSessionCount > peakConcurrentSessions) {
@@ -308,6 +485,65 @@ function buildWindowActivity(traceIndex, windowStartMs, windowEndMs, binMinutes,
308
485
  peakConcurrentAtMs,
309
486
  };
310
487
  }
488
+ function buildWindowActivity(traceIndex, windowStartMs, windowEndMs, binMinutes, breakMinutes, options, heatmapMetric) {
489
+ if (!options.cache || options.cacheVersion === undefined) {
490
+ return computeWindowActivity(traceIndex, windowStartMs, windowEndMs, binMinutes, breakMinutes, heatmapMetric);
491
+ }
492
+ return options.cache.getOrBuildWindow(options.cacheVersion, options.nowMs, {
493
+ windowStartMs,
494
+ windowEndMs,
495
+ binMinutes,
496
+ 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
+ };
546
+ }
311
547
  export function buildAgentActivityDay(traceIndex, options = {}) {
312
548
  const nowMs = typeof options.nowMs === "number" && Number.isFinite(options.nowMs) ? options.nowMs : Date.now();
313
549
  const requestedTzOffset = validateInt(options.tzOffsetMinutes, "tz_offset_min");
@@ -319,12 +555,30 @@ export function buildAgentActivityDay(traceIndex, options = {}) {
319
555
  const requestedBreakMinutes = validateInt(options.breakMinutes, "break_min");
320
556
  const binMinutes = clamp(requestedBinMinutes ?? DEFAULT_BIN_MINUTES, MIN_BIN_MINUTES, MAX_BIN_MINUTES);
321
557
  const breakMinutes = clamp(requestedBreakMinutes ?? DEFAULT_BREAK_MINUTES, MIN_BREAK_MINUTES, MAX_BREAK_MINUTES);
558
+ if (options.cache && options.cacheVersion !== undefined) {
559
+ const { cache: _cache, cacheVersion: _cacheVersion, ...uncachedOptions } = options;
560
+ return options.cache.getOrBuildDay(options.cacheVersion, nowMs, {
561
+ dateLocal,
562
+ tzOffsetMinutes,
563
+ binMinutes,
564
+ breakMinutes,
565
+ }, () => buildAgentActivityDay(traceIndex, {
566
+ ...uncachedOptions,
567
+ nowMs,
568
+ dateLocal,
569
+ tzOffsetMinutes,
570
+ binMinutes,
571
+ breakMinutes,
572
+ }));
573
+ }
322
574
  const dayStartMs = windowStartMsForDateLocal(dateLocal, tzOffsetMinutes);
323
575
  const windowStartMs = dayStartMs + DEFAULT_DAY_HOUR_START_LOCAL * 60 * 60_000;
324
576
  const windowEndOfDayMs = windowStartMs + DAY_MS;
325
577
  const todayLocal = toLocalDateString(nowMs, tzOffsetMinutes);
326
578
  const windowEndMs = dateLocal === todayLocal ? Math.max(windowStartMs, Math.min(windowEndOfDayMs, nowMs)) : windowEndOfDayMs;
327
- const windowActivity = buildWindowActivity(traceIndex, windowStartMs, windowEndMs, binMinutes, breakMinutes);
579
+ const windowActivity = buildWindowActivity(traceIndex, windowStartMs, windowEndMs, binMinutes, breakMinutes, options.cache && options.cacheVersion !== undefined
580
+ ? { cache: options.cache, cacheVersion: options.cacheVersion, 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));
@@ -361,6 +618,30 @@ export function buildAgentActivityWeek(traceIndex, options = {}) {
361
618
  if (slotMinutes > windowMinutes) {
362
619
  throw new Error("slot_min too large for hour window");
363
620
  }
621
+ if (options.cache && options.cacheVersion !== undefined) {
622
+ const { cache: _cache, cacheVersion: _cacheVersion, ...uncachedOptions } = options;
623
+ return options.cache.getOrBuildWeek(options.cacheVersion, nowMs, {
624
+ endDateLocal,
625
+ tzOffsetMinutes,
626
+ dayCount,
627
+ slotMinutes,
628
+ hourStartLocal,
629
+ hourEndLocal,
630
+ heatmapMetric,
631
+ heatmapColor,
632
+ }, () => buildAgentActivityWeek(traceIndex, {
633
+ ...uncachedOptions,
634
+ nowMs,
635
+ endDateLocal,
636
+ tzOffsetMinutes,
637
+ dayCount,
638
+ slotMinutes,
639
+ hourStartLocal,
640
+ hourEndLocal,
641
+ heatmapMetric,
642
+ heatmapColor,
643
+ }));
644
+ }
364
645
  const windowDurationMs = windowMinutes * 60_000;
365
646
  const days = [];
366
647
  const todayLocal = toLocalDateString(nowMs, tzOffsetMinutes);
@@ -372,18 +653,30 @@ export function buildAgentActivityWeek(traceIndex, options = {}) {
372
653
  const windowEndMs = dateLocal === todayLocal
373
654
  ? Math.max(windowStartMs, Math.min(nominalWindowEndMs, nowMs))
374
655
  : nominalWindowEndMs;
375
- const windowActivity = buildWindowActivity(traceIndex, windowStartMs, windowEndMs, slotMinutes, DEFAULT_BREAK_MINUTES);
656
+ const windowActivity = buildWindowActivity(traceIndex, windowStartMs, windowEndMs, slotMinutes, DEFAULT_BREAK_MINUTES, options.cache && options.cacheVersion !== undefined
657
+ ? { cache: options.cache, cacheVersion: options.cacheVersion, nowMs }
658
+ : { nowMs }, heatmapMetric);
376
659
  days.push({
377
660
  dateLocal,
378
661
  windowStartMs,
379
662
  windowEndMs,
380
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
+ },
381
672
  peakConcurrentSessions: windowActivity.peakConcurrentSessions,
382
673
  peakConcurrentAtMs: windowActivity.peakConcurrentAtMs,
674
+ totalEventCount: totalEventCountForBins(windowActivity.bins),
383
675
  bins: windowActivity.bins,
384
676
  });
385
677
  }
386
678
  return {
679
+ presentation: buildHeatmapPresentation(heatmapMetric, heatmapColor),
387
680
  tzOffsetMinutes,
388
681
  dayCount,
389
682
  slotMinutes,
@@ -392,6 +685,211 @@ export function buildAgentActivityWeek(traceIndex, options = {}) {
392
685
  startDateLocal,
393
686
  endDateLocal,
394
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),
395
893
  };
396
894
  }
397
895
  //# sourceMappingURL=activity.js.map