opencode-plugin-context 1.1.2 → 1.2.0
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 +21 -1
- package/dist/tui.js +173 -144
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -14,9 +14,10 @@ Context
|
|
|
14
14
|
━━━━━━━━━━━━━━━━━ 69%
|
|
15
15
|
138K / 200K tokens
|
|
16
16
|
$0.04 spent
|
|
17
|
-
|
|
17
|
+
62.4 TPS · avg 48.1 · 23s
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
+
|
|
20
21
|
One color-coded legend row follows the bar — `▍` marker in the segment's color,
|
|
21
22
|
then a muted letter + count. Colors follow the active theme:
|
|
22
23
|
|
|
@@ -66,6 +67,25 @@ always reaches full width. A very small segment may not fill a single bar cell
|
|
|
66
67
|
always visible in the legend. Percent is colored like the usage plugin: green
|
|
67
68
|
`<50%`, amber `50–74%`, orange `75–99%`, red `100%`.
|
|
68
69
|
|
|
70
|
+
## Throughput (TPS)
|
|
71
|
+
|
|
72
|
+
While the assistant streams, a live tokens-per-second line is appended below the
|
|
73
|
+
totals:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
62.4 TPS · avg 48.1 · 23s
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
- **instant** — smoothed rate over a 1s rolling window, colored by speed: red
|
|
80
|
+
`<10`, amber `10–49`, green `≥50`. Shown only while tokens are arriving.
|
|
81
|
+
- **avg** — tokens / active generation span (first → last token), so idle time
|
|
82
|
+
doesn't drag it down. Shown once the stream goes quiet.
|
|
83
|
+
- **elapsed** — active generation span.
|
|
84
|
+
|
|
85
|
+
Counts come from `session.text.delta` / `session.reasoning.delta`, estimated with
|
|
86
|
+
the same chars/4 heuristic used elsewhere (sub-token deltas are carried over), so
|
|
87
|
+
it tracks the live stream rather than the provider's final totals.
|
|
88
|
+
|
|
69
89
|
## Configuration
|
|
70
90
|
|
|
71
91
|
All options are optional. Plugin entry in `tui.json`:
|
package/dist/tui.js
CHANGED
|
@@ -21,27 +21,7 @@ function num(v) {
|
|
|
21
21
|
return typeof v === "number" && Number.isFinite(v) && v > 0 ? Math.round(v) : 0;
|
|
22
22
|
}
|
|
23
23
|
function estimateTokens(input) {
|
|
24
|
-
return Math.max(0, Math.
|
|
25
|
-
}
|
|
26
|
-
function formatCompactTokens(tokens) {
|
|
27
|
-
if (tokens <= 0) return "0";
|
|
28
|
-
const k = Math.round(tokens / 1e3);
|
|
29
|
-
return `${Math.max(1, k)}k`;
|
|
30
|
-
}
|
|
31
|
-
function scaleToPrompt(raw, prompt) {
|
|
32
|
-
const estimated = raw.system + raw.user + raw.assistant + raw.tool;
|
|
33
|
-
if (estimated <= prompt) {
|
|
34
|
-
return { ...raw, other: prompt - estimated };
|
|
35
|
-
}
|
|
36
|
-
const scale = prompt / estimated;
|
|
37
|
-
const scaled = {
|
|
38
|
-
system: Math.floor(raw.system * scale),
|
|
39
|
-
user: Math.floor(raw.user * scale),
|
|
40
|
-
assistant: Math.floor(raw.assistant * scale),
|
|
41
|
-
tool: Math.floor(raw.tool * scale)
|
|
42
|
-
};
|
|
43
|
-
const total = scaled.system + scaled.user + scaled.assistant + scaled.tool;
|
|
44
|
-
return { ...scaled, other: Math.max(0, prompt - total) };
|
|
24
|
+
return Math.max(0, Math.round(input.length / 4));
|
|
45
25
|
}
|
|
46
26
|
function computeContext(counts, limits, cost = 0, estimates, exclude = []) {
|
|
47
27
|
const { input, output, reasoning, cacheRead, cacheWrite } = counts;
|
|
@@ -50,66 +30,105 @@ function computeContext(counts, limits, cost = 0, estimates, exclude = []) {
|
|
|
50
30
|
const reserved = limits && limits.output > 0 ? Math.max(0, limits.output - output) : 0;
|
|
51
31
|
const free = window > 0 ? Math.max(0, window - used - reserved) : 0;
|
|
52
32
|
const prompt = input + cacheWrite;
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
{ id: "cached", tokens: cacheRead },
|
|
71
|
-
{ id: "system", tokens: scaled.system },
|
|
72
|
-
{ id: "user", tokens: scaled.user },
|
|
73
|
-
{ id: "assistant", tokens: scaled.assistant },
|
|
74
|
-
{ id: "tool", tokens: scaled.tool },
|
|
75
|
-
{ id: "other", tokens: scaled.other },
|
|
76
|
-
{ id: "think", tokens: reasoning },
|
|
77
|
-
{ id: "out", tokens: output },
|
|
78
|
-
{ id: "reserved", tokens: reserved },
|
|
79
|
-
{ id: "free", tokens: free }
|
|
80
|
-
];
|
|
81
|
-
} else {
|
|
82
|
-
raw = [
|
|
83
|
-
{ id: "cached", tokens: cacheRead },
|
|
84
|
-
{ id: "user", tokens: estimates.user },
|
|
85
|
-
{ id: "tools", tokens: estimates.tools },
|
|
86
|
-
{ id: "system", tokens: Math.max(0, prompt - estimates.user - estimates.tools) },
|
|
87
|
-
{ id: "think", tokens: reasoning },
|
|
88
|
-
{ id: "out", tokens: output },
|
|
89
|
-
{ id: "reserved", tokens: reserved },
|
|
90
|
-
{ id: "free", tokens: free }
|
|
91
|
-
];
|
|
92
|
-
}
|
|
93
|
-
} else {
|
|
94
|
-
raw = [
|
|
95
|
-
{ id: "cached", tokens: cacheRead },
|
|
96
|
-
{ id: "prompt", tokens: prompt },
|
|
97
|
-
{ id: "think", tokens: reasoning },
|
|
98
|
-
{ id: "out", tokens: output },
|
|
99
|
-
{ id: "reserved", tokens: reserved },
|
|
100
|
-
{ id: "free", tokens: free }
|
|
101
|
-
];
|
|
102
|
-
}
|
|
103
|
-
const filtered = raw.filter((segment) => segment.tokens > 0 && !excluded(segment.id));
|
|
33
|
+
const raw = estimates ? [
|
|
34
|
+
{ id: "cached", tokens: cacheRead },
|
|
35
|
+
{ id: "user", tokens: estimates.user },
|
|
36
|
+
{ id: "tools", tokens: estimates.tools },
|
|
37
|
+
{ id: "system", tokens: Math.max(0, prompt - estimates.user - estimates.tools) },
|
|
38
|
+
{ id: "think", tokens: reasoning },
|
|
39
|
+
{ id: "out", tokens: output },
|
|
40
|
+
{ id: "reserved", tokens: reserved },
|
|
41
|
+
{ id: "free", tokens: free }
|
|
42
|
+
] : [
|
|
43
|
+
{ id: "cached", tokens: cacheRead },
|
|
44
|
+
{ id: "prompt", tokens: prompt },
|
|
45
|
+
{ id: "think", tokens: reasoning },
|
|
46
|
+
{ id: "out", tokens: output },
|
|
47
|
+
{ id: "reserved", tokens: reserved },
|
|
48
|
+
{ id: "free", tokens: free }
|
|
49
|
+
];
|
|
104
50
|
return {
|
|
105
51
|
used,
|
|
106
52
|
window,
|
|
107
53
|
percent: window > 0 ? Math.min(100, Math.round(used / window * 100)) : 0,
|
|
108
|
-
segments:
|
|
54
|
+
segments: raw.filter((segment) => segment.tokens > 0 && !exclude.includes(segment.id)),
|
|
109
55
|
cost,
|
|
110
56
|
known: window > 0
|
|
111
57
|
};
|
|
112
58
|
}
|
|
59
|
+
var MIN_WINDOW_SECONDS = 0.3;
|
|
60
|
+
var MAX_INITIAL_TPS = 100;
|
|
61
|
+
function createTpsTracker(options = {}) {
|
|
62
|
+
const windowMs = options.windowMs && options.windowMs > 0 ? options.windowMs : 1e3;
|
|
63
|
+
const halfLifeMs = options.halfLifeMs && options.halfLifeMs > 0 ? options.halfLifeMs : 250;
|
|
64
|
+
let samples = [];
|
|
65
|
+
let total = 0;
|
|
66
|
+
let start = -1;
|
|
67
|
+
let last = -1;
|
|
68
|
+
let smoothed = 0;
|
|
69
|
+
let smoothedAt = 0;
|
|
70
|
+
let hasSmoothed = false;
|
|
71
|
+
function raw(now) {
|
|
72
|
+
if (samples.length === 0) return 0;
|
|
73
|
+
const cutoff = now - windowMs;
|
|
74
|
+
let tokens = 0;
|
|
75
|
+
let oldest = now;
|
|
76
|
+
for (const sample of samples) {
|
|
77
|
+
if (sample.t >= cutoff) {
|
|
78
|
+
tokens += sample.count;
|
|
79
|
+
if (sample.t < oldest) oldest = sample.t;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (tokens === 0) return 0;
|
|
83
|
+
return tokens / Math.max((now - oldest) / 1e3, MIN_WINDOW_SECONDS);
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
record(count, timestamp) {
|
|
87
|
+
if (!(count > 0)) return;
|
|
88
|
+
const t = timestamp ?? Date.now();
|
|
89
|
+
if (start < 0) start = t;
|
|
90
|
+
if (t > last) last = t;
|
|
91
|
+
total += count;
|
|
92
|
+
samples.push({ t, count });
|
|
93
|
+
const cutoff = t - windowMs;
|
|
94
|
+
let drop = 0;
|
|
95
|
+
while (drop < samples.length && samples[drop].t < cutoff) drop++;
|
|
96
|
+
if (drop > 0) samples = samples.slice(drop);
|
|
97
|
+
const value = raw(t);
|
|
98
|
+
if (!hasSmoothed) {
|
|
99
|
+
smoothed = Math.min(value, MAX_INITIAL_TPS);
|
|
100
|
+
hasSmoothed = true;
|
|
101
|
+
} else {
|
|
102
|
+
const dt = Math.max(1, t - smoothedAt);
|
|
103
|
+
const alpha = Math.exp(-Math.LN2 * dt / halfLifeMs);
|
|
104
|
+
smoothed = alpha * smoothed + (1 - alpha) * value;
|
|
105
|
+
}
|
|
106
|
+
smoothedAt = t;
|
|
107
|
+
},
|
|
108
|
+
instant() {
|
|
109
|
+
if (!hasSmoothed || Date.now() - last > windowMs) return 0;
|
|
110
|
+
return smoothed;
|
|
111
|
+
},
|
|
112
|
+
average() {
|
|
113
|
+
return start < 0 ? 0 : total / Math.max((last - start) / 1e3, MIN_WINDOW_SECONDS);
|
|
114
|
+
},
|
|
115
|
+
total() {
|
|
116
|
+
return total;
|
|
117
|
+
},
|
|
118
|
+
elapsed() {
|
|
119
|
+
return start < 0 ? 0 : last - start;
|
|
120
|
+
},
|
|
121
|
+
reset() {
|
|
122
|
+
samples = [];
|
|
123
|
+
total = 0;
|
|
124
|
+
start = -1;
|
|
125
|
+
last = -1;
|
|
126
|
+
smoothed = 0;
|
|
127
|
+
smoothedAt = 0;
|
|
128
|
+
hasSmoothed = false;
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
113
132
|
function segmentBar(segments, window, width, exclude = []) {
|
|
114
133
|
if (window <= 0 || width <= 0) return [];
|
|
115
134
|
let remaining = width;
|
|
@@ -130,11 +149,8 @@ var VALID_SEGMENT_IDS = [
|
|
|
130
149
|
"cached",
|
|
131
150
|
"user",
|
|
132
151
|
"tools",
|
|
133
|
-
"tool",
|
|
134
152
|
"system",
|
|
135
153
|
"prompt",
|
|
136
|
-
"assistant",
|
|
137
|
-
"other",
|
|
138
154
|
"think",
|
|
139
155
|
"out",
|
|
140
156
|
"reserved",
|
|
@@ -155,11 +171,8 @@ var SEGMENT_LABEL = {
|
|
|
155
171
|
cached: "c",
|
|
156
172
|
user: "u",
|
|
157
173
|
tools: "m",
|
|
158
|
-
tool: "m",
|
|
159
174
|
system: "s",
|
|
160
175
|
prompt: "p",
|
|
161
|
-
assistant: "a",
|
|
162
|
-
other: "x",
|
|
163
176
|
think: "t",
|
|
164
177
|
out: "o",
|
|
165
178
|
reserved: "r",
|
|
@@ -167,7 +180,7 @@ var SEGMENT_LABEL = {
|
|
|
167
180
|
};
|
|
168
181
|
var money = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" });
|
|
169
182
|
var intFmt = new Intl.NumberFormat("en-US");
|
|
170
|
-
var
|
|
183
|
+
var compactFmt = new Intl.NumberFormat("en", { notation: "compact", maximumFractionDigits: 1 });
|
|
171
184
|
var plugin = {
|
|
172
185
|
id: "opencode-plugin-context",
|
|
173
186
|
tui: async (api, rawOptions) => {
|
|
@@ -177,82 +190,84 @@ var plugin = {
|
|
|
177
190
|
setRenderTick((n) => n + 1);
|
|
178
191
|
api.renderer.requestRender();
|
|
179
192
|
};
|
|
193
|
+
const trackers = /* @__PURE__ */ new Map();
|
|
194
|
+
const carries = /* @__PURE__ */ new Map();
|
|
195
|
+
const trackerFor = (sessionId) => {
|
|
196
|
+
let tracker = trackers.get(sessionId);
|
|
197
|
+
if (!tracker) {
|
|
198
|
+
tracker = createTpsTracker();
|
|
199
|
+
trackers.set(sessionId, tracker);
|
|
200
|
+
}
|
|
201
|
+
return tracker;
|
|
202
|
+
};
|
|
203
|
+
const onDelta = (sessionId, text, at) => {
|
|
204
|
+
const carry = (carries.get(sessionId) ?? 0) + text.length;
|
|
205
|
+
const tokens = Math.floor(carry / 4);
|
|
206
|
+
carries.set(sessionId, carry - tokens * 4);
|
|
207
|
+
if (tokens > 0) trackerFor(sessionId).record(tokens, at);
|
|
208
|
+
throttledRepaint();
|
|
209
|
+
};
|
|
210
|
+
let lastRepaint = 0;
|
|
211
|
+
let pendingRepaint;
|
|
212
|
+
const throttledRepaint = () => {
|
|
213
|
+
const wait = lastRepaint + 50 - Date.now();
|
|
214
|
+
if (wait <= 0) {
|
|
215
|
+
lastRepaint = Date.now();
|
|
216
|
+
repaint();
|
|
217
|
+
} else if (pendingRepaint === void 0) {
|
|
218
|
+
pendingRepaint = setTimeout(() => {
|
|
219
|
+
pendingRepaint = void 0;
|
|
220
|
+
lastRepaint = Date.now();
|
|
221
|
+
repaint();
|
|
222
|
+
}, wait);
|
|
223
|
+
}
|
|
224
|
+
};
|
|
180
225
|
const unsubs = [
|
|
181
|
-
api.event.on("
|
|
182
|
-
api.event.on("message.part.updated", repaint),
|
|
183
|
-
api.event.on("message.part.removed", repaint),
|
|
184
|
-
api.event.on("message.removed", repaint),
|
|
185
|
-
api.event.on("session.updated", repaint),
|
|
186
|
-
api.event.on("session.compacted", repaint),
|
|
226
|
+
api.event.on("session.created", repaint),
|
|
187
227
|
api.event.on("session.status", repaint),
|
|
188
|
-
api.event.on("session.idle", repaint)
|
|
228
|
+
api.event.on("session.idle", repaint),
|
|
229
|
+
api.event.on("session.text.delta", (event) => onDelta(event.data.sessionID, event.data.delta, event.created)),
|
|
230
|
+
api.event.on("session.reasoning.delta", (event) => onDelta(event.data.sessionID, event.data.delta, event.created))
|
|
189
231
|
];
|
|
190
232
|
const repaintTimer = setInterval(repaint, 2e3);
|
|
191
233
|
api.lifecycle.onDispose(() => {
|
|
192
234
|
for (const unsub of unsubs) unsub();
|
|
193
235
|
clearInterval(repaintTimer);
|
|
236
|
+
if (pendingRepaint !== void 0) clearTimeout(pendingRepaint);
|
|
194
237
|
});
|
|
195
238
|
api.slots.register({
|
|
196
239
|
order: SLOT_ORDER,
|
|
197
240
|
slots: {
|
|
198
241
|
sidebar_content(_ctx, props) {
|
|
199
242
|
getRenderTick();
|
|
200
|
-
return renderPanel(api, props.session_id, config);
|
|
243
|
+
return renderPanel(api, props.session_id, config, trackers.get(props.session_id));
|
|
201
244
|
}
|
|
202
245
|
}
|
|
203
246
|
});
|
|
204
247
|
}
|
|
205
248
|
};
|
|
206
249
|
function collectEstimates(api, sessionId) {
|
|
207
|
-
let system = 0;
|
|
208
250
|
let user = 0;
|
|
209
|
-
let
|
|
210
|
-
|
|
211
|
-
let systemPrompt;
|
|
212
|
-
const messages = [...api.state.session.messages(sessionId)];
|
|
213
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
214
|
-
const s = messages[i]?.system?.trim();
|
|
215
|
-
if (s) {
|
|
216
|
-
systemPrompt = s;
|
|
217
|
-
break;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
if (systemPrompt) system += estimateTokens(systemPrompt);
|
|
221
|
-
for (const message of messages) {
|
|
251
|
+
let tools = 0;
|
|
252
|
+
for (const message of api.state.session.messages(sessionId)) {
|
|
222
253
|
const role = message.role;
|
|
223
254
|
try {
|
|
224
255
|
for (const part of api.state.part(message.id)) {
|
|
225
256
|
const p = part;
|
|
226
|
-
if (role === "user") {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
if (
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const state = p.state;
|
|
235
|
-
if (!state) continue;
|
|
236
|
-
const inputKeys = state.input && typeof state.input === "object" ? Object.keys(state.input).length : 0;
|
|
237
|
-
const inputChars = inputKeys * 16;
|
|
238
|
-
let out = "";
|
|
239
|
-
if (state.status === "pending") out = state.raw ?? "";
|
|
240
|
-
else if (state.status === "completed") out = typeof state.output === "string" ? state.output : "";
|
|
241
|
-
else if (state.status === "error") out = typeof state.error === "string" ? state.error : "";
|
|
242
|
-
else {
|
|
243
|
-
if (typeof state.output === "string") out = state.output;
|
|
244
|
-
else if (typeof state.error === "string") out = state.error;
|
|
245
|
-
else if (typeof state.raw === "string") out = state.raw;
|
|
246
|
-
}
|
|
247
|
-
const combined = "x".repeat(inputChars) + out;
|
|
248
|
-
tool += estimateTokens(combined);
|
|
249
|
-
}
|
|
257
|
+
if (p.type === "text" && role === "user") {
|
|
258
|
+
user += estimateTokens(p.text ?? "");
|
|
259
|
+
} else if (p.type === "tool") {
|
|
260
|
+
const state = p.state;
|
|
261
|
+
if (!state) continue;
|
|
262
|
+
if (state.input !== void 0) tools += estimateTokens(JSON.stringify(state.input));
|
|
263
|
+
if (typeof state.output === "string") tools += estimateTokens(state.output);
|
|
264
|
+
else if (typeof state.error === "string") tools += estimateTokens(state.error);
|
|
250
265
|
}
|
|
251
266
|
}
|
|
252
267
|
} catch {
|
|
253
268
|
}
|
|
254
269
|
}
|
|
255
|
-
return {
|
|
270
|
+
return { user, tools };
|
|
256
271
|
}
|
|
257
272
|
function sessionUsage(api, sessionId, config) {
|
|
258
273
|
const messages = api.state.session.messages(sessionId);
|
|
@@ -284,7 +299,7 @@ function sessionUsage(api, sessionId, config) {
|
|
|
284
299
|
const estimates = config.estimate ? collectEstimates(api, sessionId) : void 0;
|
|
285
300
|
return computeContext(counts, limits, cost, estimates, config.exclude);
|
|
286
301
|
}
|
|
287
|
-
function renderPanel(api, sessionId, config) {
|
|
302
|
+
function renderPanel(api, sessionId, config, tps) {
|
|
288
303
|
const theme = api.theme.current;
|
|
289
304
|
const usage = sessionUsage(api, sessionId, config);
|
|
290
305
|
const header = /* @__PURE__ */ jsx("text", { fg: theme.text, attributes: BOLD, children: "Context" });
|
|
@@ -312,14 +327,14 @@ function renderPanel(api, sessionId, config) {
|
|
|
312
327
|
/* @__PURE__ */ jsx("text", { fg: segmentColor(item.id, theme, true), children: "\u258D" }),
|
|
313
328
|
/* @__PURE__ */ jsxs("text", { fg: theme.textMuted, children: [
|
|
314
329
|
SEGMENT_LABEL[item.id],
|
|
315
|
-
|
|
330
|
+
compactFmt.format(item.tokens)
|
|
316
331
|
] })
|
|
317
332
|
] });
|
|
318
333
|
}) });
|
|
319
334
|
};
|
|
320
335
|
const usedStart = 0;
|
|
321
|
-
const usedIds = ["cached", "
|
|
322
|
-
const budgetStart = usedIds.reduce((sum, id) => sum + (cellsById.get(id) ??
|
|
336
|
+
const usedIds = ["cached", "user", "tools", "system", "think", "out"];
|
|
337
|
+
const budgetStart = usedIds.reduce((sum, id) => sum + (cellsById.get(id) ?? 0), 0);
|
|
323
338
|
const usedLegend = row(usedIds, usedStart);
|
|
324
339
|
const budgetLegend = row(["reserved", "free"], budgetStart);
|
|
325
340
|
if (usedLegend) lines.push(usedLegend);
|
|
@@ -335,7 +350,7 @@ function renderPanel(api, sessionId, config) {
|
|
|
335
350
|
/* @__PURE__ */ jsx("text", { fg: segmentColor(cell.id, theme, false), children: "\u258D" }),
|
|
336
351
|
/* @__PURE__ */ jsxs("text", { fg: theme.textMuted, children: [
|
|
337
352
|
SEGMENT_LABEL[cell.id],
|
|
338
|
-
|
|
353
|
+
compactFmt.format(tokenById.get(cell.id) ?? 0)
|
|
339
354
|
] })
|
|
340
355
|
] });
|
|
341
356
|
}) })
|
|
@@ -352,8 +367,30 @@ function renderPanel(api, sessionId, config) {
|
|
|
352
367
|
if (usage.cost > 0) {
|
|
353
368
|
lines.push(/* @__PURE__ */ jsx("text", { fg: theme.textMuted, children: `${money.format(usage.cost)} spent` }));
|
|
354
369
|
}
|
|
370
|
+
if (tps && tps.total() > 0) {
|
|
371
|
+
const instant = tps.instant();
|
|
372
|
+
const live = instant > 0.05;
|
|
373
|
+
const avg = tps.average();
|
|
374
|
+
lines.push(
|
|
375
|
+
/* @__PURE__ */ jsxs("box", { flexDirection: "row", children: [
|
|
376
|
+
live ? /* @__PURE__ */ jsx("text", { fg: speedColor(instant, theme), children: `${instant.toFixed(1)} TPS` }) : null,
|
|
377
|
+
/* @__PURE__ */ jsx("text", { fg: theme.textMuted, children: live ? ` \xB7 avg ${avg.toFixed(1)} \xB7 ${formatDuration(tps.elapsed())}` : `avg ${avg.toFixed(1)} TPS \xB7 ${formatDuration(tps.elapsed())}` })
|
|
378
|
+
] })
|
|
379
|
+
);
|
|
380
|
+
}
|
|
355
381
|
return /* @__PURE__ */ jsx("box", { width: "100%", flexDirection: "column", children: lines });
|
|
356
382
|
}
|
|
383
|
+
function formatDuration(ms) {
|
|
384
|
+
const seconds = ms / 1e3;
|
|
385
|
+
if (seconds < 60) return `${seconds.toFixed(1)}s`;
|
|
386
|
+
const whole = Math.round(seconds);
|
|
387
|
+
return `${Math.floor(whole / 60)}m${String(whole % 60).padStart(2, "0")}s`;
|
|
388
|
+
}
|
|
389
|
+
function speedColor(tps, theme) {
|
|
390
|
+
if (tps < 10) return theme.error;
|
|
391
|
+
if (tps < 50) return theme.warning;
|
|
392
|
+
return theme.success;
|
|
393
|
+
}
|
|
357
394
|
function segmentColor(id, theme, estimate) {
|
|
358
395
|
const base = {
|
|
359
396
|
cached: theme.success,
|
|
@@ -364,23 +401,15 @@ function segmentColor(id, theme, estimate) {
|
|
|
364
401
|
free: theme.text,
|
|
365
402
|
user: theme.accent,
|
|
366
403
|
tools: theme.accent,
|
|
367
|
-
|
|
368
|
-
system: theme.accent,
|
|
369
|
-
assistant: theme.accent,
|
|
370
|
-
other: theme.accent
|
|
404
|
+
system: theme.accent
|
|
371
405
|
};
|
|
372
406
|
if (!estimate) return base[id];
|
|
373
407
|
const est = {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
tool: theme.warning,
|
|
378
|
-
tools: theme.warning,
|
|
379
|
-
other: theme.textMuted,
|
|
380
|
-
cached: theme.success,
|
|
408
|
+
user: theme.info,
|
|
409
|
+
tools: theme.accent,
|
|
410
|
+
system: theme.warning,
|
|
381
411
|
think: theme.secondary,
|
|
382
412
|
out: theme.text,
|
|
383
|
-
reserved: theme.textMuted,
|
|
384
413
|
free: theme.borderSubtle
|
|
385
414
|
};
|
|
386
415
|
return est[id] ?? base[id];
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "opencode-plugin-context",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.2.0",
|
|
5
5
|
"description": "OpenCode TUI plugin that renders the session's context-window usage as a colored, segmented bar (cached / prompt / thinking / output / reserved output) in the sidebar",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
@@ -40,14 +40,14 @@
|
|
|
40
40
|
"access": "public"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"@opencode-ai/plugin": ">=
|
|
43
|
+
"@opencode-ai/plugin": ">=0.0.0-next-17444",
|
|
44
44
|
"@opentui/core": ">=0.2.9",
|
|
45
45
|
"@opentui/solid": ">=0.2.9"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@opencode-ai/plugin": "
|
|
49
|
-
"@opentui/core": "0.5.
|
|
50
|
-
"@opentui/solid": "0.5.
|
|
48
|
+
"@opencode-ai/plugin": "0.0.0-next-17444",
|
|
49
|
+
"@opentui/core": "0.5.11",
|
|
50
|
+
"@opentui/solid": "0.5.11",
|
|
51
51
|
"@types/node": "^26.2.0",
|
|
52
52
|
"esbuild": "^0.28.0",
|
|
53
53
|
"solid-js": "1.9.12",
|