@tinkink/oc-tps 1.0.0 → 1.0.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/package.json +1 -1
- package/tui.tsx +67 -31
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "@tinkink/oc-tps",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.1",
|
|
5
5
|
"description": "Temporary OpenCode 2 fork of Tarquinen/oc-tps: live TPS, average TPS, and average TTFT in the session prompt.",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
package/tui.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** @jsxImportSource @opentui/solid */
|
|
2
2
|
import { Plugin } from "@opencode/plugin/tui"
|
|
3
|
-
import {
|
|
3
|
+
import { onCleanup } from "solid-js"
|
|
4
|
+
import type { TextRenderable } from "@opentui/core"
|
|
4
5
|
|
|
5
6
|
type StreamSample = {
|
|
6
7
|
at: number
|
|
@@ -63,42 +64,63 @@ function activeDuration(samples: StreamSample[], tailAt?: number) {
|
|
|
63
64
|
return Math.max(duration, SINGLE_SAMPLE_MS)
|
|
64
65
|
}
|
|
65
66
|
|
|
67
|
+
function statusText(context: Plugin.Context, sessionID: string, tracker: Tracker) {
|
|
68
|
+
const totals = tracker.averages[sessionID]
|
|
69
|
+
const average = totals ? formatRate(totals.totalTokens / (totals.totalDurationMs / 1_000)) : undefined
|
|
70
|
+
const ttft = totals?.stepCount ? formatTtft(totals.totalTtftMs / totals.stepCount / 1_000) : undefined
|
|
71
|
+
|
|
72
|
+
let live: string | undefined
|
|
73
|
+
if (context.data.session.status(sessionID) === "running") {
|
|
74
|
+
const now = Date.now()
|
|
75
|
+
const samples = (tracker.samples[sessionID] ?? []).filter((sample) => now - sample.at <= STREAM_WINDOW_MS)
|
|
76
|
+
const last = samples[samples.length - 1]
|
|
77
|
+
if (last && now - last.at <= LIVE_STALE_MS) {
|
|
78
|
+
const tokens = samples.reduce((sum, sample) => sum + sample.tokens, 0)
|
|
79
|
+
live = formatRate(tokens / (activeDuration(samples, now) / 1_000))
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return `TPS ${live ?? "-"} | AVG ${average ?? "-"} | TTFT ${ttft ?? "-"}`
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Renders into `prompt.footer` by writing the text renderable directly.
|
|
88
|
+
*
|
|
89
|
+
* A published CLI plugin is installed under `node_modules`, where OpenCode does
|
|
90
|
+
* not inject its own `solid-js`/`@opentui/solid` runtime. The plugin therefore
|
|
91
|
+
* runs against its own Solid copy, so signal/memo updates never reach the host
|
|
92
|
+
* render loop and the footer would stay frozen at its first value. Driving the
|
|
93
|
+
* renderable and calling `requestRender()` keeps the display live regardless of
|
|
94
|
+
* which Solid instance the plugin was loaded with.
|
|
95
|
+
*/
|
|
66
96
|
function Status(props: {
|
|
67
97
|
context: Plugin.Context
|
|
68
98
|
sessionID: string
|
|
69
99
|
tracker: Tracker
|
|
70
|
-
|
|
100
|
+
register: (paint: () => void) => () => void
|
|
71
101
|
}) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
? formatTtft(totals.totalTtftMs / totals.stepCount / 1_000)
|
|
80
|
-
: undefined
|
|
81
|
-
|
|
82
|
-
let live: string | undefined
|
|
83
|
-
if (props.context.data.session.status(props.sessionID) === "running") {
|
|
84
|
-
const now = Date.now()
|
|
85
|
-
const samples = (props.tracker.samples[props.sessionID] ?? []).filter(
|
|
86
|
-
(sample) => now - sample.at <= STREAM_WINDOW_MS,
|
|
87
|
-
)
|
|
88
|
-
const last = samples[samples.length - 1]
|
|
89
|
-
if (last && now - last.at <= LIVE_STALE_MS) {
|
|
90
|
-
const tokens = samples.reduce((sum, sample) => sum + sample.tokens, 0)
|
|
91
|
-
live = formatRate(tokens / (activeDuration(samples, now) / 1_000))
|
|
92
|
-
}
|
|
93
|
-
}
|
|
102
|
+
let text: TextRenderable | undefined
|
|
103
|
+
|
|
104
|
+
const paint = () => {
|
|
105
|
+
if (!text) return
|
|
106
|
+
text.content = statusText(props.context, props.sessionID, props.tracker)
|
|
107
|
+
props.context.renderer.requestRender()
|
|
108
|
+
}
|
|
94
109
|
|
|
95
|
-
|
|
96
|
-
|
|
110
|
+
const unregister = props.register(paint)
|
|
111
|
+
onCleanup(unregister)
|
|
97
112
|
|
|
98
113
|
return (
|
|
99
114
|
<box position="absolute" right={2} bottom={2} height={1} flexDirection="row">
|
|
100
|
-
<text
|
|
101
|
-
{
|
|
115
|
+
<text
|
|
116
|
+
ref={(element) => {
|
|
117
|
+
text = element as unknown as TextRenderable
|
|
118
|
+
paint()
|
|
119
|
+
}}
|
|
120
|
+
fg={props.context.theme.text.muted}
|
|
121
|
+
flexShrink={0}
|
|
122
|
+
>
|
|
123
|
+
{statusText(props.context, props.sessionID, props.tracker)}
|
|
102
124
|
</text>
|
|
103
125
|
</box>
|
|
104
126
|
)
|
|
@@ -113,8 +135,15 @@ export default Plugin.define({
|
|
|
113
135
|
timings: {},
|
|
114
136
|
averages: {},
|
|
115
137
|
}
|
|
116
|
-
|
|
117
|
-
|
|
138
|
+
|
|
139
|
+
let painter: (() => void) | undefined
|
|
140
|
+
const bump = () => painter?.()
|
|
141
|
+
const register = (paint: () => void) => {
|
|
142
|
+
painter = paint
|
|
143
|
+
return () => {
|
|
144
|
+
if (painter === paint) painter = undefined
|
|
145
|
+
}
|
|
146
|
+
}
|
|
118
147
|
|
|
119
148
|
const clearLive = (sessionID: string) => {
|
|
120
149
|
if (!tracker.samples[sessionID]) return
|
|
@@ -221,7 +250,14 @@ export default Plugin.define({
|
|
|
221
250
|
append: "prompt.footer",
|
|
222
251
|
render: (props) => {
|
|
223
252
|
if (!props.sessionID || props.mode !== "normal") return null
|
|
224
|
-
return
|
|
253
|
+
return (
|
|
254
|
+
<Status
|
|
255
|
+
context={context}
|
|
256
|
+
sessionID={props.sessionID}
|
|
257
|
+
tracker={tracker}
|
|
258
|
+
register={register}
|
|
259
|
+
/>
|
|
260
|
+
)
|
|
225
261
|
},
|
|
226
262
|
})
|
|
227
263
|
|