opencode-plugin-context 1.1.1 → 1.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/dist/tui.js +135 -39
- package/package.json +4 -4
package/dist/tui.js
CHANGED
|
@@ -21,7 +21,27 @@ 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.
|
|
24
|
+
return Math.max(0, Math.ceil(input.length / 4));
|
|
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) };
|
|
25
45
|
}
|
|
26
46
|
function computeContext(counts, limits, cost = 0, estimates, exclude = []) {
|
|
27
47
|
const { input, output, reasoning, cacheRead, cacheWrite } = counts;
|
|
@@ -30,28 +50,62 @@ function computeContext(counts, limits, cost = 0, estimates, exclude = []) {
|
|
|
30
50
|
const reserved = limits && limits.output > 0 ? Math.max(0, limits.output - output) : 0;
|
|
31
51
|
const free = window > 0 ? Math.max(0, window - used - reserved) : 0;
|
|
32
52
|
const prompt = input + cacheWrite;
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
const normalizeExclude = (id) => {
|
|
54
|
+
if (id === "tools" || id === "tool") return exclude.includes("tools") || exclude.includes("tool");
|
|
55
|
+
return exclude.includes(id);
|
|
56
|
+
};
|
|
57
|
+
const excluded = (id) => normalizeExclude(id);
|
|
58
|
+
let raw;
|
|
59
|
+
if (estimates) {
|
|
60
|
+
const hasWebCats = estimates.system !== void 0 || estimates.assistant !== void 0 || estimates.tool !== void 0;
|
|
61
|
+
if (hasWebCats) {
|
|
62
|
+
const rawTokens = {
|
|
63
|
+
system: Math.max(0, estimates.system ?? 0),
|
|
64
|
+
user: Math.max(0, estimates.user ?? 0),
|
|
65
|
+
assistant: Math.max(0, estimates.assistant ?? 0),
|
|
66
|
+
tool: Math.max(0, estimates.tool ?? estimates.tools ?? 0)
|
|
67
|
+
};
|
|
68
|
+
const scaled = scaleToPrompt(rawTokens, prompt);
|
|
69
|
+
raw = [
|
|
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));
|
|
50
104
|
return {
|
|
51
105
|
used,
|
|
52
106
|
window,
|
|
53
107
|
percent: window > 0 ? Math.min(100, Math.round(used / window * 100)) : 0,
|
|
54
|
-
segments:
|
|
108
|
+
segments: filtered,
|
|
55
109
|
cost,
|
|
56
110
|
known: window > 0
|
|
57
111
|
};
|
|
@@ -76,8 +130,11 @@ var VALID_SEGMENT_IDS = [
|
|
|
76
130
|
"cached",
|
|
77
131
|
"user",
|
|
78
132
|
"tools",
|
|
133
|
+
"tool",
|
|
79
134
|
"system",
|
|
80
135
|
"prompt",
|
|
136
|
+
"assistant",
|
|
137
|
+
"other",
|
|
81
138
|
"think",
|
|
82
139
|
"out",
|
|
83
140
|
"reserved",
|
|
@@ -98,8 +155,11 @@ var SEGMENT_LABEL = {
|
|
|
98
155
|
cached: "c",
|
|
99
156
|
user: "u",
|
|
100
157
|
tools: "m",
|
|
158
|
+
tool: "m",
|
|
101
159
|
system: "s",
|
|
102
160
|
prompt: "p",
|
|
161
|
+
assistant: "a",
|
|
162
|
+
other: "x",
|
|
103
163
|
think: "t",
|
|
104
164
|
out: "o",
|
|
105
165
|
reserved: "r",
|
|
@@ -107,7 +167,7 @@ var SEGMENT_LABEL = {
|
|
|
107
167
|
};
|
|
108
168
|
var money = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" });
|
|
109
169
|
var intFmt = new Intl.NumberFormat("en-US");
|
|
110
|
-
var
|
|
170
|
+
var compact = (n) => formatCompactTokens(n);
|
|
111
171
|
var plugin = {
|
|
112
172
|
id: "opencode-plugin-context",
|
|
113
173
|
tui: async (api, rawOptions) => {
|
|
@@ -144,27 +204,55 @@ var plugin = {
|
|
|
144
204
|
}
|
|
145
205
|
};
|
|
146
206
|
function collectEstimates(api, sessionId) {
|
|
207
|
+
let system = 0;
|
|
147
208
|
let user = 0;
|
|
148
|
-
let
|
|
149
|
-
|
|
209
|
+
let assistant = 0;
|
|
210
|
+
let tool = 0;
|
|
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) {
|
|
150
222
|
const role = message.role;
|
|
151
223
|
try {
|
|
152
224
|
for (const part of api.state.part(message.id)) {
|
|
153
225
|
const p = part;
|
|
154
|
-
if (
|
|
155
|
-
user += estimateTokens(p.text ?? "");
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if (
|
|
160
|
-
|
|
161
|
-
else if (
|
|
226
|
+
if (role === "user") {
|
|
227
|
+
if (p.type === "text") user += estimateTokens(p.text ?? "");
|
|
228
|
+
else if (p.type === "file") user += estimateTokens(p.source?.text?.value ?? "");
|
|
229
|
+
else if (p.type === "agent") user += estimateTokens(p.source?.value ?? "");
|
|
230
|
+
} else if (role === "assistant") {
|
|
231
|
+
if (p.type === "text" || p.type === "reasoning") {
|
|
232
|
+
assistant += estimateTokens(p.text ?? "");
|
|
233
|
+
} else if (p.type === "tool") {
|
|
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
|
+
}
|
|
162
250
|
}
|
|
163
251
|
}
|
|
164
252
|
} catch {
|
|
165
253
|
}
|
|
166
254
|
}
|
|
167
|
-
return { user, tools };
|
|
255
|
+
return { system, user, assistant, tool, tools: tool };
|
|
168
256
|
}
|
|
169
257
|
function sessionUsage(api, sessionId, config) {
|
|
170
258
|
const messages = api.state.session.messages(sessionId);
|
|
@@ -224,14 +312,14 @@ function renderPanel(api, sessionId, config) {
|
|
|
224
312
|
/* @__PURE__ */ jsx("text", { fg: segmentColor(item.id, theme, true), children: "\u258D" }),
|
|
225
313
|
/* @__PURE__ */ jsxs("text", { fg: theme.textMuted, children: [
|
|
226
314
|
SEGMENT_LABEL[item.id],
|
|
227
|
-
|
|
315
|
+
compact(item.tokens)
|
|
228
316
|
] })
|
|
229
317
|
] });
|
|
230
318
|
}) });
|
|
231
319
|
};
|
|
232
320
|
const usedStart = 0;
|
|
233
|
-
const usedIds = ["cached", "user", "
|
|
234
|
-
const budgetStart = usedIds.reduce((sum, id) => sum + (cellsById.get(id) ?? 0), 0);
|
|
321
|
+
const usedIds = ["cached", "system", "user", "assistant", "tool", "other", "think", "out"];
|
|
322
|
+
const budgetStart = usedIds.reduce((sum, id) => sum + (cellsById.get(id) ?? (id === "tool" ? cellsById.get("tools") ?? 0 : 0)), 0);
|
|
235
323
|
const usedLegend = row(usedIds, usedStart);
|
|
236
324
|
const budgetLegend = row(["reserved", "free"], budgetStart);
|
|
237
325
|
if (usedLegend) lines.push(usedLegend);
|
|
@@ -247,7 +335,7 @@ function renderPanel(api, sessionId, config) {
|
|
|
247
335
|
/* @__PURE__ */ jsx("text", { fg: segmentColor(cell.id, theme, false), children: "\u258D" }),
|
|
248
336
|
/* @__PURE__ */ jsxs("text", { fg: theme.textMuted, children: [
|
|
249
337
|
SEGMENT_LABEL[cell.id],
|
|
250
|
-
|
|
338
|
+
compact(tokenById.get(cell.id) ?? 0)
|
|
251
339
|
] })
|
|
252
340
|
] });
|
|
253
341
|
}) })
|
|
@@ -276,15 +364,23 @@ function segmentColor(id, theme, estimate) {
|
|
|
276
364
|
free: theme.text,
|
|
277
365
|
user: theme.accent,
|
|
278
366
|
tools: theme.accent,
|
|
279
|
-
|
|
367
|
+
tool: theme.accent,
|
|
368
|
+
system: theme.accent,
|
|
369
|
+
assistant: theme.accent,
|
|
370
|
+
other: theme.accent
|
|
280
371
|
};
|
|
281
372
|
if (!estimate) return base[id];
|
|
282
373
|
const est = {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
374
|
+
system: theme.info,
|
|
375
|
+
user: theme.success,
|
|
376
|
+
assistant: theme.secondary,
|
|
377
|
+
tool: theme.warning,
|
|
378
|
+
tools: theme.warning,
|
|
379
|
+
other: theme.textMuted,
|
|
380
|
+
cached: theme.success,
|
|
286
381
|
think: theme.secondary,
|
|
287
382
|
out: theme.text,
|
|
383
|
+
reserved: theme.textMuted,
|
|
288
384
|
free: theme.borderSubtle
|
|
289
385
|
};
|
|
290
386
|
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.1.
|
|
4
|
+
"version": "1.1.2",
|
|
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": {
|
|
@@ -45,9 +45,9 @@
|
|
|
45
45
|
"@opentui/solid": ">=0.2.9"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@opencode-ai/plugin": "1.18.
|
|
49
|
-
"@opentui/core": "0.5.
|
|
50
|
-
"@opentui/solid": "0.5.
|
|
48
|
+
"@opencode-ai/plugin": "1.18.23",
|
|
49
|
+
"@opentui/core": "0.5.8",
|
|
50
|
+
"@opentui/solid": "0.5.8",
|
|
51
51
|
"@types/node": "^26.2.0",
|
|
52
52
|
"esbuild": "^0.28.0",
|
|
53
53
|
"solid-js": "1.9.12",
|