@oh-my-sidebar/opencode-session-tokens 0.1.0 → 0.1.1
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/tui.tsx +66 -53
- package/package.json +9 -10
package/dist/tui.tsx
CHANGED
|
@@ -1,79 +1,90 @@
|
|
|
1
1
|
/** @jsxImportSource @opentui/solid */
|
|
2
|
-
import { createMemo, createSignal, For, Show } from "solid-js"
|
|
3
|
-
import type { TuiPlugin, TuiPluginApi, TuiPluginModule } from "@opencode-ai/plugin/tui"
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import type { TuiPlugin, TuiPluginApi, TuiPluginModule } from "@opencode-ai/plugin/tui";
|
|
4
|
+
import { createMemo, createSignal, For, Show } from "solid-js";
|
|
5
|
+
|
|
6
|
+
const MAX_MODEL_ROWS = 10;
|
|
7
|
+
const INT_FORMATTER = new Intl.NumberFormat("en-US");
|
|
7
8
|
|
|
8
9
|
function safeNumber(value: unknown): number {
|
|
9
|
-
return typeof value === "number" && Number.isFinite(value) ? value : 0
|
|
10
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
function spentTokenCount(tokens:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
function spentTokenCount(tokens: {
|
|
14
|
+
input?: unknown;
|
|
15
|
+
output?: unknown;
|
|
16
|
+
reasoning?: unknown;
|
|
17
|
+
cache?: { write?: unknown };
|
|
18
|
+
}): number {
|
|
19
|
+
const input = safeNumber(tokens?.input);
|
|
20
|
+
const output = safeNumber(tokens?.output);
|
|
21
|
+
const reasoning = safeNumber(tokens?.reasoning);
|
|
22
|
+
const cacheWrite = safeNumber(tokens?.cache?.write);
|
|
23
|
+
return input + output + reasoning + cacheWrite;
|
|
18
24
|
}
|
|
19
25
|
|
|
20
26
|
function formatInt(value: number): string {
|
|
21
|
-
return INT_FORMATTER.format(Math.max(0, Math.round(value)))
|
|
27
|
+
return INT_FORMATTER.format(Math.max(0, Math.round(value)));
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
function shortModelLabel(label: string): string {
|
|
25
|
-
if (label.length <= 28) return label
|
|
26
|
-
return `${label.slice(0, 25)}
|
|
31
|
+
if (label.length <= 28) return label;
|
|
32
|
+
return `${label.slice(0, 25)}...`;
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
function View(props: { api: TuiPluginApi; sessionID: string }) {
|
|
30
|
-
const [open, setOpen] = createSignal(false)
|
|
31
|
-
const theme = () => props.api.theme.current
|
|
32
|
-
const messages = createMemo(() => props.api.state.session.messages(props.sessionID)
|
|
33
|
-
const session = createMemo(() => props.api.state.session.get(props.sessionID)
|
|
36
|
+
const [open, setOpen] = createSignal(false);
|
|
37
|
+
const theme = () => props.api.theme.current;
|
|
38
|
+
const messages = createMemo(() => props.api.state.session.messages(props.sessionID));
|
|
39
|
+
const session = createMemo(() => props.api.state.session.get(props.sessionID));
|
|
34
40
|
|
|
35
41
|
const data = createMemo(() => {
|
|
36
|
-
const totals = new Map<string, number>()
|
|
37
|
-
let breakdownTotal = 0
|
|
38
|
-
const seen = new Set<string>()
|
|
42
|
+
const totals = new Map<string, number>();
|
|
43
|
+
let breakdownTotal = 0;
|
|
44
|
+
const seen = new Set<string>();
|
|
39
45
|
|
|
40
46
|
for (const message of messages()) {
|
|
41
|
-
const role = message?.role ?? message?.info?.role
|
|
42
|
-
if (role !== "assistant") continue
|
|
47
|
+
const role = message?.role ?? message?.info?.role;
|
|
48
|
+
if (role !== "assistant") continue;
|
|
43
49
|
|
|
44
|
-
const messageID = message?.id
|
|
45
|
-
if (typeof messageID === "string" && seen.has(messageID)) continue
|
|
46
|
-
if (typeof messageID === "string") seen.add(messageID)
|
|
50
|
+
const messageID = message?.id;
|
|
51
|
+
if (typeof messageID === "string" && seen.has(messageID)) continue;
|
|
52
|
+
if (typeof messageID === "string") seen.add(messageID);
|
|
47
53
|
|
|
48
|
-
const count = spentTokenCount(message?.tokens)
|
|
49
|
-
if (count <= 0) continue
|
|
54
|
+
const count = spentTokenCount(message?.tokens);
|
|
55
|
+
if (count <= 0) continue;
|
|
50
56
|
|
|
51
|
-
const modelID = message?.modelID ?? message?.info?.modelID ?? "unknown"
|
|
57
|
+
const modelID = message?.modelID ?? message?.info?.modelID ?? "unknown";
|
|
52
58
|
|
|
53
|
-
breakdownTotal += count
|
|
54
|
-
totals.set(modelID, (totals.get(modelID) ?? 0) + count)
|
|
59
|
+
breakdownTotal += count;
|
|
60
|
+
totals.set(modelID, (totals.get(modelID) ?? 0) + count);
|
|
55
61
|
}
|
|
56
62
|
|
|
57
63
|
const perModel = [...totals.entries()]
|
|
58
64
|
.map(([model, tokens]) => ({ model, tokens }))
|
|
59
|
-
.sort((a, b) => b.tokens - a.tokens)
|
|
65
|
+
.sort((a, b) => b.tokens - a.tokens);
|
|
60
66
|
|
|
61
|
-
const sessionTotal = spentTokenCount(session()?.tokens)
|
|
62
|
-
const total = breakdownTotal > 0 ? breakdownTotal : sessionTotal
|
|
67
|
+
const sessionTotal = spentTokenCount(session()?.tokens);
|
|
68
|
+
const total = breakdownTotal > 0 ? breakdownTotal : sessionTotal;
|
|
63
69
|
|
|
64
70
|
return {
|
|
65
71
|
total,
|
|
66
72
|
perModel,
|
|
67
|
-
}
|
|
68
|
-
})
|
|
73
|
+
};
|
|
74
|
+
});
|
|
69
75
|
|
|
70
|
-
const show = createMemo(() => data().total > 0)
|
|
71
|
-
const canExpand = createMemo(() => data().perModel.length > 0)
|
|
76
|
+
const show = createMemo(() => data().total > 0);
|
|
77
|
+
const canExpand = createMemo(() => data().perModel.length > 0);
|
|
72
78
|
|
|
73
79
|
return (
|
|
74
80
|
<Show when={show()}>
|
|
75
81
|
<box>
|
|
76
|
-
<
|
|
82
|
+
<button
|
|
83
|
+
type="button"
|
|
84
|
+
flexDirection="row"
|
|
85
|
+
gap={1}
|
|
86
|
+
onMouseDown={() => canExpand() && setOpen((x) => !x)}
|
|
87
|
+
>
|
|
77
88
|
<Show when={canExpand()}>
|
|
78
89
|
<text fg={theme().text}>{open() ? "▼" : "▶"}</text>
|
|
79
90
|
</Show>
|
|
@@ -81,23 +92,25 @@ function View(props: { api: TuiPluginApi; sessionID: string }) {
|
|
|
81
92
|
<b>Session Tokens</b>
|
|
82
93
|
</text>
|
|
83
94
|
<text fg={theme().textMuted}>{formatInt(data().total)}</text>
|
|
84
|
-
</
|
|
95
|
+
</button>
|
|
85
96
|
|
|
86
97
|
<Show when={canExpand() && open()}>
|
|
87
|
-
<For each={data().perModel.slice(0, MAX_MODEL_ROWS)}>
|
|
88
|
-
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
98
|
+
<For each={data().perModel.slice(0, MAX_MODEL_ROWS)}>
|
|
99
|
+
{(row) => (
|
|
100
|
+
<box flexDirection="row">
|
|
101
|
+
<text fg={theme().textMuted}>{shortModelLabel(row.model)}</text>
|
|
102
|
+
<box flexGrow={1} />
|
|
103
|
+
<text fg={theme().textMuted}>{formatInt(row.tokens)}</text>
|
|
104
|
+
</box>
|
|
105
|
+
)}
|
|
106
|
+
</For>
|
|
94
107
|
<Show when={data().perModel.length > MAX_MODEL_ROWS}>
|
|
95
108
|
<text fg={theme().textMuted}>+{data().perModel.length - MAX_MODEL_ROWS} more</text>
|
|
96
109
|
</Show>
|
|
97
110
|
</Show>
|
|
98
111
|
</box>
|
|
99
112
|
</Show>
|
|
100
|
-
)
|
|
113
|
+
);
|
|
101
114
|
}
|
|
102
115
|
|
|
103
116
|
const tui: TuiPlugin = async (api) => {
|
|
@@ -105,15 +118,15 @@ const tui: TuiPlugin = async (api) => {
|
|
|
105
118
|
order: 120,
|
|
106
119
|
slots: {
|
|
107
120
|
sidebar_content(_ctx, props) {
|
|
108
|
-
return <View api={api} sessionID={props.session_id}
|
|
121
|
+
return <View api={api} sessionID={props.session_id} />;
|
|
109
122
|
},
|
|
110
123
|
},
|
|
111
|
-
})
|
|
112
|
-
}
|
|
124
|
+
});
|
|
125
|
+
};
|
|
113
126
|
|
|
114
127
|
const plugin: TuiPluginModule & { id: string } = {
|
|
115
128
|
id: "oh-my-sidebar.session-tokens",
|
|
116
129
|
tui,
|
|
117
|
-
}
|
|
130
|
+
};
|
|
118
131
|
|
|
119
|
-
export default plugin
|
|
132
|
+
export default plugin;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-sidebar/opencode-session-tokens",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "OpenCode TUI sidebar plugin for session token usage breakdown",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -19,10 +19,6 @@
|
|
|
19
19
|
"dist",
|
|
20
20
|
"README.md"
|
|
21
21
|
],
|
|
22
|
-
"scripts": {
|
|
23
|
-
"build": "rm -rf dist && mkdir -p dist && cp src/index.tsx dist/tui.tsx",
|
|
24
|
-
"prepublishOnly": "pnpm build"
|
|
25
|
-
},
|
|
26
22
|
"publishConfig": {
|
|
27
23
|
"access": "public"
|
|
28
24
|
},
|
|
@@ -37,9 +33,12 @@
|
|
|
37
33
|
"opencode": ">=1.4.3"
|
|
38
34
|
},
|
|
39
35
|
"dependencies": {
|
|
40
|
-
"@opencode-ai/plugin": "
|
|
41
|
-
"@opentui/core": "
|
|
42
|
-
"@opentui/solid": "
|
|
43
|
-
"solid-js": "
|
|
36
|
+
"@opencode-ai/plugin": "^1.17.10",
|
|
37
|
+
"@opentui/core": "^0.4.2",
|
|
38
|
+
"@opentui/solid": "^0.4.2",
|
|
39
|
+
"solid-js": "1.9.12"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "rm -rf dist && mkdir -p dist && cp src/index.tsx dist/tui.tsx"
|
|
44
43
|
}
|
|
45
|
-
}
|
|
44
|
+
}
|