@foae/opencode-timings 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -11
- package/package.json +1 -1
- package/src/tui.tsx +40 -29
package/README.md
CHANGED
|
@@ -11,20 +11,25 @@ context-window pollution**.
|
|
|
11
11
|
|
|
12
12
|
```
|
|
13
13
|
Timing
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
turns
|
|
17
|
-
|
|
14
|
+
api/wall ██████░░ 78%
|
|
15
|
+
api 31s · wall 40s
|
|
16
|
+
turns 4 · avg 8s
|
|
17
|
+
slowest 19s
|
|
18
|
+
per-turn ▄█▂▂
|
|
18
19
|
```
|
|
19
20
|
|
|
21
|
+
Every row names itself, so there are no unlabeled numbers or glyphs to decode.
|
|
22
|
+
|
|
20
23
|
## Metrics
|
|
21
24
|
|
|
22
|
-
| Row
|
|
23
|
-
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
26
|
-
| `
|
|
27
|
-
| `
|
|
25
|
+
| Row | Meaning |
|
|
26
|
+
|------------|---------|
|
|
27
|
+
| `api/wall` | How much of wall-clock was actual model inference, as a bar gauge and percent. |
|
|
28
|
+
| `api` | Total assistant inference time — the sum of `time.completed − time.created` over every completed assistant message. |
|
|
29
|
+
| `wall` | Span from the first to the last message timestamp. Includes the time you spend reading/typing between turns, so `api` is always a fraction of it. |
|
|
30
|
+
| `turns` | Number of completed assistant messages, plus the average per-turn duration. |
|
|
31
|
+
| `slowest` | The single slowest assistant message. |
|
|
32
|
+
| `per-turn` | Sparkline of each recent turn's duration. |
|
|
28
33
|
|
|
29
34
|
The panel is always shown; before the first turn its values read zero.
|
|
30
35
|
|
|
@@ -44,7 +49,7 @@ belongs in `tui.json`, not `opencode.json`:
|
|
|
44
49
|
OpenCode installs the plugin and its dependencies with Bun at startup. Restart
|
|
45
50
|
OpenCode and open the session sidebar to see the `Timing` panel.
|
|
46
51
|
|
|
47
|
-
You can also pin a version, e.g. `@foae/opencode-timings@0.1.
|
|
52
|
+
You can also pin a version, e.g. `@foae/opencode-timings@0.1.2`.
|
|
48
53
|
|
|
49
54
|
## Configuration
|
|
50
55
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@foae/opencode-timings",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "OpenCode TUI sidebar panel showing per-session timing: total API/inference time, wall-clock, turns, average and slowest turn. Zero context-window pollution.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "foae",
|
package/src/tui.tsx
CHANGED
|
@@ -7,13 +7,24 @@
|
|
|
7
7
|
* state. Nothing is injected into the message stream, so there is zero
|
|
8
8
|
* context-window pollution.
|
|
9
9
|
*
|
|
10
|
+
* Fancy layout — every value names itself, all rows in the muted tone:
|
|
11
|
+
*
|
|
12
|
+
* Timing
|
|
13
|
+
* api/wall ███████░ 78% gauge: how much of wall-clock was model inference
|
|
14
|
+
* api 31s · wall 40s the two raw times behind that ratio
|
|
15
|
+
* turns 4 · avg 8s completed assistant turns and their average
|
|
16
|
+
* slowest 19s the single slowest turn
|
|
17
|
+
* per-turn ▄█▂▂ sparkline of each recent turn's duration
|
|
18
|
+
*
|
|
10
19
|
* Metrics (per session):
|
|
11
|
-
* -
|
|
12
|
-
*
|
|
13
|
-
* - wall
|
|
14
|
-
*
|
|
15
|
-
* -
|
|
16
|
-
* -
|
|
20
|
+
* - api total assistant inference time = sum of (time.completed - time.created)
|
|
21
|
+
* over every completed assistant message.
|
|
22
|
+
* - wall span from the first to the last message timestamp (includes the time
|
|
23
|
+
* you spend reading/typing between turns), so api is a fraction of it.
|
|
24
|
+
* - api/wall api's share of wall-clock, as a bar gauge and percent.
|
|
25
|
+
* - turns number of completed assistant messages, plus the average duration.
|
|
26
|
+
* - slowest the single slowest assistant message.
|
|
27
|
+
* - per-turn sparkline of recent per-turn durations.
|
|
17
28
|
*
|
|
18
29
|
* Config — pass options via the tuple form in `tui.json`:
|
|
19
30
|
*
|
|
@@ -27,7 +38,7 @@
|
|
|
27
38
|
* }]
|
|
28
39
|
* ]
|
|
29
40
|
*
|
|
30
|
-
* "fancy" draws a bar gauge for the
|
|
41
|
+
* "fancy" draws a bar gauge for the api/wall ratio and a sparkline of recent
|
|
31
42
|
* turn durations; "simple" is plain labeled rows. The panel is always shown
|
|
32
43
|
* (values read zero before the first turn).
|
|
33
44
|
*/
|
|
@@ -45,7 +56,9 @@ const SIDEBAR_ORDER = 155
|
|
|
45
56
|
// drive updates; this interval is just a low-frequency backstop.
|
|
46
57
|
const REFRESH_INTERVAL_MS = 15_000
|
|
47
58
|
|
|
48
|
-
|
|
59
|
+
// Narrower than the simple-mode rows so the "api/wall" label + gauge + percent
|
|
60
|
+
// fit one sidebar line without wrapping.
|
|
61
|
+
const BAR_WIDTH = 8
|
|
49
62
|
const SPARK_POINTS = 12
|
|
50
63
|
const SPARK_LEVELS = "▁▂▃▄▅▆▇█"
|
|
51
64
|
|
|
@@ -141,9 +154,6 @@ function sparkline(values: number[]): string {
|
|
|
141
154
|
.join("")
|
|
142
155
|
}
|
|
143
156
|
|
|
144
|
-
type Tone = "muted" | "accent"
|
|
145
|
-
type Row = { text: string; tone: Tone }
|
|
146
|
-
|
|
147
157
|
function turnsLine(t: Timing, fields: Fields): string {
|
|
148
158
|
if (fields.turns && fields.avg) return `turns ${t.turns} · avg ${fmtDuration(t.avgMs)}`
|
|
149
159
|
if (fields.turns) return `turns ${t.turns}`
|
|
@@ -151,23 +161,26 @@ function turnsLine(t: Timing, fields: Fields): string {
|
|
|
151
161
|
return ""
|
|
152
162
|
}
|
|
153
163
|
|
|
154
|
-
|
|
155
|
-
|
|
164
|
+
// Every row is a labeled string in the muted tone — no value relies on the
|
|
165
|
+
// reader inferring what an unlabeled number or glyph means.
|
|
166
|
+
function buildRows(t: Timing, mode: Mode, fields: Fields): string[] {
|
|
167
|
+
const rows: string[] = []
|
|
156
168
|
if (mode === "fancy") {
|
|
157
|
-
if (fields.api) rows.push(
|
|
158
|
-
if (fields.wall) rows.push(
|
|
169
|
+
if (fields.api) rows.push(`api/wall ${bar(t.apiPct, BAR_WIDTH)} ${t.apiPct}%`)
|
|
170
|
+
if (fields.wall) rows.push(`api ${fmtDuration(t.apiMs)} · wall ${fmtDuration(t.wallMs)}`)
|
|
159
171
|
const ta = turnsLine(t, fields)
|
|
160
|
-
if (ta) rows.push(
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
172
|
+
if (ta) rows.push(ta)
|
|
173
|
+
if (fields.slow) rows.push(`slowest ${fmtDuration(t.slowestMs)}`)
|
|
174
|
+
if (fields.sparkline) {
|
|
175
|
+
const spark = sparkline(t.durations)
|
|
176
|
+
if (spark) rows.push(`per-turn ${spark}`)
|
|
177
|
+
}
|
|
165
178
|
} else {
|
|
166
|
-
if (fields.api) rows.push(
|
|
167
|
-
if (fields.wall) rows.push(
|
|
179
|
+
if (fields.api) rows.push(`api ${fmtDuration(t.apiMs)} · ${t.apiPct}% of wall`)
|
|
180
|
+
if (fields.wall) rows.push(`wall ${fmtDuration(t.wallMs)}`)
|
|
168
181
|
const ta = turnsLine(t, fields)
|
|
169
|
-
if (ta) rows.push(
|
|
170
|
-
if (fields.slow) rows.push(
|
|
182
|
+
if (ta) rows.push(ta)
|
|
183
|
+
if (fields.slow) rows.push(`slowest ${fmtDuration(t.slowestMs)}`)
|
|
171
184
|
}
|
|
172
185
|
return rows
|
|
173
186
|
}
|
|
@@ -208,9 +221,7 @@ function SidebarTimingView(props: {
|
|
|
208
221
|
for (const unsubscribe of unsubscribers) unsubscribe()
|
|
209
222
|
})
|
|
210
223
|
|
|
211
|
-
const rows = ():
|
|
212
|
-
const toneColor = (tone: Tone) =>
|
|
213
|
-
tone === "accent" ? props.api.theme.current.accent : props.api.theme.current.textMuted
|
|
224
|
+
const rows = (): string[] => buildRows(timing(), props.mode, props.fields)
|
|
214
225
|
|
|
215
226
|
return (
|
|
216
227
|
<box gap={0}>
|
|
@@ -219,8 +230,8 @@ function SidebarTimingView(props: {
|
|
|
219
230
|
</text>
|
|
220
231
|
<box gap={0}>
|
|
221
232
|
{rows().map((row) => (
|
|
222
|
-
<text fg={
|
|
223
|
-
{row
|
|
233
|
+
<text fg={props.api.theme.current.textMuted} wrapMode="none">
|
|
234
|
+
{row || " "}
|
|
224
235
|
</text>
|
|
225
236
|
))}
|
|
226
237
|
</box>
|