opencode-usage-total 0.1.2 → 0.1.3

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
  }
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.3";
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,36 @@ function roundCost(n) {
31
17
  return safeNum(Number(n.toFixed(6)));
32
18
  }
33
19
  function fmtTokens(n) {
20
+ if (n < 0) return "0";
34
21
  if (!Number.isFinite(n) || n === 0) return "0";
35
22
  if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
36
23
  if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
37
24
  return String(Math.round(n));
38
25
  }
39
26
  function fmtCost(n) {
27
+ if (n < 0) return "";
40
28
  if (!Number.isFinite(n) || n === 0) return "";
41
29
  if (n < 0.01) return `$${n.toFixed(4)}`;
42
30
  return `$${n.toFixed(2)}`;
43
31
  }
32
+ function modelTokens(m) {
33
+ return m.tokensInput + m.tokensOutput + m.tokensReasoning + m.tokensCacheRead + m.tokensCacheWrite;
34
+ }
35
+
36
+ // usage-total-tui.tsx
37
+ import { jsx, jsxs } from "@opentui/solid/jsx-runtime";
38
+ var DEFAULT_AGENT = "primary";
39
+ var UNKNOWN_ID = "?";
40
+ function resolveRouteSessionID(api) {
41
+ const current = api.route.current;
42
+ if (current.name === "session") {
43
+ const sid = current.params?.sessionID;
44
+ return typeof sid === "string" ? sid : void 0;
45
+ }
46
+ return void 0;
47
+ }
44
48
  var initialized = false;
49
+ var lastToastTime = 0;
45
50
  var tui = async (api) => {
46
51
  if (initialized) {
47
52
  api.ui?.toast?.({
@@ -133,6 +138,15 @@ var tui = async (api) => {
133
138
  loadedSessions.add(sessionID);
134
139
  const saved = api.kv?.get?.(kvKey(sessionID));
135
140
  if (saved && saved.length > 0) {
141
+ const valid = Array.isArray(saved) && typeof saved[0].provider === "string" && typeof saved[0].model === "string" && typeof saved[0].agent === "string";
142
+ if (!valid) {
143
+ api.ui?.toast?.({
144
+ message: "usage-total: discarded corrupt saved model data",
145
+ variant: "warning"
146
+ });
147
+ api.kv?.set?.(kvKey(sessionID), void 0);
148
+ return;
149
+ }
136
150
  setModelState((current) => ({ ...current, [sessionID]: saved }));
137
151
  }
138
152
  }
@@ -147,6 +161,8 @@ var tui = async (api) => {
147
161
  const safeInput = safeNum(tokens.input);
148
162
  const safeOutput = safeNum(tokens.output);
149
163
  const safeReasoning = safeNum(tokens.reasoning);
164
+ const safeCacheRead = safeNum(tokens.cacheRead);
165
+ const safeCacheWrite = safeNum(tokens.cacheWrite);
150
166
  if (existingIdx >= 0) {
151
167
  const existing = sessionModels[existingIdx];
152
168
  sessionModels[existingIdx] = {
@@ -155,6 +171,10 @@ var tui = async (api) => {
155
171
  tokensInput: safeNum(existing.tokensInput + safeInput),
156
172
  tokensOutput: safeNum(existing.tokensOutput + safeOutput),
157
173
  tokensReasoning: safeNum(existing.tokensReasoning + safeReasoning),
174
+ tokensCacheRead: safeNum(existing.tokensCacheRead + safeCacheRead),
175
+ tokensCacheWrite: safeNum(
176
+ existing.tokensCacheWrite + safeCacheWrite
177
+ ),
158
178
  messageCount: existing.messageCount + 1
159
179
  };
160
180
  } else {
@@ -164,6 +184,8 @@ var tui = async (api) => {
164
184
  tokensInput: safeInput,
165
185
  tokensOutput: safeOutput,
166
186
  tokensReasoning: safeReasoning,
187
+ tokensCacheRead: safeCacheRead,
188
+ tokensCacheWrite: safeCacheWrite,
167
189
  messageCount: 1
168
190
  });
169
191
  }
@@ -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;
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-usage-total",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
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",
@@ -30,6 +30,8 @@
30
30
  },
31
31
  "scripts": {
32
32
  "build": "tsup",
33
+ "test": "vitest",
34
+ "test:run": "vitest run",
33
35
  "prepublishOnly": "npm run build"
34
36
  },
35
37
  "devDependencies": {
@@ -38,6 +40,7 @@
38
40
  "@opentui/keymap": "^0.4.1",
39
41
  "@opentui/solid": "^0.4.1",
40
42
  "solid-js": "^1.9.12",
41
- "tsup": "^8.5.1"
43
+ "tsup": "^8.5.1",
44
+ "vitest": "^4.1.9"
42
45
  }
43
46
  }