opencode-models-usage-plugin 0.1.0 → 1.0.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/README.ja.md +113 -0
- package/README.ko.md +113 -0
- package/README.md +126 -0
- package/README.zh-CN.md +127 -0
- package/package.json +2 -2
- package/src/assets/img.png +0 -0
- package/src/session-usage.test.ts +58 -60
- package/src/session-usage.ts +48 -7
- package/src/tui.tsx +91 -25
package/README.ja.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# opencode-models-usage-plugin
|
|
2
|
+
|
|
3
|
+
**言語:** [English](./README.md) | [简体中文](./README.zh-CN.md) | 日本語 | [한국어](./README.ko.md)
|
|
4
|
+
|
|
5
|
+
**ついに、OpenCode のサイドバーで「AI モデルチームが今何をしているか」をリアルタイムで見られるようになりました。**
|
|
6
|
+
|
|
7
|
+
複数モデルや多段 Agent、さらに OMO のサブセッションまで使って本格的に Coding していると、こんな不安を感じることはありませんか?
|
|
8
|
+
|
|
9
|
+
- 今どのモデルが出力中なのか?速度は速いのか遅いのか?
|
|
10
|
+
- すでにどれだけ tokens を消費したのか?サブ Agent が別のモデルを勝手に呼んでいないか?
|
|
11
|
+
- キャッシュのヒット状況はどうか?この coding plan はまだ持ちそうか?
|
|
12
|
+
- コストが急に跳ね上がらないか?
|
|
13
|
+
|
|
14
|
+
**もう、そうした不安に振り回される必要はありません。**
|
|
15
|
+
|
|
16
|
+
`opencode-models-usage-plugin` は、現在のセッションツリー全体におけるモデル使用状況を見える化します。何が起きているかを把握しながら、より安心して AI チームを動かせるようになります。
|
|
17
|
+
|
|
18
|
+

|
|
19
|
+
|
|
20
|
+
## 何が表示されるのか
|
|
21
|
+
|
|
22
|
+
OpenCode のサイドバーで、現在のセッションツリー(再帰的な子セッションを含む)に登場した各モデルの使用状況を **provider/model** 単位で集約表示します。
|
|
23
|
+
|
|
24
|
+
- **Live TPS**: ストリーミング出力中の tokens per second。アイドル時は `—` に戻ります
|
|
25
|
+
- **Context Tokens**: そのモデルの直近の返信に対応する token 総数
|
|
26
|
+
- **Session Tokens**: 現在のセッション内でそのモデルが累積消費した token 総数
|
|
27
|
+
- **Session Cached**: 現在のセッション内でそのモデルが累積でキャッシュヒットした token 数
|
|
28
|
+
- **Total Cost**: 現時点までの累積コスト(`spent $x.xxxx` 形式)
|
|
29
|
+
|
|
30
|
+
モデルが現在アクティブに出力しているときは、モデル名が**ハイライト表示**されるため、「今どのモデルが動いているか」を一目で把握できます。
|
|
31
|
+
|
|
32
|
+
## サイドバー表示例
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
📊 Models Usage
|
|
36
|
+
● OpenAI/GPT-5.4 Fast
|
|
37
|
+
■ TPS 23.1
|
|
38
|
+
■ Context Tokens 12,345
|
|
39
|
+
■ Session Tokens 84,163
|
|
40
|
+
■ Session Cached 81,792
|
|
41
|
+
■ spent $0.0000
|
|
42
|
+
|
|
43
|
+
● Google/Gemini-2.5-Pro
|
|
44
|
+
■ TPS —
|
|
45
|
+
■ Context Tokens 8,942
|
|
46
|
+
■ Session Tokens 45,672
|
|
47
|
+
■ Session Cached 32,100
|
|
48
|
+
■ spent $0.0123
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## これが重要な理由
|
|
52
|
+
|
|
53
|
+
- **完全な透明性**: OMO サブ Agent や再帰呼び出しによる使用量も自動で集約され、見えないコストがなくなります
|
|
54
|
+
- **リアルタイム把握**: Live TPS、キャッシュヒット、累積コストをその場で確認できます
|
|
55
|
+
- **判断しやすい**: 遅いモデルやキャッシュ効率の良いモデルを素早く見分け、戦略を調整できます
|
|
56
|
+
- **安心して Coding**: 突然の高額請求や token 枯渇を気にしすぎず、開発そのものに集中できます
|
|
57
|
+
|
|
58
|
+
## 動作要件
|
|
59
|
+
|
|
60
|
+
- OpenCode `>= 1.4.3`
|
|
61
|
+
|
|
62
|
+
## インストール
|
|
63
|
+
|
|
64
|
+
### 方法 1: グローバル TUI 設定を手動で更新する(初回におすすめ)
|
|
65
|
+
|
|
66
|
+
以下のグローバル設定ファイルを開くか、新規作成します。
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
~/.config/opencode/tui.json
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
次の内容を追加してください。
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"$schema": "https://opencode.ai/tui.json",
|
|
77
|
+
"plugin": [
|
|
78
|
+
"opencode-models-usage-plugin/tui"
|
|
79
|
+
],
|
|
80
|
+
"plugin_enabled": {
|
|
81
|
+
"session-model-usage": true
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
保存して OpenCode を再起動すると、npm 経由で自動インストールされます。
|
|
87
|
+
|
|
88
|
+
### 方法 2: CLI でワンコマンドインストール
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
opencode plugin opencode-models-usage-plugin --global
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
このコマンドでプラグインのインストールとグローバル設定の更新を自動で行えます。
|
|
95
|
+
|
|
96
|
+
## 技術メモ
|
|
97
|
+
|
|
98
|
+
- データは provider/model label 単位で厳密に集約されます
|
|
99
|
+
- OMO サブ Agent や再帰的な子セッションも完全にサポートします
|
|
100
|
+
- TPS は live-only で、ストリーミング中のみ更新され、アイドル時には `—` に戻ります
|
|
101
|
+
- Context Tokens は直近の返信に対応します
|
|
102
|
+
- Session Tokens と Session Cached は現在のセッションツリー全体での累積値です
|
|
103
|
+
|
|
104
|
+
## パッケージ情報
|
|
105
|
+
|
|
106
|
+
- npm: `opencode-models-usage-plugin`
|
|
107
|
+
- 現在のバージョン: `0.1.0`
|
|
108
|
+
|
|
109
|
+
## License
|
|
110
|
+
|
|
111
|
+
`Apache-2.0`
|
|
112
|
+
|
|
113
|
+
インストール後、ぜひすぐに試してみてください。サイドバーにモデルの状態が整然と並ぶのを見た瞬間、AI Coding がもっと落ち着いて、もっと扱いやすく感じられるはずです。
|
package/README.ko.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# opencode-models-usage-plugin
|
|
2
|
+
|
|
3
|
+
**언어:** [English](./README.md) | [简体中文](./README.zh-CN.md) | [日本語](./README.ja.md) | 한국어
|
|
4
|
+
|
|
5
|
+
**이제 OpenCode 사이드바에서 “내 AI 모델 팀이 지금 무엇을 하고 있는지”를 실시간으로 볼 수 있습니다.**
|
|
6
|
+
|
|
7
|
+
여러 모델과 다층 Agent, 그리고 OMO 하위 세션까지 함께 사용하며 Coding 할 때 이런 불안을 느낀 적이 있지 않나요?
|
|
8
|
+
|
|
9
|
+
- 지금 실제로 어느 모델이 출력 중인지? 속도는 빠른지 느린지?
|
|
10
|
+
- 이미 얼마나 많은 tokens 를 소비했는지? 하위 Agent 가 다른 모델을 몰래 호출하고 있지는 않은지?
|
|
11
|
+
- 캐시 적중은 얼마나 잘 되고 있는지? 지금 coding plan 이 얼마나 더 버틸 수 있는지?
|
|
12
|
+
- 비용이 갑자기 크게 늘어나지는 않을지?
|
|
13
|
+
|
|
14
|
+
**이제 그런 불안에서 훨씬 자유로워질 수 있습니다.**
|
|
15
|
+
|
|
16
|
+
`opencode-models-usage-plugin` 은 현재 세션 트리 전체에서 모델 사용 현황을 투명하게 보여줍니다. 무슨 일이 벌어지고 있는지 이해한 상태로 AI 팀을 더 자신 있게 운용할 수 있습니다.
|
|
17
|
+
|
|
18
|
+

|
|
19
|
+
|
|
20
|
+
## 무엇을 보여주나요?
|
|
21
|
+
|
|
22
|
+
OpenCode 사이드바에서 현재 세션 트리(재귀적인 하위 세션 포함)에 등장한 각 모델의 사용 현황을 **provider/model** 기준으로 집계하여 보여줍니다.
|
|
23
|
+
|
|
24
|
+
- **Live TPS**: 스트리밍 출력 중인 현재 tokens per second. idle 상태가 되면 `—` 로 돌아갑니다
|
|
25
|
+
- **Context Tokens**: 해당 모델의 가장 최근 응답에 해당하는 token 총량
|
|
26
|
+
- **Session Tokens**: 현재 세션에서 해당 모델이 누적 소비한 token 총량
|
|
27
|
+
- **Session Cached**: 현재 세션에서 해당 모델이 누적 캐시 적중한 token 총량
|
|
28
|
+
- **Total Cost**: 지금까지 발생한 누적 비용 (`spent $x.xxxx` 형식)
|
|
29
|
+
|
|
30
|
+
어떤 모델이 현재 활발히 출력 중이면 그 모델 이름이 **하이라이트**되어, 지금 누가 일을 하고 있는지 바로 알 수 있습니다.
|
|
31
|
+
|
|
32
|
+
## 사이드바 예시
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
📊 Models Usage
|
|
36
|
+
● OpenAI/GPT-5.4 Fast
|
|
37
|
+
■ TPS 23.1
|
|
38
|
+
■ Context Tokens 12,345
|
|
39
|
+
■ Session Tokens 84,163
|
|
40
|
+
■ Session Cached 81,792
|
|
41
|
+
■ spent $0.0000
|
|
42
|
+
|
|
43
|
+
● Google/Gemini-2.5-Pro
|
|
44
|
+
■ TPS —
|
|
45
|
+
■ Context Tokens 8,942
|
|
46
|
+
■ Session Tokens 45,672
|
|
47
|
+
■ Session Cached 32,100
|
|
48
|
+
■ spent $0.0123
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 왜 중요한가요?
|
|
52
|
+
|
|
53
|
+
- **완전한 투명성**: OMO 하위 Agent 와 재귀 호출에서 생긴 사용량까지 자동으로 집계되어 숨겨진 비용이 없어집니다
|
|
54
|
+
- **실시간 통제력**: Live TPS, 캐시 적중, 누적 비용을 한눈에 확인할 수 있습니다
|
|
55
|
+
- **더 나은 판단**: 느린 모델과 캐시 효율이 좋은 모델을 빠르게 구분해 전략을 조정할 수 있습니다
|
|
56
|
+
- **덜 불안한 Coding**: 갑작스러운 비용 증가나 token 고갈을 덜 걱정하고 개발에 집중할 수 있습니다
|
|
57
|
+
|
|
58
|
+
## 요구 사항
|
|
59
|
+
|
|
60
|
+
- OpenCode `>= 1.4.3`
|
|
61
|
+
|
|
62
|
+
## 설치 방법
|
|
63
|
+
|
|
64
|
+
### 방법 1: 전역 TUI 설정 파일을 수동으로 수정하기 (처음엔 이 방법 추천)
|
|
65
|
+
|
|
66
|
+
다음 전역 설정 파일을 열거나 새로 만드세요.
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
~/.config/opencode/tui.json
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
아래 내용을 추가하세요.
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"$schema": "https://opencode.ai/tui.json",
|
|
77
|
+
"plugin": [
|
|
78
|
+
"opencode-models-usage-plugin/tui"
|
|
79
|
+
],
|
|
80
|
+
"plugin_enabled": {
|
|
81
|
+
"session-model-usage": true
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
저장한 뒤 OpenCode 를 재시작하면 npm 을 통해 자동으로 설치됩니다.
|
|
87
|
+
|
|
88
|
+
### 방법 2: CLI 한 줄로 설치하기
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
opencode plugin opencode-models-usage-plugin --global
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
이 명령은 플러그인을 설치하고 전역 설정도 자동으로 갱신합니다.
|
|
95
|
+
|
|
96
|
+
## 기술 메모
|
|
97
|
+
|
|
98
|
+
- 데이터는 provider/model label 기준으로 엄격하게 집계됩니다
|
|
99
|
+
- OMO 하위 Agent 와 재귀적인 하위 세션을 완전히 지원합니다
|
|
100
|
+
- TPS 는 live-only 이며, 스트리밍 중에만 갱신되고 idle 상태가 되면 `—` 로 돌아갑니다
|
|
101
|
+
- Context Tokens 는 가장 최근 응답의 token 총량을 나타냅니다
|
|
102
|
+
- Session Tokens 와 Session Cached 는 현재 세션 트리 전체의 누적값입니다
|
|
103
|
+
|
|
104
|
+
## 패키지 정보
|
|
105
|
+
|
|
106
|
+
- npm: `opencode-models-usage-plugin`
|
|
107
|
+
- 현재 버전: `0.1.0`
|
|
108
|
+
|
|
109
|
+
## License
|
|
110
|
+
|
|
111
|
+
`Apache-2.0`
|
|
112
|
+
|
|
113
|
+
설치 후 바로 한 번 써 보세요. 사이드바에 모든 모델의 상태가 선명하게 정리되어 보이는 순간, AI Coding 이 훨씬 더 차분하고 통제 가능하게 느껴질 것입니다.
|
package/README.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# opencode-models-usage-plugin
|
|
2
|
+
|
|
3
|
+
**Languages:** English | [简体中文](./README.zh-CN.md) | [日本語](./README.ja.md) | [한국어](./README.ko.md)
|
|
4
|
+
|
|
5
|
+
**Finally, you can see exactly what your AI model team is doing — right in the OpenCode sidebar.**
|
|
6
|
+
|
|
7
|
+
When you're coding with multiple models and multi-layer agents, including OMO sub-sessions, do you ever worry about things like:
|
|
8
|
+
|
|
9
|
+
- Which model is actively generating right now? Is it fast or slow?
|
|
10
|
+
- How many tokens have already been consumed? Are sub-agents quietly calling other models?
|
|
11
|
+
- How effective is cache reuse? How long can this coding plan keep going?
|
|
12
|
+
- Is the bill about to spike?
|
|
13
|
+
|
|
14
|
+
**Those anxieties can now be a thing of the past.**
|
|
15
|
+
|
|
16
|
+
`opencode-models-usage-plugin` gives you clear visibility into model usage across the entire session tree. You can see what is happening, stay in control, and drive your AI team with much more confidence.
|
|
17
|
+
|
|
18
|
+

|
|
19
|
+
|
|
20
|
+
## What It Displays
|
|
21
|
+
|
|
22
|
+
In the OpenCode sidebar, the plugin aggregates real-time usage for every model used in the current session tree, including all recursive sub-sessions, grouped by **provider/model**:
|
|
23
|
+
|
|
24
|
+
- **Live TPS**: shows current tokens per second while a response is streaming, and automatically returns to `—` when idle
|
|
25
|
+
- **Context Tokens**: the token total for that model's most recent reply, used as the current context footprint
|
|
26
|
+
- **Session Tokens**: the cumulative token total consumed by that model in the current session
|
|
27
|
+
- **Session Cached**: the cumulative cache-hit tokens for that model in the current session, so you can see how much cache reuse is helping
|
|
28
|
+
- **Total Cost**: the amount spent so far, shown as `spent $x.xxxx`
|
|
29
|
+
|
|
30
|
+
When a model is actively streaming, its name is **highlighted** so you can instantly see who is doing the work.
|
|
31
|
+
|
|
32
|
+
## Real Sidebar Example
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
📊 Models Usage
|
|
36
|
+
● OpenAI/GPT-5.4 Fast
|
|
37
|
+
■ TPS 23.1
|
|
38
|
+
■ Context Tokens 12,345
|
|
39
|
+
■ Session Tokens 84,163
|
|
40
|
+
■ Session Cached 81,792
|
|
41
|
+
■ spent $0.0000
|
|
42
|
+
|
|
43
|
+
● Google/Gemini-2.5-Pro
|
|
44
|
+
■ TPS —
|
|
45
|
+
■ Context Tokens 8,942
|
|
46
|
+
■ Session Tokens 45,672
|
|
47
|
+
■ Session Cached 32,100
|
|
48
|
+
■ spent $0.0123
|
|
49
|
+
|
|
50
|
+
● MiniMax/MiniMax-Text-01 (sub-session)
|
|
51
|
+
■ TPS 41.7
|
|
52
|
+
■ Context Tokens ...
|
|
53
|
+
■ Session Tokens ...
|
|
54
|
+
■ Session Cached ...
|
|
55
|
+
■ spent ...
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Why It Matters
|
|
59
|
+
|
|
60
|
+
- **Total Transparency**: usage from OMO sub-agents and recursive calls is automatically aggregated, so there are no more hidden costs
|
|
61
|
+
- **Real-Time Control**: see live TPS, cache hits, and cumulative costs at a glance
|
|
62
|
+
- **Smarter Decisions**: quickly spot slow models or strong cache performers and adjust your strategy on the fly
|
|
63
|
+
- **Lower-Stress Coding**: stop worrying about surprise bills or token drain, and focus on building
|
|
64
|
+
|
|
65
|
+
Whether you are heavily using model routing, complex agent orchestration, or simply want better visibility into token usage, this plugin can make the experience far more transparent and reassuring.
|
|
66
|
+
|
|
67
|
+
## Requirements
|
|
68
|
+
|
|
69
|
+
- OpenCode `>= 1.4.3`
|
|
70
|
+
|
|
71
|
+
## Installation
|
|
72
|
+
|
|
73
|
+
### Method 1: Manually update the global TUI config (recommended for first-time setup)
|
|
74
|
+
|
|
75
|
+
Open or create the global config file:
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
~/.config/opencode/tui.json
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Add the following:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"$schema": "https://opencode.ai/tui.json",
|
|
86
|
+
"plugin": [
|
|
87
|
+
"opencode-models-usage-plugin/tui"
|
|
88
|
+
],
|
|
89
|
+
"plugin_enabled": {
|
|
90
|
+
"session-model-usage": true
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Save the file and restart OpenCode. The plugin will be installed automatically via npm.
|
|
96
|
+
|
|
97
|
+
### Method 2: One-click install via CLI
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
opencode plugin opencode-models-usage-plugin --global
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
This command installs the plugin and updates your global config automatically.
|
|
104
|
+
|
|
105
|
+
## Technical Notes
|
|
106
|
+
|
|
107
|
+
- Data is grouped strictly by provider/model label
|
|
108
|
+
- Fully supports OMO sub-agents and all recursive sub-sessions, including Gemini, MiniMax, and others
|
|
109
|
+
- TPS is strictly live-only: it updates only during streaming and returns to `—` when idle
|
|
110
|
+
- Context Tokens reflect the token total of the most recent reply
|
|
111
|
+
- Session Tokens and Session Cached reflect cumulative values across the current session tree
|
|
112
|
+
|
|
113
|
+
## Package Info
|
|
114
|
+
|
|
115
|
+
- npm: `opencode-models-usage-plugin`
|
|
116
|
+
- current version: `0.1.0`
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
`Apache-2.0`
|
|
121
|
+
|
|
122
|
+
Try it right after installing — the first time you see all your model activity laid out clearly in the sidebar, you may realize that AI coding can finally feel much calmer and far more under control.
|
|
123
|
+
|
|
124
|
+
Issues, PRs, and feedback are always welcome.
|
|
125
|
+
|
|
126
|
+
Let’s make the OpenCode model ecosystem more transparent and more usable together. 🚀
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# opencode-models-usage-plugin
|
|
2
|
+
|
|
3
|
+
**语言:** [English](./README.md) | 简体中文 | [日本語](./README.ja.md) | [한국어](./README.ko.md)
|
|
4
|
+
|
|
5
|
+
**终于,你可以在 OpenCode 侧边栏里实时看到「你的 AI 模型团队」到底在干什么了。**
|
|
6
|
+
|
|
7
|
+
当你同时使用多个模型、多层 Agent(包括 OMO 子会话)全力 Coding 时,是否经常产生这些焦虑:
|
|
8
|
+
|
|
9
|
+
- 这个模型现在到底在不在输出?速度快不快?
|
|
10
|
+
- 已经悄悄消耗了多少 tokens?子 Agent 有没有偷偷调用其他模型?
|
|
11
|
+
- 缓存命中情况怎么样?这次 coding plan 还能撑多久?
|
|
12
|
+
- 账单会不会突然暴涨?
|
|
13
|
+
|
|
14
|
+
**现在,这些担心可以彻底告别了。**
|
|
15
|
+
|
|
16
|
+
`opencode-models-usage-plugin` 让你对整个会话树中的所有模型使用情况**一目了然**、**心中有数**。你可以更放心地驱动 AI 团队为你工作,而不必再畏手畏脚、时刻提心吊胆。
|
|
17
|
+
|
|
18
|
+

|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
## 它能显示什么?
|
|
22
|
+
|
|
23
|
+
在 OpenCode 的侧边栏中,插件会按 **provider/model** 聚合展示当前会话树(含所有递归子会话)中每个模型的实时数据:
|
|
24
|
+
|
|
25
|
+
- **实时 TPS**:正在流式输出时显示当前 tokens per second,输出结束或空闲时自动变为 `—`
|
|
26
|
+
- **Context Tokens**:该模型最近一次回复对应的 token 总数,用来表示这一轮上下文占用
|
|
27
|
+
- **Session Tokens**:当前会话中该模型累计消耗的 token 总数
|
|
28
|
+
- **Session Cached**:当前会话中该模型累计缓存命中的 token 数,能直观看到缓存节省了多少
|
|
29
|
+
- **Total Cost**:当前已产生的费用(显示为 `spent $x.xxxx`)
|
|
30
|
+
|
|
31
|
+
当某个模型正在活跃输出时,它的名称会**高亮显示**,让你一眼就知道「谁在干活」。
|
|
32
|
+
|
|
33
|
+
## 侧边栏实际显示示例
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
📊 Models Usage
|
|
37
|
+
● OpenAI/GPT-5.4 Fast
|
|
38
|
+
■ TPS 23.1
|
|
39
|
+
■ Context Tokens 12,345
|
|
40
|
+
■ Session Tokens 84,163
|
|
41
|
+
■ Session Cached 81,792
|
|
42
|
+
■ spent $0.0000
|
|
43
|
+
|
|
44
|
+
● Google/Gemini-2.5-Pro
|
|
45
|
+
■ TPS —
|
|
46
|
+
■ Context Tokens 8,942
|
|
47
|
+
■ Session Tokens 45,672
|
|
48
|
+
■ Session Cached 32,100
|
|
49
|
+
■ spent $0.0123
|
|
50
|
+
|
|
51
|
+
● MiniMax/MiniMax-Text-01(子会话)
|
|
52
|
+
■ TPS 41.7
|
|
53
|
+
■ Context Tokens ...
|
|
54
|
+
■ Session Tokens ...
|
|
55
|
+
■ Session Cached ...
|
|
56
|
+
■ spent ...
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## 核心价值
|
|
60
|
+
|
|
61
|
+
- **完全透明**:OMO 子 Agent、递归调用产生的消耗都会自动聚合,不再有“黑箱烧钱”
|
|
62
|
+
- **实时掌控**:随时看到 TPS、缓存命中和累计成本,对预算和性能心里有数
|
|
63
|
+
- **高效决策**:快速判断哪个模型响应慢、哪个缓存效果好,随时优化策略
|
|
64
|
+
- **低焦虑 Coding**:告别对账单暴涨、token 耗尽的恐惧,把注意力放回创作本身
|
|
65
|
+
|
|
66
|
+
无论你是重度使用多模型路由、复杂 Agent 编排,还是只是想对 token 消耗更有掌控感,这个插件都能显著提升你的使用体验和安心感。
|
|
67
|
+
|
|
68
|
+
## 运行要求
|
|
69
|
+
|
|
70
|
+
- OpenCode `>= 1.4.3`
|
|
71
|
+
|
|
72
|
+
## 安装方式
|
|
73
|
+
|
|
74
|
+
### 方式一:手动修改全局 TUI 配置(推荐第一次使用)
|
|
75
|
+
|
|
76
|
+
打开或新建全局配置文件:
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
~/.config/opencode/tui.json
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
添加以下内容:
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"$schema": "https://opencode.ai/tui.json",
|
|
87
|
+
"plugin": [
|
|
88
|
+
"opencode-models-usage-plugin/tui"
|
|
89
|
+
],
|
|
90
|
+
"plugin_enabled": {
|
|
91
|
+
"session-model-usage": true
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
保存后重启 OpenCode,插件会自动通过 npm 安装。
|
|
97
|
+
|
|
98
|
+
### 方式二:CLI 一键安装(最便捷)
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
opencode plugin opencode-models-usage-plugin --global
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
这条命令会自动安装插件,并更新全局配置。
|
|
105
|
+
|
|
106
|
+
## 技术说明
|
|
107
|
+
|
|
108
|
+
- 数据按 provider/model label 严格聚合
|
|
109
|
+
- 完整支持 OMO 子 Agent 及所有递归子会话,Gemini、MiniMax 等子模型调用也会被统计
|
|
110
|
+
- TPS 为严格实时:仅在流式输出过程中动态更新,空闲状态自动恢复为 `—`
|
|
111
|
+
- Context Tokens 反映最近一次回复对应的 token 总数
|
|
112
|
+
- Session Tokens 与 Session Cached 反映当前整个会话树中的累计值
|
|
113
|
+
|
|
114
|
+
## 包信息
|
|
115
|
+
|
|
116
|
+
- npm:`opencode-models-usage-plugin`
|
|
117
|
+
- 当前版本:`0.1.0`
|
|
118
|
+
|
|
119
|
+
## License
|
|
120
|
+
|
|
121
|
+
`Apache-2.0`
|
|
122
|
+
|
|
123
|
+
安装后立刻试试看——当你第一次在侧边栏看到所有模型数据清晰、安静地排列在那里时,你会真切感受到:用 AI Coding,从此可以更放松、更可控。
|
|
124
|
+
|
|
125
|
+
欢迎提交 Issue、PR,或分享你的使用感受。
|
|
126
|
+
|
|
127
|
+
让我们一起让 OpenCode 的模型世界更加透明、更好用。🚀
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-models-usage-plugin",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Show per-model usage, live TPS, tokens, and cost in the OpenCode sidebar.",
|
|
5
|
-
"license": "
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
8
8
|
"./tui": {
|
|
Binary file
|
|
@@ -32,7 +32,9 @@ test("assistant message contributes all token counters and cost", () => {
|
|
|
32
32
|
models: [
|
|
33
33
|
{
|
|
34
34
|
label: "openai/gpt-4.1",
|
|
35
|
-
|
|
35
|
+
contextTokens: 28,
|
|
36
|
+
sessionTokens: 28,
|
|
37
|
+
sessionCachedTokens: 3,
|
|
36
38
|
cost: 0.05,
|
|
37
39
|
tps: 0,
|
|
38
40
|
isLiveTps: false,
|
|
@@ -41,7 +43,7 @@ test("assistant message contributes all token counters and cost", () => {
|
|
|
41
43
|
})
|
|
42
44
|
})
|
|
43
45
|
|
|
44
|
-
test("repeated model
|
|
46
|
+
test("repeated model uses the latest output-bearing message for tokens", () => {
|
|
45
47
|
const messages: SessionMessageLike[] = [
|
|
46
48
|
{
|
|
47
49
|
role: "assistant",
|
|
@@ -62,6 +64,10 @@ test("repeated model is listed once while tokens keep accumulating", () => {
|
|
|
62
64
|
tokens: {
|
|
63
65
|
input: 4,
|
|
64
66
|
output: 6,
|
|
67
|
+
cache: {
|
|
68
|
+
read: 7,
|
|
69
|
+
write: 1,
|
|
70
|
+
},
|
|
65
71
|
},
|
|
66
72
|
time: { created: 4000, completed: 5000 },
|
|
67
73
|
},
|
|
@@ -71,10 +77,12 @@ test("repeated model is listed once while tokens keep accumulating", () => {
|
|
|
71
77
|
models: [
|
|
72
78
|
{
|
|
73
79
|
label: "openai/gpt-4.1",
|
|
74
|
-
|
|
80
|
+
contextTokens: 18,
|
|
81
|
+
sessionTokens: 33,
|
|
82
|
+
sessionCachedTokens: 7,
|
|
75
83
|
cost: 0.03,
|
|
76
84
|
tps: 0,
|
|
77
|
-
isLiveTps: false,
|
|
85
|
+
isLiveTps: false,
|
|
78
86
|
},
|
|
79
87
|
],
|
|
80
88
|
})
|
|
@@ -106,8 +114,12 @@ test("multiple models preserve first-seen order", () => {
|
|
|
106
114
|
"anthropic/claude-sonnet-4",
|
|
107
115
|
"openai/gpt-4.1",
|
|
108
116
|
])
|
|
109
|
-
assert.equal(usage.models[0].
|
|
110
|
-
assert.equal(usage.models[
|
|
117
|
+
assert.equal(usage.models[0].contextTokens, 3)
|
|
118
|
+
assert.equal(usage.models[0].sessionTokens, 3)
|
|
119
|
+
assert.equal(usage.models[0].sessionCachedTokens, 0)
|
|
120
|
+
assert.equal(usage.models[1].contextTokens, 7)
|
|
121
|
+
assert.equal(usage.models[1].sessionTokens, 7)
|
|
122
|
+
assert.equal(usage.models[1].sessionCachedTokens, 0)
|
|
111
123
|
})
|
|
112
124
|
|
|
113
125
|
test("non-assistant messages are ignored", () => {
|
|
@@ -138,7 +150,9 @@ test("non-assistant messages are ignored", () => {
|
|
|
138
150
|
models: [
|
|
139
151
|
{
|
|
140
152
|
label: "openai/gpt-4.1",
|
|
141
|
-
|
|
153
|
+
contextTokens: 5,
|
|
154
|
+
sessionTokens: 5,
|
|
155
|
+
sessionCachedTokens: 0,
|
|
142
156
|
cost: 0.1,
|
|
143
157
|
tps: 0,
|
|
144
158
|
isLiveTps: false,
|
|
@@ -180,7 +194,9 @@ test("provider catalog names are used when available", () => {
|
|
|
180
194
|
models: [
|
|
181
195
|
{
|
|
182
196
|
label: "OpenAI/GPT-4.1",
|
|
183
|
-
|
|
197
|
+
contextTokens: 5,
|
|
198
|
+
sessionTokens: 5,
|
|
199
|
+
sessionCachedTokens: 0,
|
|
184
200
|
cost: 0,
|
|
185
201
|
tps: 0,
|
|
186
202
|
isLiveTps: false,
|
|
@@ -222,7 +238,9 @@ test("provider catalog also works when providers are stored as an array", () =>
|
|
|
222
238
|
models: [
|
|
223
239
|
{
|
|
224
240
|
label: "Anthropic/Claude Sonnet 4",
|
|
225
|
-
|
|
241
|
+
contextTokens: 10,
|
|
242
|
+
sessionTokens: 10,
|
|
243
|
+
sessionCachedTokens: 0,
|
|
226
244
|
cost: 0,
|
|
227
245
|
tps: 0,
|
|
228
246
|
isLiveTps: false,
|
|
@@ -243,65 +261,50 @@ test("token helper tolerates missing counters", () => {
|
|
|
243
261
|
)
|
|
244
262
|
})
|
|
245
263
|
|
|
246
|
-
test("
|
|
264
|
+
test("latest output-bearing assistant message determines displayed tokens and cache", () => {
|
|
247
265
|
const usage = getSessionUsage([
|
|
248
|
-
// valid
|
|
249
266
|
{
|
|
250
267
|
role: "assistant",
|
|
251
268
|
providerID: "openai",
|
|
252
269
|
modelID: "gpt-4",
|
|
253
|
-
tokens: {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
summary: true,
|
|
262
|
-
tokens: { output: 100 },
|
|
263
|
-
time: { created: 1000, completed: 2000 },
|
|
264
|
-
},
|
|
265
|
-
// ignored: no completed time
|
|
266
|
-
{
|
|
267
|
-
role: "assistant",
|
|
268
|
-
providerID: "openai",
|
|
269
|
-
modelID: "gpt-4",
|
|
270
|
-
tokens: { output: 100 },
|
|
271
|
-
time: { created: 1000 },
|
|
272
|
-
},
|
|
273
|
-
// ignored: duration <= 0
|
|
274
|
-
{
|
|
275
|
-
role: "assistant",
|
|
276
|
-
providerID: "openai",
|
|
277
|
-
modelID: "gpt-4",
|
|
278
|
-
tokens: { output: 100 },
|
|
279
|
-
time: { created: 1000, completed: 1000 },
|
|
280
|
-
},
|
|
281
|
-
// ignored: generated tokens <= 0
|
|
282
|
-
{
|
|
283
|
-
role: "assistant",
|
|
284
|
-
providerID: "openai",
|
|
285
|
-
modelID: "gpt-4",
|
|
286
|
-
tokens: { input: 100, output: 0 },
|
|
270
|
+
tokens: {
|
|
271
|
+
input: 5,
|
|
272
|
+
output: 10,
|
|
273
|
+
cache: {
|
|
274
|
+
read: 20,
|
|
275
|
+
write: 2,
|
|
276
|
+
},
|
|
277
|
+
},
|
|
287
278
|
time: { created: 1000, completed: 2000 },
|
|
288
279
|
},
|
|
289
|
-
// ignored: finish === "tool-calls"
|
|
290
280
|
{
|
|
291
281
|
role: "assistant",
|
|
292
282
|
providerID: "openai",
|
|
293
283
|
modelID: "gpt-4",
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
284
|
+
tokens: {
|
|
285
|
+
input: 100,
|
|
286
|
+
output: 0,
|
|
287
|
+
cache: {
|
|
288
|
+
read: 999,
|
|
289
|
+
write: 999,
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
time: { created: 3000, completed: 4000 },
|
|
297
293
|
},
|
|
298
|
-
// valid: output + reasoning
|
|
299
294
|
{
|
|
300
295
|
role: "assistant",
|
|
301
296
|
providerID: "openai",
|
|
302
297
|
modelID: "gpt-4",
|
|
303
|
-
tokens: {
|
|
304
|
-
|
|
298
|
+
tokens: {
|
|
299
|
+
input: 11,
|
|
300
|
+
output: 7,
|
|
301
|
+
reasoning: 3,
|
|
302
|
+
cache: {
|
|
303
|
+
read: 30,
|
|
304
|
+
write: 4,
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
time: { created: 5000, completed: 6000 },
|
|
305
308
|
},
|
|
306
309
|
])
|
|
307
310
|
|
|
@@ -309,18 +312,13 @@ test("tps eligibility rules are applied correctly", () => {
|
|
|
309
312
|
models: [
|
|
310
313
|
{
|
|
311
314
|
label: "openai/gpt-4",
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
// TPS: 20 tokens / 2 seconds = 10
|
|
315
|
+
contextTokens: 55,
|
|
316
|
+
sessionTokens: 2190,
|
|
317
|
+
sessionCachedTokens: 1049,
|
|
316
318
|
cost: 0,
|
|
317
319
|
tps: 0,
|
|
318
320
|
isLiveTps: false,
|
|
319
321
|
},
|
|
320
322
|
],
|
|
321
323
|
})
|
|
322
|
-
|
|
323
|
-
// Update token expectation to match manual addition
|
|
324
|
-
const total = 10 + 100 + 100 + 100 + 100 + 100 + 10;
|
|
325
|
-
assert.equal(usage.models[0].totalTokens, total)
|
|
326
324
|
})
|
package/src/session-usage.ts
CHANGED
|
@@ -26,7 +26,9 @@ export interface SessionMessageLike {
|
|
|
26
26
|
export interface ModelUsage {
|
|
27
27
|
label: string
|
|
28
28
|
tps: number
|
|
29
|
-
|
|
29
|
+
contextTokens: number
|
|
30
|
+
sessionTokens: number
|
|
31
|
+
sessionCachedTokens: number
|
|
30
32
|
cost: number
|
|
31
33
|
isLiveTps?: boolean
|
|
32
34
|
}
|
|
@@ -105,6 +107,20 @@ export interface LiveTpsState {
|
|
|
105
107
|
tpsDurationMs: number
|
|
106
108
|
}
|
|
107
109
|
|
|
110
|
+
const getMessageTimestamp = (message: SessionMessageLike, index: number): number => {
|
|
111
|
+
const completed = toNumber(message.time?.completed)
|
|
112
|
+
if (completed > 0) {
|
|
113
|
+
return completed
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const created = toNumber(message.time?.created)
|
|
117
|
+
if (created > 0) {
|
|
118
|
+
return created
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return index
|
|
122
|
+
}
|
|
123
|
+
|
|
108
124
|
export const getSessionUsage = (
|
|
109
125
|
messages: ReadonlyArray<SessionMessageLike>,
|
|
110
126
|
providers?: unknown,
|
|
@@ -112,12 +128,15 @@ export const getSessionUsage = (
|
|
|
112
128
|
): SessionUsage => {
|
|
113
129
|
const modelStats = new Map<string, {
|
|
114
130
|
label: string
|
|
115
|
-
|
|
131
|
+
contextTokens: number
|
|
132
|
+
sessionTokens: number
|
|
133
|
+
sessionCachedTokens: number
|
|
116
134
|
cost: number
|
|
135
|
+
latestTimestamp: number
|
|
117
136
|
}>()
|
|
118
137
|
const modelsUsed: string[] = []
|
|
119
138
|
|
|
120
|
-
for (const message of messages) {
|
|
139
|
+
for (const [index, message] of messages.entries()) {
|
|
121
140
|
if (message.role !== "assistant") {
|
|
122
141
|
continue
|
|
123
142
|
}
|
|
@@ -134,15 +153,32 @@ export const getSessionUsage = (
|
|
|
134
153
|
if (!modelStats.has(modelKey)) {
|
|
135
154
|
modelStats.set(modelKey, {
|
|
136
155
|
label: resolveModelLabel(modelID, providerID, providers),
|
|
137
|
-
|
|
156
|
+
contextTokens: 0,
|
|
157
|
+
sessionTokens: 0,
|
|
158
|
+
sessionCachedTokens: 0,
|
|
138
159
|
cost: 0,
|
|
160
|
+
latestTimestamp: Number.NEGATIVE_INFINITY,
|
|
139
161
|
})
|
|
140
162
|
modelsUsed.push(modelKey)
|
|
141
163
|
}
|
|
142
164
|
|
|
143
165
|
const stats = modelStats.get(modelKey)!
|
|
144
|
-
stats.totalTokens += getTokenTotal(message.tokens)
|
|
145
166
|
stats.cost += toNumber(message.cost)
|
|
167
|
+
stats.sessionTokens += getTokenTotal(message.tokens)
|
|
168
|
+
stats.sessionCachedTokens += toNumber(message.tokens?.cache?.read)
|
|
169
|
+
|
|
170
|
+
const outputTokens = toNumber(message.tokens?.output)
|
|
171
|
+
if (outputTokens <= 0) {
|
|
172
|
+
continue
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const timestamp = getMessageTimestamp(message, index)
|
|
176
|
+
if (timestamp < stats.latestTimestamp) {
|
|
177
|
+
continue
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
stats.latestTimestamp = timestamp
|
|
181
|
+
stats.contextTokens = getTokenTotal(message.tokens)
|
|
146
182
|
}
|
|
147
183
|
|
|
148
184
|
// Inject live TPS
|
|
@@ -154,8 +190,11 @@ export const getSessionUsage = (
|
|
|
154
190
|
if (!modelStats.has(liveTpsKey)) {
|
|
155
191
|
modelStats.set(liveTpsKey, {
|
|
156
192
|
label: resolveModelLabel(modelID, providerID, providers),
|
|
157
|
-
|
|
193
|
+
contextTokens: 0,
|
|
194
|
+
sessionTokens: 0,
|
|
195
|
+
sessionCachedTokens: 0,
|
|
158
196
|
cost: 0,
|
|
197
|
+
latestTimestamp: Number.NEGATIVE_INFINITY,
|
|
159
198
|
})
|
|
160
199
|
modelsUsed.push(liveTpsKey)
|
|
161
200
|
}
|
|
@@ -174,7 +213,9 @@ export const getSessionUsage = (
|
|
|
174
213
|
|
|
175
214
|
return {
|
|
176
215
|
label: stats.label,
|
|
177
|
-
|
|
216
|
+
contextTokens: stats.contextTokens,
|
|
217
|
+
sessionTokens: stats.sessionTokens,
|
|
218
|
+
sessionCachedTokens: stats.sessionCachedTokens,
|
|
178
219
|
cost: stats.cost,
|
|
179
220
|
tps,
|
|
180
221
|
isLiveTps,
|
package/src/tui.tsx
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/** @jsxImportSource @opentui/solid */
|
|
2
2
|
|
|
3
3
|
import type { TuiPlugin, TuiPluginApi, TuiPluginModule } from "@opencode-ai/plugin/tui"
|
|
4
|
-
import {
|
|
4
|
+
import type { Accessor } from "solid-js"
|
|
5
|
+
import { For, Show, createEffect, createMemo, createSignal, onCleanup, onMount } from "solid-js"
|
|
5
6
|
|
|
6
7
|
import { getSessionUsage, type SessionMessageLike, type LiveTpsState } from "./session-usage"
|
|
7
8
|
|
|
8
|
-
const pluginID = "
|
|
9
|
+
const pluginID = "session-model-usage"
|
|
9
10
|
|
|
10
11
|
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
11
12
|
typeof value === "object" && value !== null
|
|
@@ -39,10 +40,11 @@ const mapMessages = (messages: readonly unknown[]): SessionMessageLike[] => {
|
|
|
39
40
|
return messages.map((message) => mapMessage(message))
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
const useSessionMessages = (api: TuiPluginApi, rootSessionID: string) => {
|
|
43
|
+
const useSessionMessages = (api: TuiPluginApi, rootSessionID: Accessor<string>) => {
|
|
44
|
+
const [rootMessages, setRootMessages] = createSignal<SessionMessageLike[]>([])
|
|
43
45
|
const [extraMessages, setExtraMessages] = createSignal<SessionMessageLike[]>([])
|
|
44
46
|
const [trackedSessionIDs, setTrackedSessionIDs] = createSignal<ReadonlySet<string>>(
|
|
45
|
-
new Set([rootSessionID]),
|
|
47
|
+
new Set([rootSessionID()]),
|
|
46
48
|
)
|
|
47
49
|
|
|
48
50
|
const [liveInfo, setLiveInfo] = createSignal<{
|
|
@@ -53,6 +55,17 @@ const useSessionMessages = (api: TuiPluginApi, rootSessionID: string) => {
|
|
|
53
55
|
chars: number;
|
|
54
56
|
} | null>(null)
|
|
55
57
|
|
|
58
|
+
let reloadVersion = 0
|
|
59
|
+
|
|
60
|
+
const fetchRootMessages = async (sessionID: string): Promise<SessionMessageLike[]> => {
|
|
61
|
+
const result = await api.client.session.messages({ sessionID })
|
|
62
|
+
if (result.error || !result.data) {
|
|
63
|
+
return []
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return mapMessages(result.data.map((message) => message.info))
|
|
67
|
+
}
|
|
68
|
+
|
|
56
69
|
const fetchChildrenMessages = async (
|
|
57
70
|
sessionID: string,
|
|
58
71
|
visited = new Set<string>(),
|
|
@@ -70,7 +83,10 @@ const useSessionMessages = (api: TuiPluginApi, rootSessionID: string) => {
|
|
|
70
83
|
let msgs: SessionMessageLike[] = []
|
|
71
84
|
for (const child of res.data) {
|
|
72
85
|
const childID = child.id
|
|
73
|
-
visited.
|
|
86
|
+
if (visited.has(childID)) {
|
|
87
|
+
continue
|
|
88
|
+
}
|
|
89
|
+
|
|
74
90
|
const mRes = await api.client.session.messages({ sessionID: childID })
|
|
75
91
|
if (!mRes.error && mRes.data) {
|
|
76
92
|
msgs.push(...mapMessages(mRes.data.map((message) => message.info)))
|
|
@@ -81,19 +97,37 @@ const useSessionMessages = (api: TuiPluginApi, rootSessionID: string) => {
|
|
|
81
97
|
return { messages: msgs, sessionIDs: visited }
|
|
82
98
|
}
|
|
83
99
|
|
|
84
|
-
const reloadExtraMessages = async () => {
|
|
85
|
-
const
|
|
100
|
+
const reloadExtraMessages = async (sessionID: string) => {
|
|
101
|
+
const version = ++reloadVersion
|
|
102
|
+
const [root, result] = await Promise.all([
|
|
103
|
+
fetchRootMessages(sessionID),
|
|
104
|
+
fetchChildrenMessages(sessionID),
|
|
105
|
+
])
|
|
106
|
+
|
|
107
|
+
if (version !== reloadVersion) {
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
setRootMessages(root)
|
|
86
112
|
setExtraMessages(result.messages)
|
|
87
113
|
setTrackedSessionIDs(new Set(result.sessionIDs))
|
|
88
114
|
}
|
|
89
115
|
|
|
90
|
-
|
|
91
|
-
|
|
116
|
+
createEffect(() => {
|
|
117
|
+
const sessionID = rootSessionID()
|
|
118
|
+
setRootMessages([])
|
|
119
|
+
setExtraMessages([])
|
|
120
|
+
setTrackedSessionIDs(new Set([sessionID]))
|
|
121
|
+
setLiveInfo(null)
|
|
122
|
+
void reloadExtraMessages(sessionID)
|
|
123
|
+
})
|
|
92
124
|
|
|
125
|
+
onMount(() => {
|
|
93
126
|
const unsubs = [
|
|
94
127
|
api.event.on("message.part.delta", (ev) => {
|
|
95
128
|
const { sessionID, messageID, field, delta } = ev.properties
|
|
96
129
|
if (!sessionID || !messageID || typeof delta !== "string") return
|
|
130
|
+
if (!trackedSessionIDs().has(sessionID)) return
|
|
97
131
|
if (field !== "text" && field !== "reasoning") return
|
|
98
132
|
|
|
99
133
|
setLiveInfo(prev => {
|
|
@@ -102,9 +136,13 @@ const useSessionMessages = (api: TuiPluginApi, rootSessionID: string) => {
|
|
|
102
136
|
}
|
|
103
137
|
|
|
104
138
|
// Try to find the message
|
|
105
|
-
const rootMsgs = mapMessages(api.state.session.messages(rootSessionID))
|
|
139
|
+
const rootMsgs = mapMessages(api.state.session.messages(rootSessionID()))
|
|
140
|
+
const historicalRootMessages = rootMessages()
|
|
106
141
|
const extra = extraMessages()
|
|
107
142
|
let found = rootMsgs.find(m => m.id === messageID)
|
|
143
|
+
if (!found) {
|
|
144
|
+
found = historicalRootMessages.find(m => m.id === messageID)
|
|
145
|
+
}
|
|
108
146
|
if (!found) {
|
|
109
147
|
found = extra.find(m => m.id === messageID)
|
|
110
148
|
}
|
|
@@ -127,8 +165,12 @@ const useSessionMessages = (api: TuiPluginApi, rootSessionID: string) => {
|
|
|
127
165
|
}
|
|
128
166
|
})
|
|
129
167
|
}),
|
|
130
|
-
api.event.on("message.updated", () =>
|
|
131
|
-
|
|
168
|
+
api.event.on("message.updated", () => {
|
|
169
|
+
void reloadExtraMessages(rootSessionID())
|
|
170
|
+
}),
|
|
171
|
+
api.event.on("session.status", () => {
|
|
172
|
+
void reloadExtraMessages(rootSessionID())
|
|
173
|
+
}),
|
|
132
174
|
api.event.on("session.idle", (ev) => {
|
|
133
175
|
if (!trackedSessionIDs().has(ev.properties.sessionID)) {
|
|
134
176
|
return
|
|
@@ -139,7 +181,10 @@ const useSessionMessages = (api: TuiPluginApi, rootSessionID: string) => {
|
|
|
139
181
|
]
|
|
140
182
|
|
|
141
183
|
onCleanup(() => {
|
|
142
|
-
|
|
184
|
+
reloadVersion += 1
|
|
185
|
+
unsubs.forEach((unsubscribe) => {
|
|
186
|
+
unsubscribe()
|
|
187
|
+
})
|
|
143
188
|
})
|
|
144
189
|
})
|
|
145
190
|
|
|
@@ -167,36 +212,53 @@ const useSessionMessages = (api: TuiPluginApi, rootSessionID: string) => {
|
|
|
167
212
|
}
|
|
168
213
|
})
|
|
169
214
|
|
|
170
|
-
|
|
215
|
+
const usageMessages = createMemo(() => {
|
|
216
|
+
return [...rootMessages(), ...extraMessages()]
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
return { usageMessages, liveTps }
|
|
171
220
|
}
|
|
172
221
|
|
|
173
222
|
const SessionUsagePanel = (props: { api: TuiPluginApi; sessionID: string }) => {
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
223
|
+
const activeSessionID = createMemo<string>(() => {
|
|
224
|
+
const route = props.api.route.current
|
|
225
|
+
if (route.name === "session" && typeof route.params?.sessionID === "string") {
|
|
226
|
+
return route.params.sessionID
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return props.sessionID
|
|
180
230
|
})
|
|
181
231
|
|
|
232
|
+
const { usageMessages, liveTps } = useSessionMessages(props.api, activeSessionID)
|
|
233
|
+
|
|
234
|
+
const usage = createMemo(() => getSessionUsage(usageMessages(), props.api.state.provider, liveTps()))
|
|
235
|
+
|
|
182
236
|
return (
|
|
183
237
|
<box flexDirection="column">
|
|
184
|
-
<text>📊 Models
|
|
238
|
+
<text>📊 Models Usage</text>
|
|
185
239
|
{usage().models.length === 0 ? (
|
|
186
240
|
<text>None yet</text>
|
|
187
241
|
) : (
|
|
188
242
|
<For each={usage().models}>
|
|
189
243
|
{(model) => (
|
|
190
244
|
<box flexDirection="column">
|
|
191
|
-
<text>● {model.label}</text>
|
|
245
|
+
<text fg={model.isLiveTps ? "yellow" : undefined}>● {model.label}</text>
|
|
192
246
|
<box flexDirection="column" paddingLeft={2}>
|
|
193
247
|
<box flexDirection="row">
|
|
194
248
|
<text>■ TPS </text>
|
|
195
249
|
<text fg="gray">{model.isLiveTps && model.tps > 0 ? model.tps.toFixed(1) : "—"}</text>
|
|
196
250
|
</box>
|
|
197
251
|
<box flexDirection="row">
|
|
198
|
-
<text>■
|
|
199
|
-
<text fg="gray">{model.
|
|
252
|
+
<text>■ Context Tokens </text>
|
|
253
|
+
<text fg="gray">{model.contextTokens.toLocaleString("en-US")}</text>
|
|
254
|
+
</box>
|
|
255
|
+
<box flexDirection="row">
|
|
256
|
+
<text>■ Session Tokens </text>
|
|
257
|
+
<text fg="gray">{model.sessionTokens.toLocaleString("en-US")}</text>
|
|
258
|
+
</box>
|
|
259
|
+
<box flexDirection="row">
|
|
260
|
+
<text>■ Session Cached </text>
|
|
261
|
+
<text fg="gray">{model.sessionCachedTokens.toLocaleString("en-US")}</text>
|
|
200
262
|
</box>
|
|
201
263
|
<box flexDirection="row">
|
|
202
264
|
<text>■ spent </text>
|
|
@@ -216,7 +278,11 @@ const tui: TuiPlugin = async (api) => {
|
|
|
216
278
|
order: 150,
|
|
217
279
|
slots: {
|
|
218
280
|
sidebar_content(_ctx, props) {
|
|
219
|
-
return
|
|
281
|
+
return (
|
|
282
|
+
<Show when={props.session_id} keyed>
|
|
283
|
+
{(sessionID: string) => <SessionUsagePanel api={api} sessionID={sessionID} />}
|
|
284
|
+
</Show>
|
|
285
|
+
)
|
|
220
286
|
},
|
|
221
287
|
},
|
|
222
288
|
})
|