apteva 0.4.48 → 0.4.51

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.
@@ -49,9 +49,13 @@ interface DailyUsage {
49
49
  date: string;
50
50
  input_tokens: number;
51
51
  output_tokens: number;
52
+ cache_creation_tokens: number;
53
+ cache_read_tokens: number;
54
+ reasoning_tokens: number;
52
55
  llm_calls: number;
53
56
  tool_calls: number;
54
57
  errors: number;
58
+ cost: number;
55
59
  }
56
60
 
57
61
  // Helper to extract stats from a single event
@@ -64,6 +68,7 @@ function extractEventStats(event: TelemetryEvent): {
64
68
  cache_creation_tokens: number;
65
69
  cache_read_tokens: number;
66
70
  reasoning_tokens: number;
71
+ cost: number;
67
72
  } {
68
73
  const isLlm = event.category === "LLM";
69
74
  const isTool = event.category === "TOOL";
@@ -73,6 +78,7 @@ function extractEventStats(event: TelemetryEvent): {
73
78
  const cacheCreationTokens = (event.data?.cache_creation_tokens as number) || 0;
74
79
  const cacheReadTokens = (event.data?.cache_read_tokens as number) || 0;
75
80
  const reasoningTokens = (event.data?.reasoning_tokens as number) || 0;
81
+ const cost = (event.cost as number) || 0;
76
82
 
77
83
  return {
78
84
  llm_calls: isLlm ? 1 : 0,
@@ -83,6 +89,7 @@ function extractEventStats(event: TelemetryEvent): {
83
89
  cache_creation_tokens: cacheCreationTokens,
84
90
  cache_read_tokens: cacheReadTokens,
85
91
  reasoning_tokens: reasoningTokens,
92
+ cost,
86
93
  };
87
94
  }
88
95
 
@@ -273,6 +280,7 @@ export function TelemetryPage() {
273
280
  let deltaCacheCreationTokens = 0;
274
281
  let deltaCacheReadTokens = 0;
275
282
  let deltaReasoningTokens = 0;
283
+ let deltaCost = 0;
276
284
 
277
285
  for (const event of realtimeEvents) {
278
286
  if (!countedEventIdsRef.current.has(event.id)) {
@@ -286,6 +294,7 @@ export function TelemetryPage() {
286
294
  deltaCacheCreationTokens += eventStats.cache_creation_tokens;
287
295
  deltaCacheReadTokens += eventStats.cache_read_tokens;
288
296
  deltaReasoningTokens += eventStats.reasoning_tokens;
297
+ deltaCost += eventStats.cost;
289
298
  }
290
299
  }
291
300
 
@@ -299,7 +308,7 @@ export function TelemetryPage() {
299
308
  total_cache_creation_tokens: (fetchedStats.total_cache_creation_tokens || 0) + deltaCacheCreationTokens,
300
309
  total_cache_read_tokens: (fetchedStats.total_cache_read_tokens || 0) + deltaCacheReadTokens,
301
310
  total_reasoning_tokens: (fetchedStats.total_reasoning_tokens || 0) + deltaReasoningTokens,
302
- total_cost: fetchedStats.total_cost || 0,
311
+ total_cost: (fetchedStats.total_cost || 0) + deltaCost,
303
312
  };
304
313
  }, [fetchedStats, realtimeEvents]);
305
314
 
@@ -325,6 +334,7 @@ export function TelemetryPage() {
325
334
  existing.cache_creation_tokens += eventStats.cache_creation_tokens;
326
335
  existing.cache_read_tokens += eventStats.cache_read_tokens;
327
336
  existing.reasoning_tokens += eventStats.reasoning_tokens;
337
+ existing.cost += eventStats.cost;
328
338
  } else {
329
339
  usageMap.set(event.agent_id, {
330
340
  agent_id: event.agent_id,
@@ -336,7 +346,7 @@ export function TelemetryPage() {
336
346
  cache_creation_tokens: eventStats.cache_creation_tokens,
337
347
  cache_read_tokens: eventStats.cache_read_tokens,
338
348
  reasoning_tokens: eventStats.reasoning_tokens,
339
- cost: 0,
349
+ cost: eventStats.cost,
340
350
  });
341
351
  }
342
352
  }
@@ -377,6 +387,43 @@ export function TelemetryPage() {
377
387
  return sorted;
378
388
  }, [projectUsage, projectSortKey, projectSortDir, projects]);
379
389
 
390
+ // Compute real-time chart data by merging fetched daily usage with SSE deltas
391
+ const chartData = useMemo(() => {
392
+ const buckets = new Map<string, DailyUsage>();
393
+ const useDaily = dailyUsage.length > 1;
394
+
395
+ // Seed with fetched daily data
396
+ for (const d of dailyUsage) {
397
+ buckets.set(d.date, { ...d });
398
+ }
399
+
400
+ // Add deltas from real-time events not already counted
401
+ for (const event of realtimeEvents) {
402
+ if (!countedEventIdsRef.current.has(event.id)) {
403
+ const ts = new Date(event.timestamp);
404
+ const key = useDaily
405
+ ? `${ts.getFullYear()}-${String(ts.getMonth() + 1).padStart(2, "0")}-${String(ts.getDate()).padStart(2, "0")}`
406
+ : `${ts.getFullYear()}-${String(ts.getMonth() + 1).padStart(2, "0")}-${String(ts.getDate()).padStart(2, "0")} ${String(ts.getHours()).padStart(2, "0")}:00`;
407
+ if (!buckets.has(key)) {
408
+ buckets.set(key, { date: key, llm_calls: 0, tool_calls: 0, errors: 0, input_tokens: 0, output_tokens: 0, cache_creation_tokens: 0, cache_read_tokens: 0, reasoning_tokens: 0, cost: 0 });
409
+ }
410
+ const b = buckets.get(key)!;
411
+ const s = extractEventStats(event);
412
+ b.llm_calls += s.llm_calls;
413
+ b.tool_calls += s.tool_calls;
414
+ b.errors += s.errors;
415
+ b.input_tokens += s.input_tokens;
416
+ b.output_tokens += s.output_tokens;
417
+ b.cache_creation_tokens += s.cache_creation_tokens;
418
+ b.cache_read_tokens += s.cache_read_tokens;
419
+ b.reasoning_tokens += s.reasoning_tokens;
420
+ b.cost += s.cost;
421
+ }
422
+ }
423
+
424
+ return Array.from(buckets.values()).sort((a, b) => a.date.localeCompare(b.date));
425
+ }, [dailyUsage, realtimeEvents]);
426
+
380
427
  const getProjectName = (projectId: string | null) => {
381
428
  if (!projectId) return "Unassigned";
382
429
  const project = projects.find(p => p.id === projectId);
@@ -530,14 +577,14 @@ export function TelemetryPage() {
530
577
  )}
531
578
  <h1 className="text-2xl font-semibold">
532
579
  {currentProjectId === null
533
- ? "Telemetry"
580
+ ? "Analytics"
534
581
  : currentProjectId === "unassigned"
535
- ? "Telemetry - Unassigned"
536
- : `Telemetry - ${currentProject?.name || ""}`}
582
+ ? "Analytics - Unassigned"
583
+ : `Analytics - ${currentProject?.name || ""}`}
537
584
  </h1>
538
585
  </div>
539
586
  <p className="text-[var(--color-text-muted)]">
540
- Monitor agent activity, token usage, and errors.
587
+ Monitor agent activity, usage, costs, and performance.
541
588
  </p>
542
589
  </div>
543
590
 
@@ -591,27 +638,7 @@ export function TelemetryPage() {
591
638
 
592
639
  {/* Charts */}
593
640
  {(() => {
594
- // Use daily data if we have multiple days, otherwise aggregate events by hour
595
641
  const useDaily = dailyUsage.length > 1;
596
- const chartData = useDaily ? dailyUsage : (() => {
597
- // Aggregate all visible events by hour
598
- const buckets = new Map<string, { date: string; llm_calls: number; tool_calls: number; errors: number; input_tokens: number; output_tokens: number }>();
599
- for (const event of allEvents) {
600
- const d = new Date(event.timestamp);
601
- const key = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")} ${String(d.getHours()).padStart(2, "0")}:00`;
602
- if (!buckets.has(key)) {
603
- buckets.set(key, { date: key, llm_calls: 0, tool_calls: 0, errors: 0, input_tokens: 0, output_tokens: 0 });
604
- }
605
- const b = buckets.get(key)!;
606
- const s = extractEventStats(event);
607
- b.llm_calls += s.llm_calls;
608
- b.tool_calls += s.tool_calls;
609
- b.errors += s.errors;
610
- b.input_tokens += s.input_tokens;
611
- b.output_tokens += s.output_tokens;
612
- }
613
- return Array.from(buckets.values()).sort((a, b) => a.date.localeCompare(b.date));
614
- })();
615
642
  const chartLabel = useDaily ? "Daily" : "Hourly";
616
643
 
617
644
  if (chartData.length === 0) return null;
@@ -687,67 +714,91 @@ export function TelemetryPage() {
687
714
  </ResponsiveContainer>
688
715
  </div>
689
716
 
690
- {/* Token Usage Chart */}
691
- <div className="bg-[var(--color-surface)] card p-4">
692
- <h3 className="text-sm font-medium text-[var(--color-text-secondary)] mb-4">{chartLabel} Token Usage</h3>
693
- <ResponsiveContainer width="100%" height={200}>
694
- <BarChart data={chartData}>
695
- <CartesianGrid strokeDasharray="3 3" stroke="var(--color-border)" />
696
- <XAxis
697
- dataKey="date"
698
- stroke="var(--color-border-light)"
699
- tick={{ fill: "var(--color-text-muted)", fontSize: 11 }}
700
- tickFormatter={(v) => {
701
- if (!useDaily && v.includes(" ")) {
702
- return v.split(" ")[1];
703
- }
704
- const d = new Date(v + "T00:00:00");
705
- return `${d.getMonth() + 1}/${d.getDate()}`;
706
- }}
707
- />
708
- <YAxis
709
- stroke="var(--color-border-light)"
710
- tick={{ fill: "var(--color-text-muted)", fontSize: 11 }}
711
- tickFormatter={(v) => {
712
- if (v >= 1000000) return `${(v / 1000000).toFixed(1)}M`;
713
- if (v >= 1000) return `${(v / 1000).toFixed(0)}K`;
714
- return v;
715
- }}
716
- />
717
- <Tooltip
718
- contentStyle={{
719
- backgroundColor: "var(--color-surface)",
720
- border: "1px solid var(--color-border-light)",
721
- borderRadius: "8px",
722
- fontSize: 12,
723
- }}
724
- labelStyle={{ color: "var(--color-text-secondary)" }}
725
- cursor={{ fill: "rgba(255,255,255,0.03)" }}
726
- labelFormatter={(v) => useDaily ? new Date(v + "T00:00:00").toLocaleDateString() : v}
727
- formatter={(value: number) => [value.toLocaleString(), undefined]}
728
- />
729
- <Legend
730
- wrapperStyle={{ fontSize: 11 }}
731
- iconType="circle"
732
- iconSize={8}
733
- />
734
- <Bar
735
- dataKey="input_tokens"
736
- name="Input Tokens"
737
- fill="var(--color-accent)"
738
- radius={[2, 2, 0, 0]}
739
-
740
- />
741
- <Bar
742
- dataKey="output_tokens"
743
- name="Output Tokens"
744
- fill="var(--color-accent-hover)"
745
- radius={[2, 2, 0, 0]}
717
+ {/* Token Usage Chart - stacked bars: Input (cache read / cache write / regular) and Output (reasoning / regular) */}
718
+ {(() => {
719
+ // Compute stacked segments for each bucket
720
+ const stackedData = chartData.map(d => {
721
+ const cacheRead = d.cache_read_tokens || 0;
722
+ const cacheWrite = d.cache_creation_tokens || 0;
723
+ const regularInput = Math.max(0, d.input_tokens - cacheRead - cacheWrite);
724
+ const reasoning = d.reasoning_tokens || 0;
725
+ const regularOutput = Math.max(0, d.output_tokens - reasoning);
726
+ return {
727
+ date: d.date,
728
+ // Input stack (bottom to top: cache read, cache write, regular)
729
+ input_cache_read: cacheRead,
730
+ input_cache_write: cacheWrite,
731
+ input_regular: regularInput,
732
+ // Output stack (bottom to top: reasoning, regular)
733
+ output_reasoning: reasoning,
734
+ output_regular: regularOutput,
735
+ };
736
+ });
737
+ const hasCache = stackedData.some(d => d.input_cache_read > 0 || d.input_cache_write > 0);
738
+ const hasReasoning = stackedData.some(d => d.output_reasoning > 0);
746
739
 
747
- />
748
- </BarChart>
749
- </ResponsiveContainer>
750
- </div>
740
+ return (
741
+ <div className="bg-[var(--color-surface)] card p-4">
742
+ <h3 className="text-sm font-medium text-[var(--color-text-secondary)] mb-4">{chartLabel} Token Usage</h3>
743
+ <ResponsiveContainer width="100%" height={200}>
744
+ <BarChart data={stackedData}>
745
+ <CartesianGrid strokeDasharray="3 3" stroke="var(--color-border)" />
746
+ <XAxis
747
+ dataKey="date"
748
+ stroke="var(--color-border-light)"
749
+ tick={{ fill: "var(--color-text-muted)", fontSize: 11 }}
750
+ tickFormatter={(v) => {
751
+ if (!useDaily && v.includes(" ")) {
752
+ return v.split(" ")[1];
753
+ }
754
+ const d = new Date(v + "T00:00:00");
755
+ return `${d.getMonth() + 1}/${d.getDate()}`;
756
+ }}
757
+ />
758
+ <YAxis
759
+ stroke="var(--color-border-light)"
760
+ tick={{ fill: "var(--color-text-muted)", fontSize: 11 }}
761
+ tickFormatter={(v) => {
762
+ if (v >= 1000000) return `${(v / 1000000).toFixed(1)}M`;
763
+ if (v >= 1000) return `${(v / 1000).toFixed(0)}K`;
764
+ return v;
765
+ }}
766
+ />
767
+ <Tooltip
768
+ contentStyle={{
769
+ backgroundColor: "var(--color-surface)",
770
+ border: "1px solid var(--color-border-light)",
771
+ borderRadius: "8px",
772
+ fontSize: 12,
773
+ }}
774
+ labelStyle={{ color: "var(--color-text-secondary)" }}
775
+ cursor={{ fill: "rgba(255,255,255,0.03)" }}
776
+ labelFormatter={(v) => useDaily ? new Date(v + "T00:00:00").toLocaleDateString() : v}
777
+ formatter={(value: number, name: string) => [value.toLocaleString(), name]}
778
+ />
779
+ <Legend
780
+ wrapperStyle={{ fontSize: 11 }}
781
+ iconType="circle"
782
+ iconSize={8}
783
+ />
784
+ {/* Input stack */}
785
+ {hasCache && (
786
+ <Bar dataKey="input_cache_read" name="Input (Cache Read)" stackId="input" fill="#fdba74" />
787
+ )}
788
+ {hasCache && (
789
+ <Bar dataKey="input_cache_write" name="Input (Cache Write)" stackId="input" fill="#fb923c" />
790
+ )}
791
+ <Bar dataKey="input_regular" name={hasCache ? "Input (Regular)" : "Input Tokens"} stackId="input" fill="#f97316" radius={[2, 2, 0, 0]} />
792
+ {/* Output stack */}
793
+ {hasReasoning && (
794
+ <Bar dataKey="output_reasoning" name="Output (Reasoning)" stackId="output" fill="#c4b5fd" />
795
+ )}
796
+ <Bar dataKey="output_regular" name={hasReasoning ? "Output (Regular)" : "Output Tokens"} stackId="output" fill="#a78bfa" radius={[2, 2, 0, 0]} />
797
+ </BarChart>
798
+ </ResponsiveContainer>
799
+ </div>
800
+ );
801
+ })()}
751
802
  </div>
752
803
  );
753
804
  })()}
@@ -997,7 +1048,7 @@ export function TelemetryPage() {
997
1048
  <div className="p-8 text-center text-[var(--color-text-muted)]">Loading...</div>
998
1049
  ) : allEvents.length === 0 ? (
999
1050
  <div className="p-8 text-center text-[var(--color-text-muted)]">
1000
- No telemetry events yet. Events will appear here in real-time once agents start sending data.
1051
+ No events yet. Events will appear here in real-time once agents start sending data.
1001
1052
  </div>
1002
1053
  ) : (
1003
1054
  <div className="divide-y divide-[var(--color-border)]">
@@ -12,6 +12,7 @@ export interface TelemetryEvent {
12
12
  data?: Record<string, unknown>;
13
13
  duration_ms?: number;
14
14
  error?: string;
15
+ cost?: number;
15
16
  }
16
17
 
17
18
  interface TelemetryContextValue {
package/src/web/types.ts CHANGED
@@ -158,7 +158,7 @@ export interface OnboardingStatus {
158
158
  has_any_keys: boolean;
159
159
  }
160
160
 
161
- export type Route = "dashboard" | "threads" | "agents" | "activity" | "tasks" | "connections" | "mcp" | "skills" | "tests" | "telemetry" | "settings" | "api";
161
+ export type Route = "dashboard" | "threads" | "agents" | "activity" | "tasks" | "connections" | "mcp" | "skills" | "tests" | "analytics" | "settings" | "api";
162
162
 
163
163
  // Tool use content block in trajectory
164
164
  export interface ToolUseBlock {