@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.
Files changed (3) hide show
  1. package/README.md +32 -0
  2. package/dist/tui.tsx +119 -0
  3. 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
+ }