Skip to content

Commit ee69abf

Browse files
committed
feat(gui): show the active-context checkpoint on Kiro log rows
Stateful providers report per-attempt usage only, so a Kiro row's per-request total stays small while the real active context grows. Now that usage rows persist contextTotalTokens, the row and detail panel use it as the displayed total and the detail grid lists it explicitly. displayContextTokenTotal is deliberately separate from displayTokenTotal: summarizeFilteredLogs sums the latter across requests, and summing an absolute context snapshot would count the same context once per request and inflate aggregates. The new helper carries a do-not-sum warning and is used only for single-row display. Cost is unaffected: normalizeCostTokens reads input/output/cache tokens only and never sees the checkpoint. New i18n key logs.tokens.contextTotal added to all six locales.
1 parent e3f4068 commit ee69abf

7 files changed

Lines changed: 36 additions & 2 deletions

File tree

gui/src/i18n/de.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,7 @@ export const de: Record<TKey, string> = {
530530
"logs.tokens.cacheWrite": "Cache-Schreiben (w)",
531531
"logs.tokens.reasoning": "Reasoning",
532532
"logs.tokens.noCache": "keine Cache-Daten",
533+
"logs.tokens.contextTotal": "aktiver Kontext",
533534
"logs.tokens.noCacheNote": "dieser Anbieter meldet keine Cache-Tokens",
534535
"logs.tokens.noCacheCursor": "Cursor-Cache-Details nicht gemeldet",
535536
"logs.tokens.noCacheCursorNote": "Cursor liefert keine Cache-Read/Write-Tokenzahlen; das ist unbekannt und kein bestätigter Cache-Miss",

gui/src/i18n/en.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,7 @@ export const en = {
548548
"logs.tokens.cacheWrite": "cache write (w)",
549549
"logs.tokens.reasoning": "reasoning",
550550
"logs.tokens.noCache": "no cache data",
551+
"logs.tokens.contextTotal": "active context",
551552
"logs.tokens.noCacheNote": "this provider does not report cache tokens",
552553
"logs.tokens.noCacheCursor": "Cursor cache detail unreported",
553554
"logs.tokens.noCacheCursorNote": "Cursor does not expose cache read/write token counts; this is unknown, not a confirmed cache miss",

gui/src/i18n/ja.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,7 @@ export const ja: Record<TKey, string> = {
515515
"logs.tokens.cacheWrite": "キャッシュ書き込み (w)",
516516
"logs.tokens.reasoning": "推論",
517517
"logs.tokens.noCache": "キャッシュデータなし",
518+
"logs.tokens.contextTotal": "アクティブコンテキスト",
518519
"logs.tokens.noCacheNote": "このプロバイダーはキャッシュトークンを報告しません",
519520
"logs.tokens.noCacheCursor": "Cursor のキャッシュ詳細は未報告",
520521
"logs.tokens.noCacheCursorNote": "Cursor はキャッシュ read/write トークン数を公開しません。これは不明という意味で、キャッシュミスの確定ではありません",

gui/src/i18n/ko.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,7 @@ export const ko: Record<TKey, string> = {
542542
"logs.tokens.cacheWrite": "캐시 생성 (w)",
543543
"logs.tokens.reasoning": "추론",
544544
"logs.tokens.noCache": "캐시 미보고",
545+
"logs.tokens.contextTotal": "활성 컨텍스트",
545546
"logs.tokens.noCacheNote": "이 프로바이더는 캐시 토큰 수치를 제공하지 않습니다",
546547
"logs.tokens.noCacheCursor": "Cursor 캐시 상세 미보고",
547548
"logs.tokens.noCacheCursorNote": "Cursor 프로토콜은 캐시 read/write 토큰 수치를 제공하지 않습니다. 캐시 미스가 확인됐다는 뜻은 아닙니다",

gui/src/i18n/ru.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,7 @@ export const ru: Record<TKey, string> = {
547547
"logs.tokens.cacheWrite": "запись кэша (w)",
548548
"logs.tokens.reasoning": "рассуждения",
549549
"logs.tokens.noCache": "нет данных кэша",
550+
"logs.tokens.contextTotal": "активный контекст",
550551
"logs.tokens.noCacheNote": "этот провайдер не сообщает данные о кэш-токенах",
551552
"logs.tokens.noCacheCursor": "детализация кэша Cursor не сообщается",
552553
"logs.tokens.noCacheCursorNote": "Cursor не передает число токенов чтения/записи кэша; это неизвестно, а не подтвержденный промах кэша",

gui/src/i18n/zh.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,7 @@ export const zh: Record<TKey, string> = {
542542
"logs.tokens.cacheWrite": "缓存写入 (w)",
543543
"logs.tokens.reasoning": "推理",
544544
"logs.tokens.noCache": "无缓存数据",
545+
"logs.tokens.contextTotal": "活动上下文",
545546
"logs.tokens.noCacheNote": "该提供商不报告缓存 token 数",
546547
"logs.tokens.noCacheCursor": "Cursor 未报告缓存明细",
547548
"logs.tokens.noCacheCursorNote": "Cursor 不提供缓存读写 token 数;这表示未知,并不代表已确认缓存未命中",

gui/src/pages/Logs.tsx

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import { speedLabel } from "./logs-speed-label";
1616
interface UsageBreakdown {
1717
inputTokens: number;
1818
outputTokens: number;
19+
/** Absolute active-context snapshot after the response (stateful providers such as Kiro). */
20+
contextTotalTokens?: number;
1921
totalTokens?: number;
2022
cachedInputTokens?: number;
2123
cacheReadInputTokens?: number;
@@ -137,6 +139,9 @@ function tokensTitle(log: LogEntry, t: TFn): string | undefined {
137139
];
138140
if (split.read !== undefined) parts.push(`${t("logs.tokens.cacheRead")}=${split.read}`);
139141
if (split.write !== undefined) parts.push(`${t("logs.tokens.cacheWrite")}=${split.write}`);
142+
if (typeof log.usage.contextTotalTokens === "number") {
143+
parts.push(`${t("logs.tokens.contextTotal")}=${log.usage.contextTotalTokens}`);
144+
}
140145
if (typeof log.usage.reasoningOutputTokens === "number") parts.push(`${t("logs.tokens.reasoning")}=${log.usage.reasoningOutputTokens}`);
141146
if (log.usageStatus === "estimated") parts.push(t("logs.tokens.estimatedNote"));
142147
if (log.usageStatus === "estimated" && split.read === undefined && split.write === undefined) {
@@ -154,6 +159,23 @@ function displayTokenTotal(log: LogEntry): number | undefined {
154159
return typeof explicitTotal === "number" ? Math.max(explicitTotal, baseTotal) : baseTotal;
155160
}
156161

162+
/**
163+
* Row/detail display total that also honors an absolute context checkpoint.
164+
*
165+
* Stateful providers (Kiro) report per-attempt usage only, so their per-request total stays
166+
* small while the real active context grows. `contextTotalTokens` is that absolute snapshot.
167+
*
168+
* NEVER SUM THIS ACROSS REQUESTS. A checkpoint is not a per-request delta: adding it up over
169+
* a conversation counts the same context once per request and inflates aggregates wildly.
170+
* Aggregate rollups must keep using `displayTokenTotal`.
171+
*/
172+
function displayContextTokenTotal(log: LogEntry): number | undefined {
173+
const base = displayTokenTotal(log);
174+
const contextTotal = log.usage?.contextTotalTokens;
175+
if (typeof contextTotal !== "number") return base;
176+
return Math.max(base ?? 0, contextTotal) || undefined;
177+
}
178+
157179
/** Cache read/write split; recovers reads from legacy rows that stored read+write combined. */
158180
function cacheSplit(log: LogEntry): { read?: number; write?: number } {
159181
const u = log.usage;
@@ -542,7 +564,7 @@ export default function Logs({ apiBase }: { apiBase: string }) {
542564
<td className="muted mono">{formatLogTimestamp(log.timestamp, localeTag)}</td>
543565
<td className="num mono log-col-tokens" title={tokensTitle(log, t)}>
544566
{(() => {
545-
const tokenTotal = displayTokenTotal(log);
567+
const tokenTotal = displayContextTokenTotal(log);
546568
const { read, write } = cacheSplit(log);
547569
return tokenTotal !== undefined
548570
? (
@@ -846,7 +868,13 @@ function LogDetailDialog({
846868
<span className="muted">{t("logs.tokens.cacheRead")}</span><span className="mono">{tokenSplit.read !== undefined ? formatTokens(tokenSplit.read, localeCode) : "\u2014"}</span>
847869
<span className="muted">{t("logs.tokens.cacheWrite")}</span><span className="mono">{tokenSplit.write !== undefined ? formatTokens(tokenSplit.write, localeCode) : "\u2014"}</span>
848870
<span className="muted">{t("logs.tokens.reasoning")}</span><span className="mono">{detail.usage?.reasoningOutputTokens !== undefined ? formatTokens(detail.usage.reasoningOutputTokens, localeCode) : "\u2014"}</span>
849-
<span className="muted">{t("logs.detail.totalTokens")}</span><span className="mono">{displayTokenTotal(detail) !== undefined ? formatTokens(displayTokenTotal(detail)!, localeCode) : "\u2014"}</span>
871+
<span className="muted">{t("logs.detail.totalTokens")}</span><span className="mono">{displayContextTokenTotal(detail) !== undefined ? formatTokens(displayContextTokenTotal(detail)!, localeCode) : "\u2014"}</span>
872+
{detail.usage?.contextTotalTokens !== undefined && (
873+
<>
874+
<span className="muted">{t("logs.tokens.contextTotal")}</span>
875+
<span className="mono">{formatTokens(detail.usage.contextTotalTokens, localeCode)}</span>
876+
</>
877+
)}
850878
</div>
851879
{detail.usageStatus === "estimated" && (
852880
<p className="log-detail-notes-line muted">{t("logs.tokens.estimatedNote")}</p>

0 commit comments

Comments
 (0)