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.
- package/dist/App.9t1zc5r7.js +53 -0
- package/dist/{App.d9tny4t0.js → App.k4nmqgek.js} +3 -3
- package/dist/{App.xf7wsckg.js → App.vza4fxg0.js} +2 -2
- package/dist/{SettingsPage.3sqx6wm4.js → SettingsPage.q1pqcc93.js} +1 -1
- package/dist/{TelemetryPage.a9fmxq87.js → TelemetryPage.an0ky78c.js} +1 -1
- package/dist/index.html +1 -1
- package/package.json +6 -6
- package/src/db.ts +18 -18
- package/src/routes/api/telemetry.ts +1 -0
- package/src/server.ts +1 -0
- package/src/web/App.tsx +1 -1
- package/src/web/components/dashboard/Dashboard.tsx +2 -2
- package/src/web/components/layout/Sidebar.tsx +3 -3
- package/src/web/components/settings/SettingsPage.tsx +4 -4
- package/src/web/components/telemetry/TelemetryPage.tsx +138 -87
- package/src/web/context/TelemetryContext.tsx +1 -0
- package/src/web/types.ts +1 -1
- package/dist/App.0ws87fpx.js +0 -53
|
@@ -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:
|
|
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
|
-
? "
|
|
580
|
+
? "Analytics"
|
|
534
581
|
: currentProjectId === "unassigned"
|
|
535
|
-
? "
|
|
536
|
-
: `
|
|
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,
|
|
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
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
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
|
-
|
|
749
|
-
|
|
750
|
-
|
|
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
|
|
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)]">
|
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" | "
|
|
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 {
|