summaryrefslogtreecommitdiffhomepage
path: root/src/core
diff options
context:
space:
mode:
Diffstat (limited to 'src/core')
-rw-r--r--src/core/metrics/format.test.ts44
-rw-r--r--src/core/metrics/format.ts39
-rw-r--r--src/core/metrics/index.ts3
3 files changed, 86 insertions, 0 deletions
diff --git a/src/core/metrics/format.test.ts b/src/core/metrics/format.test.ts
index 7c143d7..6a4bd38 100644
--- a/src/core/metrics/format.test.ts
+++ b/src/core/metrics/format.test.ts
@@ -2,8 +2,10 @@ import type { StepId, StepMetrics, TurnMetrics } from "@dispatch/wire";
import { describe, expect, it } from "vitest";
import {
computeCachePct,
+ computeContextUsage,
computeExpectedCachePct,
computeTps,
+ formatCompactTokens,
formatContextSize,
viewCacheRate,
viewExpectedCache,
@@ -323,3 +325,45 @@ describe("formatContextSize", () => {
expect(formatContextSize(0)).toBe("0 tokens in context");
});
});
+
+describe("formatCompactTokens", () => {
+ it("renders sub-1k counts as-is", () => {
+ expect(formatCompactTokens(0)).toBe("0");
+ expect(formatCompactTokens(812)).toBe("812");
+ });
+
+ it("renders thousands with one decimal (rounded ≥100k)", () => {
+ expect(formatCompactTokens(12300)).toBe("12.3k");
+ expect(formatCompactTokens(150000)).toBe("150k");
+ });
+
+ it("renders millions with one decimal", () => {
+ expect(formatCompactTokens(1_200_000)).toBe("1.2M");
+ expect(formatCompactTokens(1_000_000)).toBe("1.0M");
+ });
+});
+
+describe("computeContextUsage", () => {
+ it("computes an unrounded clamped percent against the limit", () => {
+ const u = computeContextUsage(34102, 1_000_000);
+ expect(u.current).toBe(34102);
+ expect(u.max).toBe(1_000_000);
+ expect(u.percent).toBeCloseTo(3.4102, 4);
+ });
+
+ it("treats unknown contextSize as current 0", () => {
+ const u = computeContextUsage(undefined, 1_000_000);
+ expect(u.current).toBe(0);
+ expect(u.percent).toBe(0);
+ });
+
+ it("clamps percent to [0,100] and over-limit reads 100", () => {
+ expect(computeContextUsage(2_000_000, 1_000_000).percent).toBe(100);
+ });
+
+ it("max null (no/zero limit) ⇒ percent null", () => {
+ expect(computeContextUsage(5000, null).percent).toBeNull();
+ expect(computeContextUsage(5000, 0).percent).toBeNull();
+ expect(computeContextUsage(5000, null).max).toBeNull();
+ });
+});
diff --git a/src/core/metrics/format.ts b/src/core/metrics/format.ts
index d8dd2cc..4d69f25 100644
--- a/src/core/metrics/format.ts
+++ b/src/core/metrics/format.ts
@@ -28,6 +28,45 @@ export function formatContextSize(n: number | undefined): string {
return `${formatTokens(n)} tokens in context`;
}
+/**
+ * Compact token count for a slim status bar: `812`, `12.3k`, `1.2M`. Full
+ * thousands-separated numbers live elsewhere; this trades precision for width.
+ */
+export function formatCompactTokens(n: number): string {
+ if (n < 1000) return `${n}`;
+ if (n < 1_000_000) {
+ const k = n / 1000;
+ return `${k >= 100 ? Math.round(k) : k.toFixed(1)}k`;
+ }
+ const m = n / 1_000_000;
+ return `${m >= 100 ? Math.round(m) : m.toFixed(1)}M`;
+}
+
+/**
+ * Context-window occupancy: the current size against a max window limit.
+ *
+ * `current` is the latest turn's context size (0 when unknown); `max` is the
+ * model's window limit (or `null` when unknown). `percent` is
+ * `current / max * 100` clamped to [0, 100], UNROUNDED (the UI picks the
+ * precision) — so a few-thousand-token context against a 1,000,000 window still
+ * reads non-zero. `percent` is `null` when `max` is unknown (no bar/denominator).
+ */
+export interface ContextUsage {
+ readonly current: number;
+ readonly max: number | null;
+ readonly percent: number | null;
+}
+
+export function computeContextUsage(
+ contextSize: number | undefined,
+ contextLimit: number | null | undefined,
+): ContextUsage {
+ const current = contextSize ?? 0;
+ const max = typeof contextLimit === "number" && contextLimit > 0 ? contextLimit : null;
+ const percent = max === null ? null : Math.max(0, Math.min(100, (current / max) * 100));
+ return { current, max, percent };
+}
+
/** Compute tokens-per-second. Returns null when elapsed time is absent or zero. */
export function computeTps(outputTokens: number, elapsedMs: number | undefined): number | null {
if (elapsedMs === undefined || elapsedMs <= 0) return null;
diff --git a/src/core/metrics/index.ts b/src/core/metrics/index.ts
index 773d697..36cd96f 100644
--- a/src/core/metrics/index.ts
+++ b/src/core/metrics/index.ts
@@ -1,7 +1,10 @@
export {
+ type ContextUsage,
computeCachePct,
+ computeContextUsage,
computeExpectedCachePct,
computeTps,
+ formatCompactTokens,
formatContextSize,
viewCacheRate,
viewExpectedCache,