@oh-my-sidebar/opencode-session-tokens 0.1.0
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/README.md +32 -0
- package/dist/tui.tsx +119 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# @oh-my-sidebar/opencode-session-tokens
|
|
2
|
+
|
|
3
|
+
OpenCode TUI sidebar plugin that displays the current session's token usage breakdown by model.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Total session token count
|
|
8
|
+
- Per-model token breakdown
|
|
9
|
+
- Expandable/collapsible details view
|
|
10
|
+
- Supports input, output, reasoning, and cache write tokens
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @oh-my-sidebar/opencode-session-tokens
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
Add the plugin to your OpenCode TUI configuration (`tui.jsonc`):
|
|
21
|
+
|
|
22
|
+
```jsonc
|
|
23
|
+
{
|
|
24
|
+
"plugin": [
|
|
25
|
+
"@oh-my-sidebar/opencode-session-tokens"
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## License
|
|
31
|
+
|
|
32
|
+
MIT
|
package/dist/tui.tsx
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
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
|
+
|
|
5
|
+
const MAX_MODEL_ROWS = 10
|
|
6
|
+
const INT_FORMATTER = new Intl.NumberFormat("en-US")
|
|
7
|
+
|
|
8
|
+
function safeNumber(value: unknown): number {
|
|
9
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function spentTokenCount(tokens: any): number {
|
|
13
|
+
const input = safeNumber(tokens?.input)
|
|
14
|
+
const output = safeNumber(tokens?.output)
|
|
15
|
+
const reasoning = safeNumber(tokens?.reasoning)
|
|
16
|
+
const cacheWrite = safeNumber(tokens?.cache?.write)
|
|
17
|
+
return input + output + reasoning + cacheWrite
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function formatInt(value: number): string {
|
|
21
|
+
return INT_FORMATTER.format(Math.max(0, Math.round(value)))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function shortModelLabel(label: string): string {
|
|
25
|
+
if (label.length <= 28) return label
|
|
26
|
+
return `${label.slice(0, 25)}...`
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
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) as any[])
|
|
33
|
+
const session = createMemo(() => props.api.state.session.get(props.sessionID) as any)
|
|
34
|
+
|
|
35
|
+
const data = createMemo(() => {
|
|
36
|
+
const totals = new Map<string, number>()
|
|
37
|
+
let breakdownTotal = 0
|
|
38
|
+
const seen = new Set<string>()
|
|
39
|
+
|
|
40
|
+
for (const message of messages()) {
|
|
41
|
+
const role = message?.role ?? message?.info?.role
|
|
42
|
+
if (role !== "assistant") continue
|
|
43
|
+
|
|
44
|
+
const messageID = message?.id
|
|
45
|
+
if (typeof messageID === "string" && seen.has(messageID)) continue
|
|
46
|
+
if (typeof messageID === "string") seen.add(messageID)
|
|
47
|
+
|
|
48
|
+
const count = spentTokenCount(message?.tokens)
|
|
49
|
+
if (count <= 0) continue
|
|
50
|
+
|
|
51
|
+
const modelID = message?.modelID ?? message?.info?.modelID ?? "unknown"
|
|
52
|
+
|
|
53
|
+
breakdownTotal += count
|
|
54
|
+
totals.set(modelID, (totals.get(modelID) ?? 0) + count)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const perModel = [...totals.entries()]
|
|
58
|
+
.map(([model, tokens]) => ({ model, tokens }))
|
|
59
|
+
.sort((a, b) => b.tokens - a.tokens)
|
|
60
|
+
|
|
61
|
+
const sessionTotal = spentTokenCount(session()?.tokens)
|
|
62
|
+
const total = breakdownTotal > 0 ? breakdownTotal : sessionTotal
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
total,
|
|
66
|
+
perModel,
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
const show = createMemo(() => data().total > 0)
|
|
71
|
+
const canExpand = createMemo(() => data().perModel.length > 0)
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<Show when={show()}>
|
|
75
|
+
<box>
|
|
76
|
+
<box flexDirection="row" gap={1} onMouseDown={() => canExpand() && setOpen((x) => !x)}>
|
|
77
|
+
<Show when={canExpand()}>
|
|
78
|
+
<text fg={theme().text}>{open() ? "▼" : "▶"}</text>
|
|
79
|
+
</Show>
|
|
80
|
+
<text fg={theme().text}>
|
|
81
|
+
<b>Session Tokens</b>
|
|
82
|
+
</text>
|
|
83
|
+
<text fg={theme().textMuted}>{formatInt(data().total)}</text>
|
|
84
|
+
</box>
|
|
85
|
+
|
|
86
|
+
<Show when={canExpand() && open()}>
|
|
87
|
+
<For each={data().perModel.slice(0, MAX_MODEL_ROWS)}>{(row) => (
|
|
88
|
+
<box flexDirection="row">
|
|
89
|
+
<text fg={theme().textMuted}>{shortModelLabel(row.model)}</text>
|
|
90
|
+
<box flexGrow={1} />
|
|
91
|
+
<text fg={theme().textMuted}>{formatInt(row.tokens)}</text>
|
|
92
|
+
</box>
|
|
93
|
+
)}</For>
|
|
94
|
+
<Show when={data().perModel.length > MAX_MODEL_ROWS}>
|
|
95
|
+
<text fg={theme().textMuted}>+{data().perModel.length - MAX_MODEL_ROWS} more</text>
|
|
96
|
+
</Show>
|
|
97
|
+
</Show>
|
|
98
|
+
</box>
|
|
99
|
+
</Show>
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const tui: TuiPlugin = async (api) => {
|
|
104
|
+
api.slots.register({
|
|
105
|
+
order: 120,
|
|
106
|
+
slots: {
|
|
107
|
+
sidebar_content(_ctx, props) {
|
|
108
|
+
return <View api={api} sessionID={props.session_id} />
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const plugin: TuiPluginModule & { id: string } = {
|
|
115
|
+
id: "oh-my-sidebar.session-tokens",
|
|
116
|
+
tui,
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export default plugin
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@oh-my-sidebar/opencode-session-tokens",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OpenCode TUI sidebar plugin for session token usage breakdown",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/edso404/oh-my-sidebar",
|
|
8
|
+
"directory": "packages/session-tokens"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/edso404/oh-my-sidebar/tree/main/packages/session-tokens",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/edso404/oh-my-sidebar/issues"
|
|
13
|
+
},
|
|
14
|
+
"type": "module",
|
|
15
|
+
"exports": {
|
|
16
|
+
"./tui": "./dist/tui.tsx"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "rm -rf dist && mkdir -p dist && cp src/index.tsx dist/tui.tsx",
|
|
24
|
+
"prepublishOnly": "pnpm build"
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"opencode",
|
|
31
|
+
"opencode-plugin",
|
|
32
|
+
"sidebar",
|
|
33
|
+
"tokens"
|
|
34
|
+
],
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"engines": {
|
|
37
|
+
"opencode": ">=1.4.3"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@opencode-ai/plugin": "catalog:",
|
|
41
|
+
"@opentui/core": "catalog:",
|
|
42
|
+
"@opentui/solid": "catalog:",
|
|
43
|
+
"solid-js": "catalog:"
|
|
44
|
+
}
|
|
45
|
+
}
|