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.
@@ -1,31 +1,36 @@
1
1
  // usage-total.ts
2
2
  var UsageTotalPlugin = async ({ client }) => {
3
- await client.app.log({
4
- body: {
5
- service: "usage-total",
6
- level: "info",
7
- message: "usage-total plugin loaded"
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
- void client.app.log({
16
+ client.app.log({
14
17
  body: {
15
18
  service: "usage-total",
16
- level: "info",
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
- void client.app.log({
27
+ client.app.log({
24
28
  body: {
25
29
  service: "usage-total",
26
- level: "info",
30
+ level: "debug",
27
31
  message: `message.updated [${msg.id}] role=${msg.role}`
28
32
  }
33
+ }).catch(() => {
29
34
  });
30
35
  }
31
36
  }
@@ -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-XNCJ32GU.js";
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.2";
9
+ var version = "0.1.4";
10
10
 
11
- // usage-total-tui.tsx
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 === 0) return "0";
35
- if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
36
- if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
37
- return String(Math.round(n));
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
- messageCount: existing.messageCount + 1
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
- messageCount: 1
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
- const session = api.state?.session?.get?.(eventSessionID);
186
- if (session?.parentID && session.parentID !== eventSessionID) {
187
- upsertModel(session.parentID, entry, cost, tokens);
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;
@@ -0,0 +1,5 @@
1
+ import { Plugin } from '@opencode-ai/plugin';
2
+
3
+ declare const UsageTotalPlugin: Plugin;
4
+
5
+ export { UsageTotalPlugin };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  UsageTotalPlugin
3
- } from "./chunk-XNCJ32GU.js";
3
+ } from "./chunk-2K4AVHCL.js";
4
4
  export {
5
5
  UsageTotalPlugin
6
6
  };
package/package.json CHANGED
@@ -1,12 +1,18 @@
1
1
  {
2
2
  "name": "opencode-usage-total",
3
- "version": "0.1.2",
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
- ".": "./dist/index.js",
9
- "./runtime": "./dist/usage-total.js"
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
  }