@oh-my-sidebar/opencode-context-progress 0.1.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 +34 -0
- package/dist/tui.d.ts +7 -0
- package/dist/tui.js +111 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# @oh-my-sidebar/opencode-context-progress
|
|
2
|
+
|
|
3
|
+
OpenCode TUI sidebar plugin that displays the current session's context usage progress.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Real-time context window usage bar
|
|
8
|
+
- Token count and context window limit display
|
|
9
|
+
- Session cost tracking
|
|
10
|
+
- Color-coded warnings (yellow at 70%, red at 90%)
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @oh-my-sidebar/opencode-context-progress
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
Register the plugin in your OpenCode configuration:
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"plugins": {
|
|
25
|
+
"oh-my-sidebar.context-progress": {
|
|
26
|
+
"tui": "@oh-my-sidebar/opencode-context-progress/tui"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## License
|
|
33
|
+
|
|
34
|
+
MIT
|
package/dist/tui.d.ts
ADDED
package/dist/tui.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// src/index.tsx
|
|
2
|
+
import { TextAttributes } from "@opentui/core";
|
|
3
|
+
import { createMemo } from "solid-js";
|
|
4
|
+
import { jsx, jsxs } from "@opentui/solid/jsx-runtime";
|
|
5
|
+
var BAR_WIDTH = 24;
|
|
6
|
+
function formatInt(value) {
|
|
7
|
+
return new Intl.NumberFormat("en-US").format(Math.max(0, Math.round(value)));
|
|
8
|
+
}
|
|
9
|
+
function formatMoney(value) {
|
|
10
|
+
return `$${value.toFixed(2)}`;
|
|
11
|
+
}
|
|
12
|
+
function safeNumber(value) {
|
|
13
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
14
|
+
}
|
|
15
|
+
function messageTokenCount(message) {
|
|
16
|
+
const input = safeNumber(message?.tokens?.input);
|
|
17
|
+
const output = safeNumber(message?.tokens?.output);
|
|
18
|
+
const reasoning = safeNumber(message?.tokens?.reasoning);
|
|
19
|
+
const cacheRead = safeNumber(message?.tokens?.cache?.read);
|
|
20
|
+
const cacheWrite = safeNumber(message?.tokens?.cache?.write);
|
|
21
|
+
return input + output + reasoning + cacheRead + cacheWrite;
|
|
22
|
+
}
|
|
23
|
+
function buildBar(percent) {
|
|
24
|
+
const clamped = Math.max(0, Math.min(100, percent));
|
|
25
|
+
const filled = Math.max(0, Math.min(BAR_WIDTH, Math.round(clamped / 100 * BAR_WIDTH)));
|
|
26
|
+
return {
|
|
27
|
+
bar: `${"\u2588".repeat(filled)}${"\u2591".repeat(BAR_WIDTH - filled)}`,
|
|
28
|
+
clamped
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function View(props) {
|
|
32
|
+
const messages = createMemo(() => props.api.state.session.messages(props.sessionID));
|
|
33
|
+
const sessionCost = createMemo(() => {
|
|
34
|
+
const sessionState = props.api.state?.session;
|
|
35
|
+
const fromState = safeNumber(sessionState?.get?.(props.sessionID)?.cost);
|
|
36
|
+
if (fromState > 0) return fromState;
|
|
37
|
+
return messages().filter((m) => (m?.role ?? m?.info?.role) === "assistant").reduce((sum, m) => sum + safeNumber(m?.cost), 0);
|
|
38
|
+
});
|
|
39
|
+
const usage = createMemo(() => {
|
|
40
|
+
const lastAssistant = messages().findLast((m) => {
|
|
41
|
+
const role = m?.role ?? m?.info?.role;
|
|
42
|
+
const output = safeNumber(m?.tokens?.output);
|
|
43
|
+
return role === "assistant" && output > 0;
|
|
44
|
+
});
|
|
45
|
+
if (!lastAssistant) {
|
|
46
|
+
return {
|
|
47
|
+
tokens: 0,
|
|
48
|
+
contextWindow: 0,
|
|
49
|
+
percent: 0
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const tokens = messageTokenCount(lastAssistant);
|
|
53
|
+
const providerID = lastAssistant?.providerID ?? lastAssistant?.info?.providerID;
|
|
54
|
+
const modelID = lastAssistant?.modelID ?? lastAssistant?.info?.modelID;
|
|
55
|
+
const model = props.api.state.provider.find((item) => item.id === providerID)?.models?.[modelID];
|
|
56
|
+
const contextWindow = safeNumber(model?.limit?.context);
|
|
57
|
+
const percent = contextWindow > 0 ? Math.round(tokens / contextWindow * 100) : 0;
|
|
58
|
+
return {
|
|
59
|
+
tokens,
|
|
60
|
+
contextWindow,
|
|
61
|
+
percent
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
const detailLine = createMemo(() => {
|
|
65
|
+
const state = usage();
|
|
66
|
+
const limitText = state.contextWindow > 0 ? formatInt(state.contextWindow) : "--";
|
|
67
|
+
return `${formatInt(state.tokens)} / ${limitText} / ${formatMoney(sessionCost())}`;
|
|
68
|
+
});
|
|
69
|
+
const theme = () => props.api.theme.current;
|
|
70
|
+
const progress = createMemo(() => {
|
|
71
|
+
const percent = usage().percent;
|
|
72
|
+
const bar = buildBar(percent);
|
|
73
|
+
const color = percent >= 90 ? theme().error : percent >= 70 ? theme().warning : theme().accent;
|
|
74
|
+
return {
|
|
75
|
+
bar: bar.bar,
|
|
76
|
+
color,
|
|
77
|
+
percent: bar.clamped
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
return /* @__PURE__ */ jsxs("box", { children: [
|
|
81
|
+
/* @__PURE__ */ jsx("text", { fg: theme().text, attributes: TextAttributes.BOLD, children: "Context" }),
|
|
82
|
+
/* @__PURE__ */ jsxs("box", { flexDirection: "row", gap: 1, children: [
|
|
83
|
+
/* @__PURE__ */ jsx("text", { fg: progress().color, children: progress().bar }),
|
|
84
|
+
/* @__PURE__ */ jsxs("text", { fg: progress().color, children: [
|
|
85
|
+
" ",
|
|
86
|
+
progress().percent,
|
|
87
|
+
"%"
|
|
88
|
+
] })
|
|
89
|
+
] }),
|
|
90
|
+
/* @__PURE__ */ jsx("text", { fg: theme().textMuted, children: detailLine() })
|
|
91
|
+
] });
|
|
92
|
+
}
|
|
93
|
+
var tui = async (api) => {
|
|
94
|
+
const { slots } = api;
|
|
95
|
+
slots.register({
|
|
96
|
+
order: 100,
|
|
97
|
+
slots: {
|
|
98
|
+
sidebar_content(_ctx, props) {
|
|
99
|
+
return /* @__PURE__ */ jsx(View, { api, sessionID: props.session_id });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
var plugin = {
|
|
105
|
+
id: "oh-my-sidebar.context-progress",
|
|
106
|
+
tui
|
|
107
|
+
};
|
|
108
|
+
var src_default = plugin;
|
|
109
|
+
export {
|
|
110
|
+
src_default as default
|
|
111
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@oh-my-sidebar/opencode-context-progress",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OpenCode TUI sidebar plugin for context usage progress",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/edso404/opencode-zanbo-sidebar",
|
|
8
|
+
"directory": "packages/context-progress"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/edso404/opencode-zanbo-sidebar/tree/master/packages/context-progress",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/edso404/opencode-zanbo-sidebar/issues"
|
|
13
|
+
},
|
|
14
|
+
"type": "module",
|
|
15
|
+
"exports": {
|
|
16
|
+
"./tui": {
|
|
17
|
+
"types": "./dist/tui.d.ts",
|
|
18
|
+
"import": "./dist/tui.js",
|
|
19
|
+
"default": "./dist/tui.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsup",
|
|
28
|
+
"dev": "tsup --watch",
|
|
29
|
+
"prepublishOnly": "pnpm build"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"opencode",
|
|
36
|
+
"opencode-plugin",
|
|
37
|
+
"sidebar",
|
|
38
|
+
"context"
|
|
39
|
+
],
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"engines": {
|
|
42
|
+
"opencode": ">=1.4.3"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@opencode-ai/plugin": "catalog:",
|
|
46
|
+
"@opentui/core": "catalog:",
|
|
47
|
+
"@opentui/solid": "catalog:",
|
|
48
|
+
"solid-js": "catalog:"
|
|
49
|
+
}
|
|
50
|
+
}
|