diff --git a/packages/tui/src/component/devtools-bar.tsx b/packages/tui/src/component/devtools-bar.tsx
index 3b1fbe76ab..2212c07aa0 100644
--- a/packages/tui/src/component/devtools-bar.tsx
+++ b/packages/tui/src/component/devtools-bar.tsx
@@ -384,16 +384,18 @@ export function DevToolsBar() {
>
{turnTokens() ? "[x]" : "[ ]"} Turn token usage
-
- void config.update((draft) => {
- draft.debug = { ...draft.debug, turn_tokens: verboseTurnTokens() ? true : "verbose" }
- })
- }
- hoverBackground
- >
- {verboseTurnTokens() ? "[x]" : "[ ]"} Turn token usage (verbose)
-
+
+
+ void config.update((draft) => {
+ draft.debug = { ...draft.debug, turn_tokens: verboseTurnTokens() ? true : "verbose" }
+ })
+ }
+ hoverBackground
+ >
+ {verboseTurnTokens() ? "[x]" : "[ ]"} Turn token usage (verbose)
+
+
{(group) => (
diff --git a/packages/tui/src/routes/session/index.tsx b/packages/tui/src/routes/session/index.tsx
index bedc64e1fd..7ed72cec6f 100644
--- a/packages/tui/src/routes/session/index.tsx
+++ b/packages/tui/src/routes/session/index.tsx
@@ -1222,6 +1222,11 @@ function TurnTokenUsage(props: {
}) {
const config = useConfig()
const theme = useTheme()
+ const renderer = useRenderer()
+ // Collapsed by default: one summary line for the whole turn. Click to
+ // open the full per-step table, click again to close.
+ const [expanded, setExpanded] = createSignal(false)
+ const [hover, setHover] = createSignal(false)
const verbose = () => config.data.debug?.turn_tokens === "verbose"
const steps = createMemo(() => {
let previousCache = props.previousCache
@@ -1257,49 +1262,79 @@ function TurnTokenUsage(props: {
cached: Math.max("Cached".length, ...steps().map((item) => item.cached.toLocaleString().length)),
total: Math.max("Total".length, ...steps().map((item) => item.total.toLocaleString().length)),
}))
+ const summary = createMemo(() => {
+ const items = steps()
+ const last = items[items.length - 1]
+ return {
+ count: items.length,
+ newTokens: items.reduce((sum, item) => sum + item.newTokens, 0),
+ cached: last?.cached ?? 0,
+ total: last?.total ?? 0,
+ reuseDrops: items.filter((item) => item.reuseDrop !== undefined).length,
+ }
+ })
return (
0}>
-
-
- ◈
-
-
- Tokens
+ setHover(true)}
+ onMouseOut={() => setHover(false)}
+ onMouseUp={() => {
+ if (renderer.getSelection()?.getSelectedText()) return
+ setExpanded((value) => !value)
+ }}
+ >
+
+ {expanded() ? "- " : "+ "}
+ Tokens
+
+ : {summary().count} {summary().count === 1 ? "step" : "steps"} ·{" "}
+ {summary().newTokens.toLocaleString()} new · {summary().cached.toLocaleString()} cached ·{" "}
+ {summary().total.toLocaleString()} total
+
+ 0}>
+
+ {" "}
+ · ! {summary().reuseDrops} likely cache {summary().reuseDrops === 1 ? "bust" : "busts"}
+
+
-
-
- {"Step".padEnd(columns().step + 2)}
- {"New".padStart(columns().newTokens)}
- {" "}
- {"Cached".padStart(columns().cached)}
- {" "}
- {"Total".padStart(columns().total)}
-
-
-
- {(item) => (
-
-
- {item.finish.padEnd(columns().step + 2)}
-
- {item.newTokens.toLocaleString().padStart(columns().newTokens)}
-
- {" "}
- {item.cached.toLocaleString().padStart(columns().cached)}
- {" "}
- {item.total.toLocaleString().padStart(columns().total)}
-
-
-
-
- ! Likely cache bust: {item.reuseDrop?.toLocaleString()} fewer cached tokens than the previous step
+
+
+
+ {"Step".padEnd(columns().step + 2)}
+ {"New".padStart(columns().newTokens)}
+ {" "}
+ {"Cached".padStart(columns().cached)}
+ {" "}
+ {"Total".padStart(columns().total)}
+
+
+
+ {(item) => (
+
+
+ {item.finish.padEnd(columns().step + 2)}
+
+ {item.newTokens.toLocaleString().padStart(columns().newTokens)}
+
+ {" "}
+ {item.cached.toLocaleString().padStart(columns().cached)}
+ {" "}
+ {item.total.toLocaleString().padStart(columns().total)}
-
-
- )}
-
+
+
+
+ ! Likely cache bust: {item.reuseDrop?.toLocaleString()} fewer cached tokens than the previous step
+
+
+
+ )}
+
+
)