opencode-usage-total 0.1.1 → 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.
- package/dist/{chunk-XNCJ32GU.js → chunk-2K4AVHCL.js} +16 -11
- package/dist/index.js +280 -200
- package/dist/usage-total.js +1 -1
- package/package.json +5 -2
|
@@ -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.js
CHANGED
|
@@ -1,237 +1,317 @@
|
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
var
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return api.route.current.name === "session" && typeof ((_a = api.route.current.params) == null ? void 0 : _a.sessionID) === "string" ? api.route.current.params.sessionID : void 0;
|
|
13
|
-
}
|
|
14
|
-
function modelTokens(m) {
|
|
15
|
-
return m.tokensInput + m.tokensOutput + m.tokensReasoning;
|
|
16
|
-
}
|
|
7
|
+
|
|
8
|
+
// package.json
|
|
9
|
+
var version = "0.1.3";
|
|
10
|
+
|
|
11
|
+
// helpers.ts
|
|
17
12
|
function safeNum(value) {
|
|
18
13
|
const n = typeof value === "number" ? value : Number(value);
|
|
19
14
|
return Number.isFinite(n) ? n : 0;
|
|
20
15
|
}
|
|
16
|
+
function roundCost(n) {
|
|
17
|
+
return safeNum(Number(n.toFixed(6)));
|
|
18
|
+
}
|
|
21
19
|
function fmtTokens(n) {
|
|
20
|
+
if (n < 0) return "0";
|
|
22
21
|
if (!Number.isFinite(n) || n === 0) return "0";
|
|
23
22
|
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
24
23
|
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
|
|
25
24
|
return String(Math.round(n));
|
|
26
25
|
}
|
|
27
26
|
function fmtCost(n) {
|
|
27
|
+
if (n < 0) return "";
|
|
28
28
|
if (!Number.isFinite(n) || n === 0) return "";
|
|
29
29
|
if (n < 0.01) return `$${n.toFixed(4)}`;
|
|
30
30
|
return `$${n.toFixed(2)}`;
|
|
31
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
|
+
}
|
|
48
|
+
var initialized = false;
|
|
49
|
+
var lastToastTime = 0;
|
|
32
50
|
var tui = async (api) => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
setExpanded(next);
|
|
51
|
-
api.kv.set(EXPANDED_KV_KEY, next);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
],
|
|
55
|
-
bindings: [{ key: "alt+m", cmd: TOGGLE_CMD }]
|
|
56
|
-
}) : void 0;
|
|
57
|
-
const loadedSessions = /* @__PURE__ */ new Set();
|
|
58
|
-
function kvKey(sessionID) {
|
|
59
|
-
return `usage-total:models:${sessionID}`;
|
|
51
|
+
if (initialized) {
|
|
52
|
+
api.ui?.toast?.({
|
|
53
|
+
message: "usage-total TUI already loaded; skipping re-init",
|
|
54
|
+
variant: "warning"
|
|
55
|
+
});
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
initialized = true;
|
|
59
|
+
api.ui?.toast?.({ message: "usage-total TUI loaded", variant: "info" });
|
|
60
|
+
let unsub;
|
|
61
|
+
let keymapDispose;
|
|
62
|
+
let solidDispose;
|
|
63
|
+
let flushPending;
|
|
64
|
+
const cleanup = () => {
|
|
65
|
+
try {
|
|
66
|
+
flushPending?.();
|
|
67
|
+
} catch {
|
|
60
68
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
api.kv.set(kvKey(sessionID), models);
|
|
65
|
-
}
|
|
69
|
+
try {
|
|
70
|
+
unsub?.();
|
|
71
|
+
} catch {
|
|
66
72
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const saved = api.kv.get(kvKey(sessionID));
|
|
71
|
-
if (saved && saved.length > 0) {
|
|
72
|
-
setModelState((current) => ({ ...current, [sessionID]: saved }));
|
|
73
|
-
}
|
|
73
|
+
try {
|
|
74
|
+
keymapDispose?.();
|
|
75
|
+
} catch {
|
|
74
76
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
try {
|
|
78
|
+
solidDispose?.();
|
|
79
|
+
} catch {
|
|
80
|
+
}
|
|
81
|
+
initialized = false;
|
|
82
|
+
};
|
|
83
|
+
try {
|
|
84
|
+
if (api.lifecycle?.onDispose) {
|
|
85
|
+
api.lifecycle.onDispose(cleanup);
|
|
86
|
+
}
|
|
87
|
+
createRoot((dispose) => {
|
|
88
|
+
solidDispose = dispose;
|
|
89
|
+
const [modelState, setModelState] = createSignal({});
|
|
90
|
+
const EXPANDED_KV_KEY = "usage-total.sidebar.expanded";
|
|
91
|
+
const [expanded, setExpanded] = createSignal(
|
|
92
|
+
api.kv?.get?.(EXPANDED_KV_KEY, true) !== false
|
|
81
93
|
);
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
tokensOutput: safeOutput,
|
|
102
|
-
tokensReasoning: safeReasoning,
|
|
103
|
-
messageCount: 1
|
|
104
|
-
});
|
|
94
|
+
const TOGGLE_CMD = "usage-total.toggle-section";
|
|
95
|
+
keymapDispose = api.keymap?.registerLayer ? api.keymap.registerLayer({
|
|
96
|
+
commands: [
|
|
97
|
+
{
|
|
98
|
+
name: TOGGLE_CMD,
|
|
99
|
+
title: "Usage: Toggle models section",
|
|
100
|
+
description: "Collapse or expand the models list in the sidebar",
|
|
101
|
+
run: () => {
|
|
102
|
+
const next = !expanded();
|
|
103
|
+
setExpanded(next);
|
|
104
|
+
api.kv?.set?.(EXPANDED_KV_KEY, next);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
],
|
|
108
|
+
bindings: [{ key: "alt+m", cmd: TOGGLE_CMD }]
|
|
109
|
+
}) : void 0;
|
|
110
|
+
const loadedSessions = /* @__PURE__ */ new Set();
|
|
111
|
+
function kvKey(sessionID) {
|
|
112
|
+
return `usage-total:models:${sessionID}`;
|
|
105
113
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
114
|
+
const SAVE_DEBOUNCE_MS = 500;
|
|
115
|
+
const dirtySessions = /* @__PURE__ */ new Set();
|
|
116
|
+
let saveTimer;
|
|
117
|
+
function flushDirtySessions() {
|
|
118
|
+
if (saveTimer) {
|
|
119
|
+
clearTimeout(saveTimer);
|
|
120
|
+
saveTimer = void 0;
|
|
121
|
+
}
|
|
122
|
+
for (const sid of dirtySessions) {
|
|
123
|
+
const models = modelState()[sid];
|
|
124
|
+
if (models && models.length > 0) {
|
|
125
|
+
api.kv?.set?.(kvKey(sid), models);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
dirtySessions.clear();
|
|
120
129
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
130
|
+
flushPending = flushDirtySessions;
|
|
131
|
+
function scheduleSave(sessionID) {
|
|
132
|
+
dirtySessions.add(sessionID);
|
|
133
|
+
if (saveTimer) clearTimeout(saveTimer);
|
|
134
|
+
saveTimer = setTimeout(flushDirtySessions, SAVE_DEBOUNCE_MS);
|
|
124
135
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
agent = info.agent ?? DEFAULT_AGENT;
|
|
142
|
-
} else if (info.role === "assistant") {
|
|
143
|
-
provider = info.providerID ?? UNKNOWN_ID;
|
|
144
|
-
model = info.modelID ?? UNKNOWN_ID;
|
|
145
|
-
agent = info.agent ?? info.mode ?? DEFAULT_AGENT;
|
|
146
|
-
cost = safeNum(info.cost);
|
|
147
|
-
tokens = {
|
|
148
|
-
input: safeNum((_b = info.tokens) == null ? void 0 : _b.input),
|
|
149
|
-
output: safeNum((_c = info.tokens) == null ? void 0 : _c.output),
|
|
150
|
-
reasoning: safeNum((_d = info.tokens) == null ? void 0 : _d.reasoning)
|
|
151
|
-
};
|
|
152
|
-
} else {
|
|
153
|
-
return;
|
|
136
|
+
function loadSession(sessionID) {
|
|
137
|
+
if (loadedSessions.has(sessionID)) return;
|
|
138
|
+
loadedSessions.add(sessionID);
|
|
139
|
+
const saved = api.kv?.get?.(kvKey(sessionID));
|
|
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
|
+
}
|
|
150
|
+
setModelState((current) => ({ ...current, [sessionID]: saved }));
|
|
151
|
+
}
|
|
154
152
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
153
|
+
function upsertModel(sessionID, entry, cost, tokens) {
|
|
154
|
+
const dedupeKey = `${entry.provider}/${entry.model}/${entry.agent}`;
|
|
155
|
+
const current = modelState();
|
|
156
|
+
const sessionModels = [...current[sessionID] ?? []];
|
|
157
|
+
const existingIdx = sessionModels.findIndex(
|
|
158
|
+
(m) => `${m.provider}/${m.model}/${m.agent}` === dedupeKey
|
|
159
|
+
);
|
|
160
|
+
const safeCost = safeNum(cost);
|
|
161
|
+
const safeInput = safeNum(tokens.input);
|
|
162
|
+
const safeOutput = safeNum(tokens.output);
|
|
163
|
+
const safeReasoning = safeNum(tokens.reasoning);
|
|
164
|
+
const safeCacheRead = safeNum(tokens.cacheRead);
|
|
165
|
+
const safeCacheWrite = safeNum(tokens.cacheWrite);
|
|
166
|
+
if (existingIdx >= 0) {
|
|
167
|
+
const existing = sessionModels[existingIdx];
|
|
168
|
+
sessionModels[existingIdx] = {
|
|
169
|
+
...existing,
|
|
170
|
+
cost: roundCost(existing.cost + safeCost),
|
|
171
|
+
tokensInput: safeNum(existing.tokensInput + safeInput),
|
|
172
|
+
tokensOutput: safeNum(existing.tokensOutput + safeOutput),
|
|
173
|
+
tokensReasoning: safeNum(existing.tokensReasoning + safeReasoning),
|
|
174
|
+
tokensCacheRead: safeNum(existing.tokensCacheRead + safeCacheRead),
|
|
175
|
+
tokensCacheWrite: safeNum(
|
|
176
|
+
existing.tokensCacheWrite + safeCacheWrite
|
|
177
|
+
),
|
|
178
|
+
messageCount: existing.messageCount + 1
|
|
179
|
+
};
|
|
180
|
+
} else {
|
|
181
|
+
sessionModels.push({
|
|
182
|
+
...entry,
|
|
183
|
+
cost: safeCost,
|
|
184
|
+
tokensInput: safeInput,
|
|
185
|
+
tokensOutput: safeOutput,
|
|
186
|
+
tokensReasoning: safeReasoning,
|
|
187
|
+
tokensCacheRead: safeCacheRead,
|
|
188
|
+
tokensCacheWrite: safeCacheWrite,
|
|
189
|
+
messageCount: 1
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
setModelState({
|
|
193
|
+
...current,
|
|
194
|
+
[sessionID]: sessionModels
|
|
195
|
+
});
|
|
196
|
+
scheduleSave(sessionID);
|
|
197
|
+
return existingIdx < 0;
|
|
198
|
+
}
|
|
199
|
+
function trackModel(eventSessionID, entry, cost, tokens) {
|
|
200
|
+
const isNew = upsertModel(eventSessionID, entry, cost, tokens);
|
|
201
|
+
if (isNew && Date.now() - lastToastTime > 2e3) {
|
|
202
|
+
lastToastTime = Date.now();
|
|
203
|
+
api.ui?.toast?.({
|
|
204
|
+
message: `${entry.agent}: ${entry.model}`,
|
|
205
|
+
variant: "success"
|
|
206
|
+
});
|
|
207
|
+
}
|
|
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);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
unsub = api.event?.on?.("message.updated", (event) => {
|
|
222
|
+
const info = event?.properties?.info;
|
|
223
|
+
if (!info) return;
|
|
224
|
+
const eventSessionID = info.sessionID;
|
|
225
|
+
if (!eventSessionID) return;
|
|
226
|
+
let provider;
|
|
227
|
+
let model;
|
|
228
|
+
let agent;
|
|
229
|
+
let cost = 0;
|
|
230
|
+
let tokens = {};
|
|
231
|
+
if (info.role === "user") {
|
|
232
|
+
const mdl = info.model;
|
|
233
|
+
provider = mdl?.providerID ?? UNKNOWN_ID;
|
|
234
|
+
model = mdl?.modelID ?? UNKNOWN_ID;
|
|
235
|
+
agent = info.agent ?? DEFAULT_AGENT;
|
|
236
|
+
} else if (info.role === "assistant") {
|
|
237
|
+
provider = info.providerID ?? UNKNOWN_ID;
|
|
238
|
+
model = info.modelID ?? UNKNOWN_ID;
|
|
239
|
+
agent = info.agent ?? info.mode ?? DEFAULT_AGENT;
|
|
240
|
+
cost = safeNum(info.cost);
|
|
241
|
+
tokens = {
|
|
242
|
+
input: safeNum(info.tokens?.input),
|
|
243
|
+
output: safeNum(info.tokens?.output),
|
|
244
|
+
reasoning: safeNum(info.tokens?.reasoning),
|
|
245
|
+
cacheRead: safeNum(info.tokens?.cache?.read),
|
|
246
|
+
cacheWrite: safeNum(info.tokens?.cache?.write)
|
|
247
|
+
};
|
|
248
|
+
} else {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
trackModel(eventSessionID, { provider, model, agent }, cost, tokens);
|
|
252
|
+
});
|
|
253
|
+
api.slots?.register?.({
|
|
254
|
+
order: 200,
|
|
255
|
+
slots: {
|
|
256
|
+
sidebar_content(ctx, props) {
|
|
257
|
+
const sessionID = props.session_id ?? resolveRouteSessionID(api) ?? "";
|
|
258
|
+
if (sessionID) loadSession(sessionID);
|
|
259
|
+
const models = sessionID ? modelState()[sessionID] ?? [] : [];
|
|
260
|
+
if (!sessionID || models.length === 0) {
|
|
261
|
+
return /* @__PURE__ */ jsxs("box", { flexDirection: "column", children: [
|
|
262
|
+
/* @__PURE__ */ jsx("text", { fg: ctx.theme.current.text, children: "\u{1F9E0} Models" }),
|
|
263
|
+
/* @__PURE__ */ jsx("text", { fg: ctx.theme.current.textMuted, children: sessionID ? "waiting for messages..." : "open a session to track models" })
|
|
264
|
+
] });
|
|
265
|
+
}
|
|
266
|
+
const totalCost = models.reduce((sum, m) => sum + m.cost, 0);
|
|
267
|
+
const totalTokens = models.reduce(
|
|
268
|
+
(sum, m) => sum + modelTokens(m),
|
|
269
|
+
0
|
|
176
270
|
);
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
children: [
|
|
189
|
-
/* @__PURE__ */ jsxs(
|
|
190
|
-
"box",
|
|
191
|
-
{
|
|
192
|
-
flexDirection: "row",
|
|
193
|
-
justifyContent: "space-between",
|
|
194
|
-
children: [
|
|
195
|
-
/* @__PURE__ */ jsxs("box", { flexDirection: "row", children: [
|
|
196
|
-
/* @__PURE__ */ jsxs(
|
|
197
|
-
"text",
|
|
198
|
-
{
|
|
199
|
-
fg: ctx.theme.current.text,
|
|
200
|
-
bold: true,
|
|
201
|
-
children: [
|
|
202
|
-
expanded() ? "\u25BC" : "\u25B6",
|
|
203
|
-
" \u{1F9E0} Models"
|
|
204
|
-
]
|
|
205
|
-
}
|
|
206
|
-
),
|
|
207
|
-
/* @__PURE__ */ jsx("text", { fg: ctx.theme.current.textMuted, children: " 0.1.0" })
|
|
271
|
+
return /* @__PURE__ */ jsxs("box", { flexDirection: "column", children: [
|
|
272
|
+
/* @__PURE__ */ jsxs(
|
|
273
|
+
"box",
|
|
274
|
+
{
|
|
275
|
+
flexDirection: "row",
|
|
276
|
+
justifyContent: "space-between",
|
|
277
|
+
children: [
|
|
278
|
+
/* @__PURE__ */ jsxs("box", { flexDirection: "row", children: [
|
|
279
|
+
/* @__PURE__ */ jsxs("text", { fg: ctx.theme.current.text, children: [
|
|
280
|
+
expanded() ? "\u25BC" : "\u25B6",
|
|
281
|
+
" \u{1F9E0} Models"
|
|
208
282
|
] }),
|
|
209
|
-
/* @__PURE__ */
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
] },
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
283
|
+
/* @__PURE__ */ jsxs("text", { fg: ctx.theme.current.textMuted, children: [
|
|
284
|
+
" ",
|
|
285
|
+
version
|
|
286
|
+
] })
|
|
287
|
+
] }),
|
|
288
|
+
/* @__PURE__ */ jsx("text", { fg: ctx.theme.current.text, children: totalCost > 0 ? `${fmtCost(totalCost)} \xB7 ${fmtTokens(totalTokens)}` : fmtTokens(totalTokens) })
|
|
289
|
+
]
|
|
290
|
+
}
|
|
291
|
+
),
|
|
292
|
+
expanded() && models.map((m) => /* @__PURE__ */ jsxs("box", { flexDirection: "column", children: [
|
|
293
|
+
/* @__PURE__ */ jsxs("text", { fg: ctx.theme.current.text, children: [
|
|
294
|
+
m.agent,
|
|
295
|
+
":"
|
|
296
|
+
] }),
|
|
297
|
+
/* @__PURE__ */ jsxs("box", { flexDirection: "row", justifyContent: "space-between", children: [
|
|
298
|
+
/* @__PURE__ */ jsx("text", { fg: ctx.theme.current.text, children: " " + m.model }),
|
|
299
|
+
/* @__PURE__ */ jsx("text", { fg: ctx.theme.current.textMuted, children: m.cost > 0 ? `${fmtCost(m.cost)}${modelTokens(m) > 0 ? ` \xB7 ${fmtTokens(modelTokens(m))}` : ""}` : modelTokens(m) > 0 ? fmtTokens(modelTokens(m)) : "-" })
|
|
300
|
+
] })
|
|
301
|
+
] }))
|
|
302
|
+
] });
|
|
303
|
+
}
|
|
226
304
|
}
|
|
227
|
-
}
|
|
305
|
+
});
|
|
228
306
|
});
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
307
|
+
} catch (err) {
|
|
308
|
+
cleanup();
|
|
309
|
+
api.ui?.toast?.({
|
|
310
|
+
message: "usage-total TUI failed to initialize",
|
|
311
|
+
variant: "error"
|
|
233
312
|
});
|
|
234
|
-
|
|
313
|
+
throw err;
|
|
314
|
+
}
|
|
235
315
|
};
|
|
236
316
|
var usage_total_tui_default = { id: "usage-total", tui };
|
|
237
317
|
export {
|
package/dist/usage-total.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-usage-total",
|
|
3
|
-
"version": "0.1.
|
|
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
|
}
|