@oh-my-pi/omp-stats 18.5.0 → 18.5.1

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.
@@ -14,6 +14,8 @@ export declare function isUnpricedMessage(message: Pick<MessageStats, "provider"
14
14
  /** Format one request's cost, distinguishing unpriced usage from free usage. */
15
15
  export declare function formatMessageCost(message: Pick<MessageStats, "provider" | "usage" | "costUnpriced">, digits?: number): string;
16
16
  export declare function formatPercent(value: number, digits?: number): string;
17
+ /** Show small nonzero failure rates without rounding them to zero. */
18
+ export declare function formatErrorRate(value: number): string;
17
19
  export declare function formatDurationMs(value: number | null, digits?: number): string;
18
20
  /** Wall-clock length: seconds below a minute, then `17m 41s` / `2h 05m`. */
19
21
  export declare function formatElapsed(ms: number): string;
@@ -61,7 +61,7 @@ export interface MessageStatsInput extends Omit<MessageStats, "usage" | "costUnp
61
61
  export interface RequestDetails extends MessageStats {
62
62
  /** The full conversation history or just the last turn. */
63
63
  messages: unknown[];
64
- /** The model's response. */
64
+ /** The model's response; `null` for `model_usage` entries, which journal no payload. */
65
65
  output: unknown;
66
66
  }
67
67
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/omp-stats",
4
- "version": "18.5.0",
4
+ "version": "18.5.1",
5
5
  "description": "Local observability dashboard for omp AI usage statistics",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": {
@@ -42,9 +42,9 @@
42
42
  "fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
43
43
  },
44
44
  "dependencies": {
45
- "@oh-my-pi/pi-ai": "18.5.0",
46
- "@oh-my-pi/pi-catalog": "18.5.0",
47
- "@oh-my-pi/pi-utils": "18.5.0",
45
+ "@oh-my-pi/pi-ai": "18.5.1",
46
+ "@oh-my-pi/pi-catalog": "18.5.1",
47
+ "@oh-my-pi/pi-utils": "18.5.1",
48
48
  "lucide-react": "^1.24.0",
49
49
  "react": "19.2.7",
50
50
  "react-dom": "19.2.7"
package/src/aggregator.ts CHANGED
@@ -565,6 +565,9 @@ export async function getRequestDetails(id: number): Promise<RequestDetails | nu
565
565
  if (!msg) return null;
566
566
 
567
567
  const entry = await getSessionEntry(msg.sessionFile, msg.entryId);
568
+ // Role-model attempts (judge, auto-thinking, …) journal only a `model_usage`
569
+ // record: usage and outcome, no request or response payload.
570
+ if (entry?.type === "model_usage") return { ...msg, messages: [entry], output: null };
568
571
  if (entry?.type !== "message" || !("message" in entry)) return null;
569
572
 
570
573
  return {
@@ -56,6 +56,13 @@ export function formatPercent(value: number, digits = 1): string {
56
56
  return `${(value * 100).toFixed(digits)}%`;
57
57
  }
58
58
 
59
+ /** Show small nonzero failure rates without rounding them to zero. */
60
+ export function formatErrorRate(value: number): string {
61
+ const percent = value * 100;
62
+ if (percent > 0 && percent < 0.005) return "<0.01%";
63
+ return formatPercent(value, percent > 0 && percent < 0.1 ? 2 : 1);
64
+ }
65
+
59
66
  export function formatDurationMs(value: number | null, digits?: number): string {
60
67
  if (value === null) return "-";
61
68
  const sec = value / 1000;
@@ -7,6 +7,7 @@ import { buildModelColorLookup, modelKey, OTHER_COLOR } from "../data/colors";
7
7
  import {
8
8
  formatCompact,
9
9
  formatDurationMs,
10
+ formatErrorRate,
10
11
  formatEstimatedCost,
11
12
  formatInteger,
12
13
  formatPercent,
@@ -388,7 +389,7 @@ function buildModelColumns(view: ModelsView, expandedKey: string | null, bucketW
388
389
  ) : (
389
390
  <span title={`${formatInteger(row.failedRequests)} failed`}>
390
391
  <Badge tone={errorRateTone(row.errorRate)} mono>
391
- {formatPercent(row.errorRate)}
392
+ {formatErrorRate(row.errorRate)}
392
393
  </Badge>
393
394
  </span>
394
395
  ),
@@ -477,7 +478,7 @@ function ModelDetail({
477
478
  label: "Error rate",
478
479
  value: (
479
480
  <span className={`tone-${errorRateTone(model.errorRate)}`}>
480
- {formatPercent(model.errorRate)}{" "}
481
+ {formatErrorRate(model.errorRate)}{" "}
481
482
  <span className="dim">({formatInteger(model.failedRequests)} failed)</span>
482
483
  </span>
483
484
  ),
@@ -5,6 +5,7 @@ import { Legend, ShareBar, TimeChart } from "../charts";
5
5
  import {
6
6
  formatCompact,
7
7
  formatDurationMs,
8
+ formatErrorRate,
8
9
  formatEstimatedCost,
9
10
  formatInteger,
10
11
  formatMessageCost,
@@ -133,7 +134,7 @@ export function OverviewRoute({ active, range, onRequestClick }: OverviewRoutePr
133
134
  />
134
135
  <Stat
135
136
  label="Error rate"
136
- value={formatPercent(overall.errorRate)}
137
+ value={formatErrorRate(overall.errorRate)}
137
138
  hint={`${formatInteger(overall.successfulRequests)} succeeded`}
138
139
  spark={series.errors}
139
140
  sparkColor="var(--bad)"
@@ -4,6 +4,7 @@ import { BarList } from "../charts";
4
4
  import {
5
5
  formatCompact,
6
6
  formatDurationMs,
7
+ formatErrorRate,
7
8
  formatEstimatedCost,
8
9
  formatFolder,
9
10
  formatInteger,
@@ -296,7 +297,7 @@ function folderColumns(maxRequests: number, maxCost: number): Column<FolderRowVi
296
297
  render: row => (
297
298
  <span title={`${formatInteger(row.failedRequests)} failed`}>
298
299
  <Badge tone={row.failedRequests > 0 ? errorRateTone(row.errorRate) : "neutral"} mono>
299
- {formatPercent(row.errorRate)}
300
+ {formatErrorRate(row.errorRate)}
300
301
  </Badge>
301
302
  </span>
302
303
  ),
@@ -6,6 +6,7 @@ import { buildColorLookup, OTHER_COLOR, SERIES_COLORS } from "../data/colors";
6
6
  import {
7
7
  formatCompact,
8
8
  formatCost,
9
+ formatErrorRate,
9
10
  formatEstimatedCost,
10
11
  formatInteger,
11
12
  formatPercent,
@@ -178,7 +179,7 @@ export function ProvidersRoute({ active, range }: ProvidersRouteProps) {
178
179
  />
179
180
  <Stat
180
181
  label="Error rate"
181
- value={formatPercent(t.requests > 0 ? t.failed / t.requests : 0)}
182
+ value={formatErrorRate(t.requests > 0 ? t.failed / t.requests : 0)}
182
183
  hint={`${formatInteger(t.requests - t.failed)} succeeded`}
183
184
  />
184
185
  </StatGrid>
@@ -338,7 +339,7 @@ function ProviderTotalsTable({
338
339
  <span className="row" style={{ gap: 8, justifyContent: "flex-end" }}>
339
340
  <span className="num dim">{formatInteger(p.failedRequests)}</span>
340
341
  <Badge tone={p.failedRequests > 0 ? errorRateTone(rate) : "neutral"} mono>
341
- {formatPercent(rate)}
342
+ {formatErrorRate(rate)}
342
343
  </Badge>
343
344
  </span>
344
345
  );
@@ -3,11 +3,11 @@ import { getRecentRequests } from "../api";
3
3
  import {
4
4
  formatCompact,
5
5
  formatDurationMs,
6
+ formatErrorRate,
6
7
  formatEstimatedCost,
7
8
  formatFolder,
8
9
  formatInteger,
9
10
  formatMessageCost,
10
- formatPercent,
11
11
  formatRelativeTime,
12
12
  formatTimestamp,
13
13
  } from "../data/formatters";
@@ -125,7 +125,7 @@ export function RequestsRoute({ active, range, onRequestClick }: RequestsRoutePr
125
125
  <Stat
126
126
  label="Failed"
127
127
  value={formatInteger(summary.failed)}
128
- hint={`${summary.requests > 0 ? formatPercent(summary.failed / summary.requests) : "–"} · ${formatInteger(summary.aborted)} aborted`}
128
+ hint={`${summary.requests > 0 ? formatErrorRate(summary.failed / summary.requests) : "–"} · ${formatInteger(summary.aborted)} aborted`}
129
129
  />
130
130
  <Stat
131
131
  label="Tokens"
@@ -5,6 +5,7 @@ import { Legend, Sparkline, TimeChart, useHiddenSeries } from "../charts";
5
5
  import { buildColorLookup, OTHER_COLOR } from "../data/colors";
6
6
  import {
7
7
  formatCompact,
8
+ formatErrorRate,
8
9
  formatEstimatedCost,
9
10
  formatInteger,
10
11
  formatPercent,
@@ -96,7 +97,7 @@ export function ToolsRoute({ active, range }: ToolsRouteProps) {
96
97
  <Stat
97
98
  label="Error rate"
98
99
  title="Tool results that came back flagged as errors"
99
- value={formatPercent(t.calls > 0 ? t.errors / t.calls : 0)}
100
+ value={formatErrorRate(t.calls > 0 ? t.errors / t.calls : 0)}
100
101
  hint={`${formatInteger(t.calls - t.errors)} succeeded`}
101
102
  spark={view.totalErrors}
102
103
  sparkColor="var(--bad)"
@@ -389,7 +390,7 @@ function buildToolColumns(
389
390
  <span className="row" style={{ gap: 8, justifyContent: "flex-end" }}>
390
391
  <span className="num dim">{formatInteger(row.errors)}</span>
391
392
  <Badge tone={row.errors > 0 ? errorRateTone(row.errorRate) : "neutral"} mono>
392
- {formatPercent(row.errorRate)}
393
+ {formatErrorRate(row.errorRate)}
393
394
  </Badge>
394
395
  </span>
395
396
  ),
@@ -519,7 +520,7 @@ function ToolModelTable({
519
520
  <span className="row" style={{ gap: 8, justifyContent: "flex-end" }}>
520
521
  <span className="num dim">{formatInteger(row.errors)}</span>
521
522
  <Badge tone={row.errors > 0 ? errorRateTone(row.errorRate) : "neutral"} mono>
522
- {formatPercent(row.errorRate)}
523
+ {formatErrorRate(row.errorRate)}
523
524
  </Badge>
524
525
  </span>
525
526
  ),
@@ -949,6 +949,7 @@ kbd {
949
949
 
950
950
  .btn[data-variant="accent"] {
951
951
  background: var(--accent-gradient);
952
+ background-origin: border-box;
952
953
  color: #fff;
953
954
  border-color: transparent;
954
955
  }
@@ -175,7 +175,7 @@ function RequestDetailsBody({ details, aborted }: { details: RequestDetails; abo
175
175
  </div>
176
176
  </section>
177
177
 
178
- <JsonBlock data={output} title="Output message" />
178
+ {output != null && <JsonBlock data={output} title="Output message" />}
179
179
  <JsonBlock data={messages} title="Session entry" initialCollapsed />
180
180
  <JsonBlock data={row} title="Stats row" initialCollapsed />
181
181
  </>
package/src/index.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  import { parseArgs } from "node:util";
4
4
  import { formatDuration, formatNumber, formatPercent, normalizePremiumRequests } from "@oh-my-pi/pi-utils";
5
5
  import chalk from "@oh-my-pi/pi-utils/chalk";
6
+ import { formatErrorRate } from "./client/data/formatters";
6
7
  import { getDashboardStats, getTotalMessageCount, syncAllSessions } from "./aggregator";
7
8
  import { closeDb } from "./db";
8
9
  import { refreshRollups } from "./rollup";
@@ -58,7 +59,7 @@ export async function printStatsSummary(): Promise<void> {
58
59
 
59
60
  console.log(chalk.bold("Overall:"));
60
61
  console.log(` Requests: ${formatNumber(overall.totalRequests)} (${formatNumber(overall.failedRequests)} errors)`);
61
- console.log(` Error Rate: ${formatPercent(overall.errorRate)}`);
62
+ console.log(` Error Rate: ${formatErrorRate(overall.errorRate)}`);
62
63
  console.log(` Total Tokens: ${formatNumber(overall.totalInputTokens + overall.totalOutputTokens)}`);
63
64
  console.log(` Input Tokens: ${formatNumber(overall.totalInputTokens)}`);
64
65
  console.log(` Output Tokens: ${formatNumber(overall.totalOutputTokens)}`);
package/src/types.ts CHANGED
@@ -63,7 +63,7 @@ export interface MessageStatsInput extends Omit<MessageStats, "usage" | "costUnp
63
63
  export interface RequestDetails extends MessageStats {
64
64
  /** The full conversation history or just the last turn. */
65
65
  messages: unknown[];
66
- /** The model's response. */
66
+ /** The model's response; `null` for `model_usage` entries, which journal no payload. */
67
67
  output: unknown;
68
68
  }
69
69