summaryrefslogtreecommitdiffhomepage
path: root/packages/frontend/tests
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-02 13:25:23 +0900
committerAdam Malczewski <[email protected]>2026-06-02 13:25:23 +0900
commit6433cc42de1ceca7210e2b64ad3b98b3a5ce7d02 (patch)
tree78b30dedd471ab76177b3631a956ab160615e303 /packages/frontend/tests
parent3f629a8469fe483243671e1ca15582a111e96541 (diff)
downloaddispatch-6433cc42de1ceca7210e2b64ad3b98b3a5ce7d02.tar.gz
dispatch-6433cc42de1ceca7210e2b64ad3b98b3a5ce7d02.zip
feat(context-window): show current/max context usage per tab/model
Add a 'Context Window' sidebar view showing the live context occupancy (latest request's input+output) against the model's maximum context window, resolved dynamically from the models.dev catalog. - core: models.dev catalog module (resolveContextLimit) with disk cache, TTL, stale-fallback + offline penalty memo; null for unknown models. - api: GET /models/context-limit?provider=&modelId=. - frontend: ContextWindowPanel + computeContextUsage helper; App resolves + caches the active model's max (anthropic/opencode-anthropic only); percent shown to 2 decimals; degrades to bare token count when max unknown. - tests: core catalog (13), api route (3), frontend helper (6).
Diffstat (limited to 'packages/frontend/tests')
-rw-r--r--packages/frontend/tests/context-window.test.ts84
1 files changed, 84 insertions, 0 deletions
diff --git a/packages/frontend/tests/context-window.test.ts b/packages/frontend/tests/context-window.test.ts
new file mode 100644
index 0000000..bb64ed5
--- /dev/null
+++ b/packages/frontend/tests/context-window.test.ts
@@ -0,0 +1,84 @@
+import { describe, expect, it } from "vitest";
+import { computeContextUsage } from "../src/lib/context-window.js";
+import type { CacheStats } from "../src/lib/types.js";
+
+function stats(last: CacheStats["last"]): CacheStats {
+ return {
+ inputTokens: 0,
+ outputTokens: 0,
+ cacheReadTokens: 0,
+ cacheWriteTokens: 0,
+ requests: last ? 1 : 0,
+ last,
+ };
+}
+
+describe("computeContextUsage", () => {
+ it("derives current context from the LAST request's input + output", () => {
+ const usage = computeContextUsage(
+ stats({
+ inputTokens: 47000,
+ outputTokens: 1200,
+ cacheReadTokens: 40000,
+ cacheWriteTokens: 0,
+ }),
+ 200000,
+ );
+ // 47000 + 1200 — NOT the cumulative totals, and cache tokens are already
+ // inside inputTokens (not re-added).
+ expect(usage.current).toBe(48200);
+ expect(usage.max).toBe(200000);
+ expect(usage.percent).toBeCloseTo(24.1, 5); // 48200 / 200000 * 100, unrounded
+ });
+
+ it("returns max=null and percent=null when the limit is unknown", () => {
+ const usage = computeContextUsage(
+ stats({ inputTokens: 100, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }),
+ null,
+ );
+ expect(usage.current).toBe(100);
+ expect(usage.max).toBeNull();
+ expect(usage.percent).toBeNull();
+ });
+
+ it("treats a non-positive limit as unknown", () => {
+ const usage = computeContextUsage(
+ stats({ inputTokens: 100, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }),
+ 0,
+ );
+ expect(usage.max).toBeNull();
+ expect(usage.percent).toBeNull();
+ });
+
+ it("reports zero usage when no request has completed yet", () => {
+ expect(computeContextUsage(null, 200000)).toEqual({
+ current: 0,
+ max: 200000,
+ percent: 0,
+ });
+ expect(computeContextUsage(stats(null), 200000)).toEqual({
+ current: 0,
+ max: 200000,
+ percent: 0,
+ });
+ });
+
+ it("clamps percent to 100 when context overflows the window", () => {
+ const usage = computeContextUsage(
+ stats({ inputTokens: 250000, outputTokens: 5000, cacheReadTokens: 0, cacheWriteTokens: 0 }),
+ 200000,
+ );
+ expect(usage.current).toBe(255000);
+ expect(usage.percent).toBe(100);
+ });
+
+ it("keeps an unrounded percent so the UI can show 2 decimals", () => {
+ const usage = computeContextUsage(
+ stats({ inputTokens: 3690, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }),
+ 1000000,
+ );
+ // 3690 / 1,000,000 * 100 = 0.369 → displayed as "0.37%" (toFixed(2)).
+ expect(usage.percent).toBeCloseTo(0.369, 6);
+ expect((usage.percent as number).toFixed(2)).toBe("0.37");
+ });
+});