@oh-my-pi/omp-stats 17.0.5 → 17.0.7

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.
@@ -1,10 +1,11 @@
1
- /*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */
1
+ /*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
2
2
  @layer properties;
3
3
  @layer theme, base, components, utilities;
4
4
  @layer theme {
5
5
  :root, :host {
6
- --font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
7
- "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
6
+ --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
7
+ "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
8
+ "Segoe UI Symbol", "Noto Color Emoji";
8
9
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
9
10
  "Courier New", monospace;
10
11
  --spacing: 0.25rem;
@@ -40,7 +41,7 @@
40
41
  line-height: 1.5;
41
42
  -webkit-text-size-adjust: 100%;
42
43
  tab-size: 4;
43
- font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");
44
+ font-family: var(--default-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");
44
45
  font-feature-settings: var(--default-font-feature-settings, normal);
45
46
  font-variation-settings: var(--default-font-variation-settings, normal);
46
47
  -webkit-tap-highlight-color: transparent;
@@ -92,7 +93,7 @@
92
93
  border-color: inherit;
93
94
  border-collapse: collapse;
94
95
  }
95
- :-moz-focusring {
96
+ :-moz-focusring:where(:not(iframe)) {
96
97
  outline: auto;
97
98
  }
98
99
  progress {
@@ -251,7 +252,7 @@
251
252
  max-width: 440px;
252
253
  }
253
254
  .min-w-0 {
254
- min-width: 0;
255
+ min-width: 0px;
255
256
  }
256
257
  .flex-1 {
257
258
  flex: 1;
@@ -298,40 +299,30 @@
298
299
  .gap-6 {
299
300
  gap: calc(var(--spacing) * 6);
300
301
  }
301
- .space-y-0\.5 {
302
- :where(& > :not(:last-child)) {
303
- --tw-space-y-reverse: 0;
304
- margin-block-start: calc(calc(var(--spacing) * 0.5) * var(--tw-space-y-reverse));
305
- margin-block-end: calc(calc(var(--spacing) * 0.5) * calc(1 - var(--tw-space-y-reverse)));
306
- }
302
+ :where(.space-y-0\.5 > :not(:last-child)) {
303
+ --tw-space-y-reverse: 0;
304
+ margin-block-start: calc(calc(var(--spacing) * 0.5) * var(--tw-space-y-reverse));
305
+ margin-block-end: calc(calc(var(--spacing) * 0.5) * calc(1 - var(--tw-space-y-reverse)));
307
306
  }
308
- .space-y-1 {
309
- :where(& > :not(:last-child)) {
310
- --tw-space-y-reverse: 0;
311
- margin-block-start: calc(var(--spacing) * var(--tw-space-y-reverse));
312
- margin-block-end: calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)));
313
- }
307
+ :where(.space-y-1 > :not(:last-child)) {
308
+ --tw-space-y-reverse: 0;
309
+ margin-block-start: calc(var(--spacing) * var(--tw-space-y-reverse));
310
+ margin-block-end: calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)));
314
311
  }
315
- .space-y-2 {
316
- :where(& > :not(:last-child)) {
317
- --tw-space-y-reverse: 0;
318
- margin-block-start: calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));
319
- margin-block-end: calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)));
320
- }
312
+ :where(.space-y-2 > :not(:last-child)) {
313
+ --tw-space-y-reverse: 0;
314
+ margin-block-start: calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));
315
+ margin-block-end: calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)));
321
316
  }
322
- .space-y-4 {
323
- :where(& > :not(:last-child)) {
324
- --tw-space-y-reverse: 0;
325
- margin-block-start: calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));
326
- margin-block-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)));
327
- }
317
+ :where(.space-y-4 > :not(:last-child)) {
318
+ --tw-space-y-reverse: 0;
319
+ margin-block-start: calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));
320
+ margin-block-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)));
328
321
  }
329
- .space-y-6 {
330
- :where(& > :not(:last-child)) {
331
- --tw-space-y-reverse: 0;
332
- margin-block-start: calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));
333
- margin-block-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)));
334
- }
322
+ :where(.space-y-6 > :not(:last-child)) {
323
+ --tw-space-y-reverse: 0;
324
+ margin-block-start: calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));
325
+ margin-block-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)));
335
326
  }
336
327
  .truncate {
337
328
  overflow: hidden;
@@ -466,30 +457,24 @@
466
457
  transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
467
458
  transition-duration: var(--tw-duration, var(--default-transition-duration));
468
459
  }
469
- .hover\:bg-\[var\(--bg-hover\)\] {
470
- &:hover {
471
- @media (hover: hover) {
472
- background-color: var(--bg-hover);
473
- }
460
+ @media (hover: hover) {
461
+ .hover\:bg-\[var\(--bg-hover\)\]:hover {
462
+ background-color: var(--bg-hover);
474
463
  }
475
464
  }
476
- .md\:grid-cols-3 {
477
- @media (width >= 48rem) {
465
+ @media (width >= 48rem) {
466
+ .md\:grid-cols-3 {
478
467
  grid-template-columns: repeat(3, minmax(0, 1fr));
479
468
  }
480
- }
481
- .md\:grid-cols-6 {
482
- @media (width >= 48rem) {
469
+ .md\:grid-cols-6 {
483
470
  grid-template-columns: repeat(6, minmax(0, 1fr));
484
471
  }
485
472
  }
486
- .lg\:col-span-2 {
487
- @media (width >= 64rem) {
473
+ @media (width >= 64rem) {
474
+ .lg\:col-span-2 {
488
475
  grid-column: span 2 / span 2;
489
476
  }
490
- }
491
- .lg\:grid-cols-3 {
492
- @media (width >= 64rem) {
477
+ .lg\:grid-cols-3 {
493
478
  grid-template-columns: repeat(3, minmax(0, 1fr));
494
479
  }
495
480
  }
@@ -974,15 +959,15 @@
974
959
  }
975
960
  .stats-metric-secondary-grid {
976
961
  display: grid;
977
- grid-template-columns: repeat(6, 1fr);
962
+ grid-template-columns: repeat(8, 1fr);
978
963
  gap: 12px;
979
964
  }
980
- @media (max-width: 1023px) {
965
+ @media (max-width: 1279px) {
981
966
  .stats-metric-secondary-grid {
982
- grid-template-columns: repeat(3, 1fr);
967
+ grid-template-columns: repeat(4, 1fr);
983
968
  }
984
969
  }
985
- @media (max-width: 600px) {
970
+ @media (max-width: 767px) {
986
971
  .stats-metric-secondary-grid {
987
972
  grid-template-columns: repeat(2, 1fr);
988
973
  }
@@ -1,4 +1,12 @@
1
1
  import type { AgentType, AgentTypeStats, BehaviorOverallStats, BehaviorTimeSeriesPoint, CostTimeSeriesPoint, FolderStats, ModelPerformancePoint, TimeRange, ToolUsageStats } from "../types.js";
2
+ export interface ConversationTokenStats {
3
+ totalInputTokens: number;
4
+ totalOutputTokens: number;
5
+ totalCacheReadTokens: number;
6
+ totalCacheWriteTokens: number;
7
+ }
8
+ /** Sum every conversation-token bucket shown by the overview. */
9
+ export declare function sumConversationTokens(stats: ConversationTokenStats): number;
2
10
  export interface AgentTokenSegment {
3
11
  agentType: AgentType;
4
12
  /** input + output + cache read + cache write — the displayed denominator. */
@@ -16,9 +24,8 @@ export interface AgentTokenShareView {
16
24
  /**
17
25
  * Build the "token usage by agent" breakdown: one segment per agent type that
18
26
  * appears in the data, ordered main -> subagents -> advisor, each carrying its
19
- * token total and share of the grand total. Token counts sum the same four
20
- * columns the overview renders (input + output + cache read + cache write) so a
21
- * segment's share never disagrees with the count beside it.
27
+ * token total and share of the grand total. Token counts use the same four
28
+ * conversation-token buckets as the overview total so the two views reconcile.
22
29
  */
23
30
  export declare function buildAgentTokenShare(stats: AgentTypeStats[]): AgentTokenShareView;
24
31
  export interface CostSummaryView {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/omp-stats",
4
- "version": "17.0.5",
4
+ "version": "17.0.7",
5
5
  "description": "Local observability dashboard for pi AI usage statistics",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -39,9 +39,9 @@
39
39
  "fmt": "biome format --write ."
40
40
  },
41
41
  "dependencies": {
42
- "@oh-my-pi/pi-ai": "17.0.5",
43
- "@oh-my-pi/pi-catalog": "17.0.5",
44
- "@oh-my-pi/pi-utils": "17.0.5",
42
+ "@oh-my-pi/pi-ai": "17.0.7",
43
+ "@oh-my-pi/pi-catalog": "17.0.7",
44
+ "@oh-my-pi/pi-utils": "17.0.7",
45
45
  "@tailwindcss/node": "^4.3.2",
46
46
  "chart.js": "^4.5.1",
47
47
  "date-fns": "^4.4.0",
@@ -14,6 +14,18 @@ import type {
14
14
  /** Fixed display order for the agent-token-share breakdown. */
15
15
  const AGENT_TYPE_ORDER: AgentType[] = ["main", "subagent", "advisor"];
16
16
 
17
+ export interface ConversationTokenStats {
18
+ totalInputTokens: number;
19
+ totalOutputTokens: number;
20
+ totalCacheReadTokens: number;
21
+ totalCacheWriteTokens: number;
22
+ }
23
+
24
+ /** Sum every conversation-token bucket shown by the overview. */
25
+ export function sumConversationTokens(stats: ConversationTokenStats): number {
26
+ return stats.totalInputTokens + stats.totalOutputTokens + stats.totalCacheReadTokens + stats.totalCacheWriteTokens;
27
+ }
28
+
17
29
  export interface AgentTokenSegment {
18
30
  agentType: AgentType;
19
31
  /** input + output + cache read + cache write — the displayed denominator. */
@@ -33,25 +45,21 @@ export interface AgentTokenShareView {
33
45
  /**
34
46
  * Build the "token usage by agent" breakdown: one segment per agent type that
35
47
  * appears in the data, ordered main -> subagents -> advisor, each carrying its
36
- * token total and share of the grand total. Token counts sum the same four
37
- * columns the overview renders (input + output + cache read + cache write) so a
38
- * segment's share never disagrees with the count beside it.
48
+ * token total and share of the grand total. Token counts use the same four
49
+ * conversation-token buckets as the overview total so the two views reconcile.
39
50
  */
40
51
  export function buildAgentTokenShare(stats: AgentTypeStats[]): AgentTokenShareView {
41
52
  const byType = new Map<AgentType, AgentTypeStats>();
42
53
  for (const stat of stats) byType.set(stat.agentType, stat);
43
54
 
44
- const tokensOf = (stat: AgentTypeStats) =>
45
- stat.totalInputTokens + stat.totalOutputTokens + stat.totalCacheReadTokens + stat.totalCacheWriteTokens;
46
-
47
55
  const present = AGENT_TYPE_ORDER.map(type => byType.get(type)).filter(
48
56
  (stat): stat is AgentTypeStats => stat !== undefined,
49
57
  );
50
- const totalTokens = present.reduce((sum, stat) => sum + tokensOf(stat), 0);
58
+ const totalTokens = present.reduce((sum, stat) => sum + sumConversationTokens(stat), 0);
51
59
  const totalCost = present.reduce((sum, stat) => sum + stat.totalCost, 0);
52
60
 
53
61
  const segments = present.map(stat => {
54
- const tokens = tokensOf(stat);
62
+ const tokens = sumConversationTokens(stat);
55
63
  return {
56
64
  agentType: stat.agentType,
57
65
  tokens,
@@ -226,8 +226,8 @@ export function OverviewRoute({ active, range, refreshTrigger, onRequestClick }:
226
226
  </AsyncBoundary>
227
227
 
228
228
  <Panel
229
- title="Token Usage by Agent"
230
- subtitle="Share of tokens across the main agent, task subagents, and the advisor"
229
+ title="Conversation Tokens by Agent"
230
+ subtitle="Uncached input + cache reads + cache writes + output, grouped by agent type"
231
231
  >
232
232
  <AsyncBoundary loading={overviewLoading} error={overviewError} data={overview}>
233
233
  {overview && <AgentTokenShare stats={overview.byAgentType} />}
@@ -581,17 +581,17 @@
581
581
 
582
582
  .stats-metric-secondary-grid {
583
583
  display: grid;
584
- grid-template-columns: repeat(6, 1fr);
584
+ grid-template-columns: repeat(8, 1fr);
585
585
  gap: 12px;
586
586
  }
587
587
 
588
- @media (max-width: 1023px) {
588
+ @media (max-width: 1279px) {
589
589
  .stats-metric-secondary-grid {
590
- grid-template-columns: repeat(3, 1fr);
590
+ grid-template-columns: repeat(4, 1fr);
591
591
  }
592
592
  }
593
593
 
594
- @media (max-width: 600px) {
594
+ @media (max-width: 767px) {
595
595
  .stats-metric-secondary-grid {
596
596
  grid-template-columns: repeat(2, 1fr);
597
597
  }
@@ -6,6 +6,7 @@ import {
6
6
  formatPercent,
7
7
  formatTokensPerSecond,
8
8
  } from "../data/formatters";
9
+ import { sumConversationTokens } from "../data/view-models";
9
10
  import type { AggregatedStats } from "../types";
10
11
 
11
12
  export interface MetricClusterProps {
@@ -13,6 +14,8 @@ export interface MetricClusterProps {
13
14
  }
14
15
 
15
16
  export function MetricCluster({ stats }: MetricClusterProps) {
17
+ const conversationTokens = sumConversationTokens(stats);
18
+
16
19
  return (
17
20
  <div className="stats-metric-cluster">
18
21
  <div className="stats-metric-primary-grid">
@@ -37,14 +40,22 @@ export function MetricCluster({ stats }: MetricClusterProps) {
37
40
  </div>
38
41
 
39
42
  <div className="stats-metric-secondary-grid">
40
- <div className="stats-metric-card secondary">
41
- <div className="stats-metric-label">Input Tokens</div>
43
+ <div className="stats-metric-card secondary" title="Conversation input not served from cache">
44
+ <div className="stats-metric-label">Uncached Input</div>
42
45
  <div className="stats-metric-value">{formatCompact(stats.totalInputTokens)}</div>
43
46
  </div>
47
+ <div className="stats-metric-card secondary" title="Conversation input read from the prompt cache">
48
+ <div className="stats-metric-label">Cache Read</div>
49
+ <div className="stats-metric-value">{formatCompact(stats.totalCacheReadTokens)}</div>
50
+ </div>
44
51
  <div className="stats-metric-card secondary">
45
52
  <div className="stats-metric-label">Output Tokens</div>
46
53
  <div className="stats-metric-value">{formatCompact(stats.totalOutputTokens)}</div>
47
54
  </div>
55
+ <div className="stats-metric-card secondary" title="Uncached input + cache reads + cache writes + output">
56
+ <div className="stats-metric-label">Conversation Total</div>
57
+ <div className="stats-metric-value">{formatCompact(conversationTokens)}</div>
58
+ </div>
48
59
  <div className="stats-metric-card secondary">
49
60
  <div className="stats-metric-label">Premium Requests</div>
50
61
  <div className="stats-metric-value">{formatInteger(stats.totalPremiumRequests)}</div>