From 52b24e781a73abc92d6f8eb2f26c0cd3a3fa593f Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 1 Aug 2026 09:38:51 -0700 Subject: [PATCH] fix(sessions): reuse canonical token count formatting --- src/commands/sessions.test.ts | 89 +++++++++++++++++++++++++++++++++++ src/commands/sessions.ts | 9 ++-- 2 files changed, 93 insertions(+), 5 deletions(-) diff --git a/src/commands/sessions.test.ts b/src/commands/sessions.test.ts index 6a4cbd9796e2..53855a4da6fa 100644 --- a/src/commands/sessions.test.ts +++ b/src/commands/sessions.test.ts @@ -56,6 +56,95 @@ describe("sessionsCommand", () => { ); }); + it.each([ + { totalTokens: 0, contextTokens: 420, expected: "0/420 (0%)" }, + { totalTokens: 1, contextTokens: 999, expected: "1/999 (0%)" }, + { totalTokens: 420, contextTokens: 999, expected: "420/999 (42%)" }, + { totalTokens: 999, contextTokens: 1_000, expected: "999/1.0k (100%)" }, + { totalTokens: 1_000, contextTokens: 200_000, expected: "1.0k/200k (1%)" }, + { totalTokens: 999_499, contextTokens: 1_000_000, expected: "999k/1.0m (100%)" }, + { totalTokens: 999_500, contextTokens: 1_000_000, expected: "1.0m/1.0m (100%)" }, + { totalTokens: 1_000_000, contextTokens: 2_500_000, expected: "1.0m/2.5m (40%)" }, + ])( + "formats actual session-table token boundaries ($totalTokens/$contextTokens)", + async ({ totalTokens, contextTokens, expected }) => { + const store = await writeStore({ + "agent:main:boundary": { + sessionId: "boundary-session", + updatedAt: Date.now() - 60_000, + totalTokens, + totalTokensFresh: true, + contextTokens, + model: "test:opus", + }, + }); + + const { runtime, logs } = makeRuntime(); + await sessionsCommand({ store }, runtime); + cleanupStore(store); + + const row = logs.find((line) => line.includes("id:boundary-session")) ?? ""; + expect(row).toContain(expected); + }, + ); + + it("formats unknown totals with the actual session context-token boundary", async () => { + const store = await writeStore({ + "agent:main:unknown-boundary": { + sessionId: "unknown-boundary-session", + updatedAt: Date.now() - 60_000, + contextTokens: 999, + model: "test:opus", + }, + }); + + const { runtime, logs } = makeRuntime(); + await sessionsCommand({ store }, runtime); + cleanupStore(store); + + const row = logs.find((line) => line.includes("id:unknown-boundary-session")) ?? ""; + expect(row).toContain("unknown/999 (?%)"); + }); + + it("keeps exact numeric token counts unchanged in JSON output", async () => { + const store = await writeStore({ + "agent:main:small": { + sessionId: "small-boundary-session", + updatedAt: Date.now() - 60_000, + totalTokens: 420, + totalTokensFresh: true, + contextTokens: 999, + model: "test:opus", + }, + "agent:main:large": { + sessionId: "large-boundary-session", + updatedAt: Date.now() - 120_000, + totalTokens: 1_000_000, + totalTokensFresh: true, + contextTokens: 2_500_000, + model: "test:opus", + }, + }); + + const payload = await runSessionsJson<{ + sessions?: Array<{ key: string; totalTokens: number; contextTokens: number }>; + }>(sessionsCommand, store); + expect(payload.sessions).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + key: "agent:main:small", + totalTokens: 420, + contextTokens: 999, + }), + expect.objectContaining({ + key: "agent:main:large", + totalTokens: 1_000_000, + contextTokens: 2_500_000, + }), + ]), + ); + }); + it("renders the agent runtime in the tabular view", async () => { setMockSessionsConfig(() => ({ agents: { diff --git a/src/commands/sessions.ts b/src/commands/sessions.ts index eddaa4a42bca..915a10a28a9e 100644 --- a/src/commands/sessions.ts +++ b/src/commands/sessions.ts @@ -33,6 +33,7 @@ import { deliveryContextFromSession, sessionDeliveryOrigin, } from "../utils/delivery-context.shared.js"; +import { formatTokenCount } from "../utils/token-format.js"; import { resolveSessionStoreTargetsOrExit } from "./session-store-targets.js"; import { resolveSessionDisplayModelRef, @@ -72,8 +73,6 @@ const DEFAULT_SESSIONS_LIMIT = 100; const TOP_N_SELECTION_LIMIT = 200; const contextLookupRuntimeLoader = createLazyImportLoader(() => import("../agents/context.js")); -const formatKTokens = (value: number) => `${(value / 1000).toFixed(value >= 10_000 ? 0 : 1)}k`; - /** * Inline ACP model overlay — catalog #20. * @@ -176,12 +175,12 @@ const formatTokensCell = ( rich: boolean, ) => { if (total === undefined) { - const ctxLabel = contextTokens ? formatKTokens(contextTokens) : "?"; + const ctxLabel = contextTokens ? formatTokenCount(contextTokens) : "?"; const label = `unknown/${ctxLabel} (?%)`; return rich ? theme.muted(label.padEnd(TOKENS_PAD)) : label.padEnd(TOKENS_PAD); } - const totalLabel = formatKTokens(total); - const ctxLabel = contextTokens ? formatKTokens(contextTokens) : "?"; + const totalLabel = formatTokenCount(total); + const ctxLabel = contextTokens ? formatTokenCount(contextTokens) : "?"; const pct = contextTokens ? Math.min(999, Math.round((total / contextTokens) * 100)) : null; const label = `${totalLabel}/${ctxLabel} (${pct ?? "?"}%)`; const padded = label.padEnd(TOKENS_PAD);