diff options
Diffstat (limited to 'src/core')
| -rw-r--r-- | src/core/metrics/format.test.ts | 44 | ||||
| -rw-r--r-- | src/core/metrics/format.ts | 39 | ||||
| -rw-r--r-- | src/core/metrics/index.ts | 3 |
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, |
