opencode2-tps 0.2.1-rc.1 → 0.3.0-rc.1
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 +14 -6
- package/dist/tui.js +24 -2
- package/package.json +6 -7
package/README.md
CHANGED
|
@@ -12,9 +12,15 @@ When OpenCode reports terminal usage, the token count becomes exact while TPS re
|
|
|
12
12
|
|
|
13
13
|
## Install
|
|
14
14
|
|
|
15
|
-
Built against the OpenCode 2 preview. The earliest known compatible beta is `0.0.0-beta-17595`; the latest tested beta is `0.0.0-beta-
|
|
15
|
+
Built against the OpenCode 2 preview. The earliest known compatible beta is `0.0.0-beta-17595`; the latest tested beta is `0.0.0-beta-19381`. The TUI plugin API is still moving, so a much newer or older build may drop the indicator without an error. If the figure never appears, check your version first.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Install it with the CLI's plugin command, which adds the entry to `~/.config/opencode/cli.json` for you:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
opencode2 plugin add opencode2-tps
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or add the package to `~/.config/opencode/cli.json` yourself:
|
|
18
24
|
|
|
19
25
|
```json
|
|
20
26
|
{
|
|
@@ -35,12 +41,12 @@ To set options, use the object form:
|
|
|
35
41
|
}
|
|
36
42
|
```
|
|
37
43
|
|
|
38
|
-
A running TUI picks up `cli.json` changes immediately. On first use it installs the package into its own cache
|
|
44
|
+
A running TUI picks up `cli.json` changes immediately. On first use it installs the package into its own cache under `~/.cache/opencode/npm/` on Linux — one millisecond-timestamped generation per spec, for example `opencode2-tps@latest/<generation>/`, with the newest generation winning.
|
|
39
45
|
|
|
40
|
-
|
|
46
|
+
The host reuses the newest installed generation without contacting the registry, so a restart alone may not pick up a new release. To upgrade, delete the spec's cache directory and restart:
|
|
41
47
|
|
|
42
48
|
```sh
|
|
43
|
-
rm -rf ~/.cache/opencode/
|
|
49
|
+
rm -rf ~/.cache/opencode/npm/opencode2-tps@latest
|
|
44
50
|
```
|
|
45
51
|
|
|
46
52
|
To pin a version instead, put the range in the entry — `"opencode2-tps@0.1.0"`. Every distinct entry gets its own cache directory.
|
|
@@ -67,7 +73,9 @@ The defaults are usable as they are. For the full option list, the ranges and mo
|
|
|
67
73
|
|
|
68
74
|
While output streams, the plugin estimates tokens from observable UTF-8 bytes at a default of 4.75 bytes per token and calculates a bounded rolling delivery rate. Complete text, reasoning, and tool-input events reconcile buffered or missed deltas without creating artificial live-rate spikes.
|
|
69
75
|
|
|
70
|
-
At the end of each model step, OpenCode's reported output and reasoning usage replaces the byte estimate. Settled TPS divides those exact tokens by observed step spans ending at the final model-content boundary
|
|
76
|
+
At the end of each model step, OpenCode's reported output and reasoning usage replaces the byte estimate. Settled TPS divides those exact tokens by observed step spans ending at `session.step.streamed`, the host's authoritative end of the model stream, published before local tools join. Hosts that do not publish the event fall back to the final model-content boundary. Either way, local tool execution and time between model calls are excluded.
|
|
77
|
+
|
|
78
|
+
OpenCode's built-in assistant-footer t/s divides visible output tokens by the same step spans, leaving hidden reasoning out of its numerator. This plugin counts output plus reasoning, so on reasoning models its settled figure reads higher than the built-in one — those tokens were generated too.
|
|
71
79
|
|
|
72
80
|
TPS is always approximate (`~`) because OpenCode does not expose token-level timestamps. Proprietary reasoning may be encrypted or represented only by a short summary, and some providers buffer tool arguments until completion. During those opaque intervals the live rate holds or becomes unavailable instead of continuously falling. Opaque provider state is never counted by byte length.
|
|
73
81
|
|
package/dist/tui.js
CHANGED
|
@@ -176,6 +176,7 @@ export class TpsTracker {
|
|
|
176
176
|
const step = {
|
|
177
177
|
assistantMessageID,
|
|
178
178
|
startedAt: now,
|
|
179
|
+
streamedAt: null,
|
|
179
180
|
lastBoundaryAt: null,
|
|
180
181
|
observableBytes: 0,
|
|
181
182
|
blocks: new Map(),
|
|
@@ -242,6 +243,19 @@ export class TpsTracker {
|
|
|
242
243
|
step.observableBytes += block.finalBytes - block.streamedBytes;
|
|
243
244
|
step.lastBoundaryAt = Math.max(step.lastBoundaryAt ?? now, now);
|
|
244
245
|
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* The host's authoritative end of the model stream, published after the
|
|
249
|
+
* provider stream exits and before local tools join. Assigned rather than
|
|
250
|
+
* maxed so a retried attempt reusing the message ID moves the boundary to its
|
|
251
|
+
* own completion.
|
|
252
|
+
*/
|
|
253
|
+
markStreamed(sessionID, assistantMessageID, now) {
|
|
254
|
+
const st = this.runs.get(sessionID);
|
|
255
|
+
const step = st?.activeStep;
|
|
256
|
+
if (!step || step.assistantMessageID !== assistantMessageID) return;
|
|
257
|
+
step.streamedAt = now;
|
|
258
|
+
}
|
|
245
259
|
settleActiveStep(st, generatedTokens) {
|
|
246
260
|
const step = st.activeStep;
|
|
247
261
|
if (!step) return;
|
|
@@ -251,7 +265,10 @@ export class TpsTracker {
|
|
|
251
265
|
st.tokensEstimated = true;
|
|
252
266
|
st.partial = true;
|
|
253
267
|
}
|
|
254
|
-
|
|
268
|
+
// `session.step.streamed` is the exact stream end; the last content boundary
|
|
269
|
+
// remains the fallback for hosts that do not publish it.
|
|
270
|
+
const end = step.streamedAt ?? step.lastBoundaryAt;
|
|
271
|
+
if (end !== null) st.settledDurationMs += Math.max(0, end - step.startedAt);
|
|
255
272
|
st.settledSteps.add(step.assistantMessageID);
|
|
256
273
|
st.activeStep = null;
|
|
257
274
|
}
|
|
@@ -478,6 +495,11 @@ const definition = {
|
|
|
478
495
|
tracker.beginStep(e.data.sessionID, e.data.assistantMessageID, e.created);
|
|
479
496
|
touch();
|
|
480
497
|
};
|
|
498
|
+
const onStepStreamed = e => {
|
|
499
|
+
if (!isActive() || !isNewEvent(e)) return;
|
|
500
|
+
tracker.markStreamed(e.data.sessionID, e.data.assistantMessageID, e.created);
|
|
501
|
+
touch();
|
|
502
|
+
};
|
|
481
503
|
const onStepFinished = e => {
|
|
482
504
|
if (!isActive() || !isNewEvent(e)) return;
|
|
483
505
|
const tokens = e.data.tokens;
|
|
@@ -489,7 +511,7 @@ const definition = {
|
|
|
489
511
|
if (!isActive() || !isNewEvent(e)) return;
|
|
490
512
|
tracker.beginRun(e.data.sessionID);
|
|
491
513
|
touch();
|
|
492
|
-
}), ctx.data.on("session.text.delta", onDelta), ctx.data.on("session.reasoning.delta", onDelta), ctx.data.on("session.tool.input.delta", onDelta), ctx.data.on("session.text.started", onBlockStarted), ctx.data.on("session.reasoning.started", onBlockStarted), ctx.data.on("session.tool.input.started", onBlockStarted), ctx.data.on("session.text.ended", onBlockEnded), ctx.data.on("session.reasoning.ended", onBlockEnded), ctx.data.on("session.tool.input.ended", onBlockEnded), ctx.data.on("session.step.started", onStepStarted), ctx.data.on("session.step.ended", onStepFinished), ctx.data.on("session.step.failed", onStepFinished), ctx.data.on("session.execution.succeeded", onFinish), ctx.data.on("session.execution.failed", onFinish), ctx.data.on("session.execution.interrupted", onFinish), ctx.data.on("session.idle", onFinish), ctx.data.on("session.deleted", e => {
|
|
514
|
+
}), ctx.data.on("session.text.delta", onDelta), ctx.data.on("session.reasoning.delta", onDelta), ctx.data.on("session.tool.input.delta", onDelta), ctx.data.on("session.text.started", onBlockStarted), ctx.data.on("session.reasoning.started", onBlockStarted), ctx.data.on("session.tool.input.started", onBlockStarted), ctx.data.on("session.text.ended", onBlockEnded), ctx.data.on("session.reasoning.ended", onBlockEnded), ctx.data.on("session.tool.input.ended", onBlockEnded), ctx.data.on("session.step.started", onStepStarted), ctx.data.on("session.step.streamed", onStepStreamed), ctx.data.on("session.step.ended", onStepFinished), ctx.data.on("session.step.failed", onStepFinished), ctx.data.on("session.execution.succeeded", onFinish), ctx.data.on("session.execution.failed", onFinish), ctx.data.on("session.execution.interrupted", onFinish), ctx.data.on("session.idle", onFinish), ctx.data.on("session.deleted", e => {
|
|
493
515
|
if (!isActive() || !isNewEvent(e)) return;
|
|
494
516
|
tracker.evict(e.data.sessionID);
|
|
495
517
|
touch();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode2-tps",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0-rc.1",
|
|
4
4
|
"description": "Live token-throughput indicator for the OpenCode 2 TUI prompt composer",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "P-Theo",
|
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
"throughput"
|
|
22
22
|
],
|
|
23
23
|
"exports": {
|
|
24
|
-
".": "./dist/tui.js",
|
|
25
24
|
"./tui": "./dist/tui.js"
|
|
26
25
|
},
|
|
27
26
|
"files": [
|
|
@@ -47,11 +46,11 @@
|
|
|
47
46
|
"devDependencies": {
|
|
48
47
|
"@babel/core": "^7.29.7",
|
|
49
48
|
"@babel/preset-typescript": "^7.29.7",
|
|
50
|
-
"@opencode
|
|
51
|
-
"@opencode
|
|
52
|
-
"@opencode
|
|
53
|
-
"@opentui/core": "^0.5.
|
|
54
|
-
"@opentui/solid": "^0.5.
|
|
49
|
+
"@opencode/cli": "0.0.0-beta-19381",
|
|
50
|
+
"@opencode/plugin": "0.0.0-beta-19381",
|
|
51
|
+
"@opencode/theme": "0.0.0-beta-19381",
|
|
52
|
+
"@opentui/core": "^0.5.10",
|
|
53
|
+
"@opentui/solid": "^0.5.10",
|
|
55
54
|
"@oxlint/plugins": "^1.78.0",
|
|
56
55
|
"@types/bun": "^1.3.14",
|
|
57
56
|
"@types/node": "^24.0.0",
|