Band showing avg time to first token and output tokens/s

Shows how fast the model answers in the current session.
LLM speed ┃ TTFT 2.17s ┃ Throughput 195.2 tok/s ┃ Requests 3
| Cell | Meaning |
|---|---|
TTFT | Average time to first token (text, thinking or tool call) over all requests. |
Throughput | Output tokens divided by generation time (first token to end of response), over all requests. |
Requests | Number of model requests counted. A tool-use loop makes several per turn. |
- until the first request completes.hooks/register.tsx 81 lines1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { Speed } from '../types'
5
6const speed = atom(
7 { plugin: 'llm-speed', key: 'speed' } as const,
8 { steps: 0, ttftMs: 0, tokens: 0, genMs: 0 } as Speed,
9)
10
11export const register: Register = on => {
12 on('turn.step', async function* ($, e, next) {
13 const start = await $.clock.now()
14 let first = 0
15
16 const result = yield* (async function* () {
17 const stream = next(e)
18 for await (const c of stream) {
19 if (!first && (c.kind === 'text' || c.kind === 'thinking' || c.kind === 'tool')) {
20 first = await $.clock.now()
21 }
22 yield c
23 }
24 return stream.result
25 })()
26
27 // ponytail: steps without usage or a first token are skipped
28 const tokens = result?.usage?.output_tokens ?? 0
29 if (first && tokens) {
30 const end = await $.clock.now()
31 await update($, speed, s => ({
32 steps: s.steps + 1,
33 ttftMs: s.ttftMs + (first - start),
34 tokens: s.tokens + tokens,
35 genMs: s.genMs + (end - first),
36 }))
37 $.ui.invalidate('ui.render')
38 }
39 return result
40 })
41
42 // stacked above the usage bar (SessionMode, bottom right)
43 on('ui.render', { component: 'SessionMode' }, async ($, e, next) => {
44 const s = await read($, speed)
45 const other = await next(e)
46 const { Box, Text } = $.ui.resolve(e)
47 const line = (
48 <Box>
49 <Box width={11}><Text bold color="#7dcfff">LLM speed</Text></Box>
50 <Box>
51 <Text color="#565f89"> ┃ </Text>
52 <Box width={24}>
53 <Text dimColor>TTFT </Text>
54 <Text bold color={s.steps ? '#c0caf5' : '#565f89'}>{s.steps ? `${(s.ttftMs / s.steps / 1000).toFixed(2)}s` : '-'}</Text>
55 </Box>
56 </Box>
57 <Box>
58 <Text color="#565f89"> ┃ </Text>
59 <Box width={24}>
60 <Text dimColor>Throughput </Text>
61 <Text bold color={s.genMs ? '#c0caf5' : '#565f89'}>{s.genMs ? `${(s.tokens / (s.genMs / 1000)).toFixed(1)} tok/s` : '-'}</Text>
62 </Box>
63 </Box>
64 <Box>
65 <Text color="#565f89"> ┃ </Text>
66 <Box>
67 <Text dimColor>Requests </Text>
68 <Text bold color={s.steps ? '#c0caf5' : '#565f89'}>{s.steps || '-'}</Text>
69 </Box>
70 </Box>
71 </Box>
72 )
73 return other.type === 'engine' ? line : (
74 <Box flexDirection="column" alignItems="flex-start">
75 {other}
76 {line}
77 </Box>
78 )
79 })
80}
81types/index.d.ts 8 lines1export type Speed = { steps: number; ttftMs: number; tokens: number; genMs: number }
2
3declare module 'claude-code' {
4 interface PluginState {
5 'llm-speed': { speed: Speed }
6 }
7}
8