opencode-usage-total 0.1.2 → 0.1.4
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/{chunk-XNCJ32GU.js → chunk-2K4AVHCL.js} +16 -11
- package/dist/index.d.ts +12 -0
- package/dist/index.js +61 -28
- package/dist/usage-total.d.ts +5 -0
- package/dist/usage-total.js +1 -1
- package/package.json +13 -4
|
@@ -1,31 +1,36 @@
|
|
|
1
1
|
// usage-total.ts
|
|
2
2
|
var UsageTotalPlugin = async ({ client }) => {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
try {
|
|
4
|
+
await client.app.log({
|
|
5
|
+
body: {
|
|
6
|
+
service: "usage-total",
|
|
7
|
+
level: "info",
|
|
8
|
+
message: "usage-total plugin loaded"
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
} catch {
|
|
12
|
+
}
|
|
10
13
|
return {
|
|
11
14
|
event: async ({ event }) => {
|
|
12
15
|
if (event.type === "session.created") {
|
|
13
|
-
|
|
16
|
+
client.app.log({
|
|
14
17
|
body: {
|
|
15
18
|
service: "usage-total",
|
|
16
|
-
level: "
|
|
19
|
+
level: "debug",
|
|
17
20
|
message: `session.created [${event.properties.info.id}]`
|
|
18
21
|
}
|
|
22
|
+
}).catch(() => {
|
|
19
23
|
});
|
|
20
24
|
}
|
|
21
25
|
if (event.type === "message.updated") {
|
|
22
26
|
const msg = event.properties.info;
|
|
23
|
-
|
|
27
|
+
client.app.log({
|
|
24
28
|
body: {
|
|
25
29
|
service: "usage-total",
|
|
26
|
-
level: "
|
|
30
|
+
level: "debug",
|
|
27
31
|
message: `message.updated [${msg.id}] role=${msg.role}`
|
|
28
32
|
}
|
|
33
|
+
}).catch(() => {
|
|
29
34
|
});
|
|
30
35
|
}
|
|
31
36
|
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { TuiPlugin } from '@opencode-ai/plugin/tui';
|
|
2
|
+
export { UsageTotalPlugin } from './usage-total.js';
|
|
3
|
+
import '@opencode-ai/plugin';
|
|
4
|
+
|
|
5
|
+
/** @jsxImportSource @opentui/solid */
|
|
6
|
+
|
|
7
|
+
declare const _default: {
|
|
8
|
+
id: string;
|
|
9
|
+
tui: TuiPlugin;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export { _default as default };
|
package/dist/index.js
CHANGED
|
@@ -1,28 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
UsageTotalPlugin
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-2K4AVHCL.js";
|
|
4
4
|
|
|
5
5
|
// usage-total-tui.tsx
|
|
6
6
|
import { createRoot, createSignal } from "solid-js";
|
|
7
7
|
|
|
8
8
|
// package.json
|
|
9
|
-
var version = "0.1.
|
|
9
|
+
var version = "0.1.4";
|
|
10
10
|
|
|
11
|
-
//
|
|
12
|
-
import { jsx, jsxs } from "@opentui/solid/jsx-runtime";
|
|
13
|
-
var DEFAULT_AGENT = "primary";
|
|
14
|
-
var UNKNOWN_ID = "?";
|
|
15
|
-
function resolveRouteSessionID(api) {
|
|
16
|
-
const current = api.route.current;
|
|
17
|
-
if (current.name === "session") {
|
|
18
|
-
const sid = current.params?.sessionID;
|
|
19
|
-
return typeof sid === "string" ? sid : void 0;
|
|
20
|
-
}
|
|
21
|
-
return void 0;
|
|
22
|
-
}
|
|
23
|
-
function modelTokens(m) {
|
|
24
|
-
return m.tokensInput + m.tokensOutput + m.tokensReasoning;
|
|
25
|
-
}
|
|
11
|
+
// helpers.ts
|
|
26
12
|
function safeNum(value) {
|
|
27
13
|
const n = typeof value === "number" ? value : Number(value);
|
|
28
14
|
return Number.isFinite(n) ? n : 0;
|
|
@@ -31,17 +17,38 @@ function roundCost(n) {
|
|
|
31
17
|
return safeNum(Number(n.toFixed(6)));
|
|
32
18
|
}
|
|
33
19
|
function fmtTokens(n) {
|
|
34
|
-
if (!Number.isFinite(n) || n
|
|
35
|
-
|
|
36
|
-
if (
|
|
37
|
-
|
|
20
|
+
if (!Number.isFinite(n) || n < 0) return "0";
|
|
21
|
+
const r = Math.round(n);
|
|
22
|
+
if (r < 1e3) return String(r);
|
|
23
|
+
if (r >= 1e6 || Math.round(r / 100) >= 1e4) {
|
|
24
|
+
return `${(r / 1e6).toFixed(1)}M`;
|
|
25
|
+
}
|
|
26
|
+
return `${(r / 1e3).toFixed(1)}k`;
|
|
38
27
|
}
|
|
39
28
|
function fmtCost(n) {
|
|
29
|
+
if (n < 0) return "";
|
|
40
30
|
if (!Number.isFinite(n) || n === 0) return "";
|
|
41
31
|
if (n < 0.01) return `$${n.toFixed(4)}`;
|
|
42
32
|
return `$${n.toFixed(2)}`;
|
|
43
33
|
}
|
|
34
|
+
function modelTokens(m) {
|
|
35
|
+
return m.tokensInput + m.tokensOutput + m.tokensReasoning + m.tokensCacheRead + m.tokensCacheWrite;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// usage-total-tui.tsx
|
|
39
|
+
import { jsx, jsxs } from "@opentui/solid/jsx-runtime";
|
|
40
|
+
var DEFAULT_AGENT = "primary";
|
|
41
|
+
var UNKNOWN_ID = "?";
|
|
42
|
+
function resolveRouteSessionID(api) {
|
|
43
|
+
const current = api.route.current;
|
|
44
|
+
if (current.name === "session") {
|
|
45
|
+
const sid = current.params?.sessionID;
|
|
46
|
+
return typeof sid === "string" ? sid : void 0;
|
|
47
|
+
}
|
|
48
|
+
return void 0;
|
|
49
|
+
}
|
|
44
50
|
var initialized = false;
|
|
51
|
+
var lastToastTime = 0;
|
|
45
52
|
var tui = async (api) => {
|
|
46
53
|
if (initialized) {
|
|
47
54
|
api.ui?.toast?.({
|
|
@@ -133,6 +140,15 @@ var tui = async (api) => {
|
|
|
133
140
|
loadedSessions.add(sessionID);
|
|
134
141
|
const saved = api.kv?.get?.(kvKey(sessionID));
|
|
135
142
|
if (saved && saved.length > 0) {
|
|
143
|
+
const valid = Array.isArray(saved) && typeof saved[0].provider === "string" && typeof saved[0].model === "string" && typeof saved[0].agent === "string";
|
|
144
|
+
if (!valid) {
|
|
145
|
+
api.ui?.toast?.({
|
|
146
|
+
message: "usage-total: discarded corrupt saved model data",
|
|
147
|
+
variant: "warning"
|
|
148
|
+
});
|
|
149
|
+
api.kv?.set?.(kvKey(sessionID), void 0);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
136
152
|
setModelState((current) => ({ ...current, [sessionID]: saved }));
|
|
137
153
|
}
|
|
138
154
|
}
|
|
@@ -147,6 +163,8 @@ var tui = async (api) => {
|
|
|
147
163
|
const safeInput = safeNum(tokens.input);
|
|
148
164
|
const safeOutput = safeNum(tokens.output);
|
|
149
165
|
const safeReasoning = safeNum(tokens.reasoning);
|
|
166
|
+
const safeCacheRead = safeNum(tokens.cacheRead);
|
|
167
|
+
const safeCacheWrite = safeNum(tokens.cacheWrite);
|
|
150
168
|
if (existingIdx >= 0) {
|
|
151
169
|
const existing = sessionModels[existingIdx];
|
|
152
170
|
sessionModels[existingIdx] = {
|
|
@@ -155,7 +173,10 @@ var tui = async (api) => {
|
|
|
155
173
|
tokensInput: safeNum(existing.tokensInput + safeInput),
|
|
156
174
|
tokensOutput: safeNum(existing.tokensOutput + safeOutput),
|
|
157
175
|
tokensReasoning: safeNum(existing.tokensReasoning + safeReasoning),
|
|
158
|
-
|
|
176
|
+
tokensCacheRead: safeNum(existing.tokensCacheRead + safeCacheRead),
|
|
177
|
+
tokensCacheWrite: safeNum(
|
|
178
|
+
existing.tokensCacheWrite + safeCacheWrite
|
|
179
|
+
)
|
|
159
180
|
};
|
|
160
181
|
} else {
|
|
161
182
|
sessionModels.push({
|
|
@@ -164,7 +185,8 @@ var tui = async (api) => {
|
|
|
164
185
|
tokensInput: safeInput,
|
|
165
186
|
tokensOutput: safeOutput,
|
|
166
187
|
tokensReasoning: safeReasoning,
|
|
167
|
-
|
|
188
|
+
tokensCacheRead: safeCacheRead,
|
|
189
|
+
tokensCacheWrite: safeCacheWrite
|
|
168
190
|
});
|
|
169
191
|
}
|
|
170
192
|
setModelState({
|
|
@@ -176,15 +198,24 @@ var tui = async (api) => {
|
|
|
176
198
|
}
|
|
177
199
|
function trackModel(eventSessionID, entry, cost, tokens) {
|
|
178
200
|
const isNew = upsertModel(eventSessionID, entry, cost, tokens);
|
|
179
|
-
if (isNew) {
|
|
201
|
+
if (isNew && Date.now() - lastToastTime > 2e3) {
|
|
202
|
+
lastToastTime = Date.now();
|
|
180
203
|
api.ui?.toast?.({
|
|
181
204
|
message: `${entry.agent}: ${entry.model}`,
|
|
182
205
|
variant: "success"
|
|
183
206
|
});
|
|
184
207
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
208
|
+
let cursor = eventSessionID;
|
|
209
|
+
const visited = /* @__PURE__ */ new Set();
|
|
210
|
+
while (true) {
|
|
211
|
+
visited.add(cursor);
|
|
212
|
+
const sess = api.state?.session?.get?.(cursor);
|
|
213
|
+
if (!sess?.parentID || sess.parentID === cursor || visited.has(sess.parentID))
|
|
214
|
+
break;
|
|
215
|
+
cursor = sess.parentID;
|
|
216
|
+
}
|
|
217
|
+
if (cursor !== eventSessionID) {
|
|
218
|
+
upsertModel(cursor, entry, cost, tokens);
|
|
188
219
|
}
|
|
189
220
|
}
|
|
190
221
|
unsub = api.event?.on?.("message.updated", (event) => {
|
|
@@ -210,7 +241,9 @@ var tui = async (api) => {
|
|
|
210
241
|
tokens = {
|
|
211
242
|
input: safeNum(info.tokens?.input),
|
|
212
243
|
output: safeNum(info.tokens?.output),
|
|
213
|
-
reasoning: safeNum(info.tokens?.reasoning)
|
|
244
|
+
reasoning: safeNum(info.tokens?.reasoning),
|
|
245
|
+
cacheRead: safeNum(info.tokens?.cache?.read),
|
|
246
|
+
cacheWrite: safeNum(info.tokens?.cache?.write)
|
|
214
247
|
};
|
|
215
248
|
} else {
|
|
216
249
|
return;
|
package/dist/usage-total.js
CHANGED
package/package.json
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-usage-total",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Track model usage, tokens, and costs per agent in the OpenCode TUI sidebar",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"exports": {
|
|
8
|
-
".":
|
|
9
|
-
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./runtime": {
|
|
13
|
+
"types": "./dist/usage-total.d.ts",
|
|
14
|
+
"import": "./dist/usage-total.js"
|
|
15
|
+
}
|
|
10
16
|
},
|
|
11
17
|
"files": [
|
|
12
18
|
"dist",
|
|
@@ -30,6 +36,8 @@
|
|
|
30
36
|
},
|
|
31
37
|
"scripts": {
|
|
32
38
|
"build": "tsup",
|
|
39
|
+
"test": "vitest",
|
|
40
|
+
"test:run": "vitest run",
|
|
33
41
|
"prepublishOnly": "npm run build"
|
|
34
42
|
},
|
|
35
43
|
"devDependencies": {
|
|
@@ -38,6 +46,7 @@
|
|
|
38
46
|
"@opentui/keymap": "^0.4.1",
|
|
39
47
|
"@opentui/solid": "^0.4.1",
|
|
40
48
|
"solid-js": "^1.9.12",
|
|
41
|
-
"tsup": "^8.5.1"
|
|
49
|
+
"tsup": "^8.5.1",
|
|
50
|
+
"vitest": "^4.1.9"
|
|
42
51
|
}
|
|
43
52
|
}
|