opencode-cache-hit 0.2.0 → 0.2.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/AGENTS.md +2 -1
- package/README.md +5 -18
- package/README.zh-CN.md +155 -96
- package/docs/assets/cache-hit-panel.v3.png +0 -0
- package/docs/en/design.md +22 -4
- package/docs/en/timeline-duplicate-writes.md +125 -0
- package/docs/en/timeline.md +17 -13
- package/docs/zh-CN/design.md +23 -5
- package/docs/zh-CN/timeline.md +18 -15
- package/package.json +1 -1
- package/scripts/README.md +63 -0
- package/scripts/timeline-dashboard.ts +728 -0
- package/src/agents-view.tsx +8 -8
- package/src/cache-ttl-view.tsx +3 -8
- package/src/format-cost.ts +70 -0
- package/src/format-model.ts +227 -0
- package/src/sidebar-host.tsx +6 -6
- package/src/timeline/collector.ts +40 -87
- package/src/timeline/records.ts +0 -30
- package/src/tui-panel/README.md +2 -2
- package/src/tui-panel/README.zh-CN.md +2 -2
- package/src/tui-panel/components.tsx +31 -4
- package/src/tui-panel/index.ts +6 -1
- package/src/tui-panel/palette.ts +5 -0
- package/src/version.ts +4 -1
- package/docs/assets/.gitkeep +0 -0
- package/docs/assets/cache-hit-panel.png +0 -0
package/docs/en/timeline.md
CHANGED
|
@@ -131,7 +131,7 @@ Example values above; code defaults below (`enabled: false`, rotation `0` except
|
|
|
131
131
|
1. Optional size roll **before** append.
|
|
132
132
|
2. `appendFile` one JSON line.
|
|
133
133
|
3. Optional line trim **after** append.
|
|
134
|
-
4.
|
|
134
|
+
4. Event-driven: `message.updated` → `handleMessage()` → fire-and-forget `appendFile`. No polling, no dedup.
|
|
135
135
|
|
|
136
136
|
## Rotation and retention
|
|
137
137
|
|
|
@@ -169,11 +169,11 @@ Does **not** match legacy `ses_*.jsonl` names.
|
|
|
169
169
|
|
|
170
170
|
New filename after midnight; previous days remain until cleanup runs.
|
|
171
171
|
|
|
172
|
-
###
|
|
172
|
+
### Collection
|
|
173
173
|
|
|
174
|
-
-
|
|
175
|
-
- Switching main session:
|
|
176
|
-
-
|
|
174
|
+
- `message.updated` event carries the full `Message` object. The collector subscribes directly — no polling, no dedup.
|
|
175
|
+
- Switching main session: `resetForRootChange()` clears in-memory cache; events for the new session arrive naturally.
|
|
176
|
+
- Restarts are safe: messages before startup were already written to JSONL in the previous session. No replay, no scan.
|
|
177
177
|
|
|
178
178
|
## Runtime wiring
|
|
179
179
|
|
|
@@ -181,19 +181,18 @@ New filename after midnight; previous days remain until cleanup runs.
|
|
|
181
181
|
sequenceDiagram
|
|
182
182
|
participant E as message.updated
|
|
183
183
|
participant H as sidebar-host
|
|
184
|
-
participant
|
|
184
|
+
participant C as timeline/collector
|
|
185
185
|
participant W as timeline/writer
|
|
186
186
|
|
|
187
|
-
E->>H:
|
|
188
|
-
H->>
|
|
189
|
-
alt
|
|
190
|
-
|
|
187
|
+
E->>H: { sessionID, info: Message }
|
|
188
|
+
H->>C: handleMessage(sessionID, info)
|
|
189
|
+
alt assistant and complete
|
|
190
|
+
C->>W: append JSONL (fire-and-forget)
|
|
191
191
|
end
|
|
192
192
|
```
|
|
193
193
|
|
|
194
|
-
- Child ids from `child-session-sync` / `session.list`; timeline
|
|
195
|
-
-
|
|
196
|
-
- Scope: current TUI root session + its children.
|
|
194
|
+
- Child ids from `child-session-sync` / `session.list`; timeline writes main and child sessions from events.
|
|
195
|
+
- No debounce, no polling. Scope: current TUI root session + its children.
|
|
197
196
|
|
|
198
197
|
## UI phases
|
|
199
198
|
|
|
@@ -214,8 +213,13 @@ python3 -c "import json,sys; r=[json.loads(x) for x in open(sys.argv[1]) if x.st
|
|
|
214
213
|
|
|
215
214
|
bun scripts/plot-hit-rate.ts "$LOG" -o /tmp/hit.svg
|
|
216
215
|
bun scripts/plot-hit-rate.ts "$LOG" --by-root -o /tmp/hit-multi.svg
|
|
216
|
+
|
|
217
|
+
# interactive HTML dashboard (filters, Chart.js); add --open to launch browser
|
|
218
|
+
bun scripts/timeline-dashboard.ts --open
|
|
217
219
|
```
|
|
218
220
|
|
|
221
|
+
Default log dir matches `timeline.dir` in plugin config (`~/.local/share/opencode/logs/cache-hit/`).
|
|
222
|
+
|
|
219
223
|
### Phase 2 — sidebar Timeline section (planned)
|
|
220
224
|
|
|
221
225
|
### Phase 3 — metric window linkage (planned)
|
package/docs/zh-CN/design.md
CHANGED
|
@@ -57,8 +57,8 @@ sequenceDiagram
|
|
|
57
57
|
|
|
58
58
|
Slot->>Host: sessionId, display, api
|
|
59
59
|
Host->>API: session.list → childIds
|
|
60
|
-
API-->>Host: message.updated
|
|
61
|
-
Host->>Host: refreshTick
|
|
60
|
+
API-->>Host: message.updated { info: Message }
|
|
61
|
+
Host->>Host: refreshTick++, timeline.handleMessage(info)
|
|
62
62
|
Host->>API: session.messages(sid / cid)
|
|
63
63
|
Host->>W: main, messages, subAgents
|
|
64
64
|
W->>W: aggregate / format / TuiPanel
|
|
@@ -77,6 +77,7 @@ sequenceDiagram
|
|
|
77
77
|
| `stats.ts` | 纯函数聚合(无 UI) |
|
|
78
78
|
| `session-list.ts` | `session.list` 响应解析、`childSessionIdsForParent` |
|
|
79
79
|
| `format-cost.ts` / `format-tokens.ts` / `format-cache-ui.ts` | 展示格式化(**不含** `computeHitBarWidth`,其在 `tui-panel/layout.ts`) |
|
|
80
|
+
| `format-model.ts` | 子 agent 行 label(`formatSubAgentLabel`)与厂商品牌色(`modelRowColor`) |
|
|
80
81
|
| `message-timing.ts` | SDK 时间字段辅助 |
|
|
81
82
|
| `timeline/` | 按次 JSONL(`records` / `writer` / `collector`) |
|
|
82
83
|
| `plugin-config.ts` / `load-config.ts` | 配置归一化与默认值 |
|
|
@@ -88,9 +89,9 @@ sequenceDiagram
|
|
|
88
89
|
| 模块 | 职责 |
|
|
89
90
|
|------|------|
|
|
90
91
|
| `layout.ts` | 视觉列宽、`justifyRow`、`computeHitBarWidth`、分隔线 |
|
|
91
|
-
| `palette.ts` | 主题色 →
|
|
92
|
+
| `palette.ts` | 主题色 → 面板调色板;`toneBrandHex` 用于深色面板上的厂商色 |
|
|
92
93
|
| `use-panel-layout.ts` | `createPanelLayout`(测宽)、`createSectionFold` |
|
|
93
|
-
| `components.tsx` | `TuiPanel`、`TuiHitRow`、`TuiMetricRow`
|
|
94
|
+
| `components.tsx` | `TuiPanel`、`TuiHitRow`、`TuiMetricRow`(`labelFg` / `valueFg` 分段上色)等(需 `@opentui/solid`) |
|
|
94
95
|
| `index.ts` | 对外 barrel |
|
|
95
96
|
|
|
96
97
|
纯逻辑模块(如 `use-cache-hit-metrics`)应从 `layout.ts` / `palette.ts` 直接 import,避免经 `index.ts` 拉入 JSX(便于 `bun test` 冒烟)。
|
|
@@ -130,6 +131,23 @@ flowchart TD
|
|
|
130
131
|
|
|
131
132
|
主 session 若仍有编排类调用,其 token/费用不会出现在 Agents 合计中;UI 通过 `agentsScopeHint`(「仅子会话 / sub-sessions」)标明。与 visual-cache 对主 session 的展示互补,不是漏算 bug。
|
|
132
133
|
|
|
134
|
+
### 子 session 行展示
|
|
135
|
+
|
|
136
|
+
多个子 session 并行时,**Agents** 段下每个有活动的子 session 一行(见截图 `docs/assets/cache-hit-panel.v3.png`)。
|
|
137
|
+
|
|
138
|
+
| 方面 | 行为 |
|
|
139
|
+
|------|------|
|
|
140
|
+
| **目的** | 在窄侧栏中辨认「哪条子 session、用的什么模型」 |
|
|
141
|
+
| **Label 文案** | `{displayModelName} …{sessionIdTail}` — 与主块 **Model** 行同源(`shortModelName` + 去日期尾);**不做**语义缩写(如 `ds-flash`) |
|
|
142
|
+
| **截断** | `gauge` ≈ 实测面板宽 − 边框 gutter;label 预算再减去右侧 cost/tok。优先保留**模型前缀**;先缩短 ID 尾(6 → 4 字符) |
|
|
143
|
+
| **Label 颜色** | 厂商近似品牌色(`MODEL_BRAND_HEX`,经 `toneBrandHex` 压低饱和度以适配深色终端) |
|
|
144
|
+
| **金额颜色** | `muted`,与 label 分开,避免与 Agents 合计 **Cost** 的 `success` 绿色混淆 |
|
|
145
|
+
| **Family 匹配** | `MODEL_FAMILY_RULES`(claude、deepseek、openai、gemini、qwen、glm、kimi、minimax、grok、mimo、meta、mistral);模型 slug 与 `providerID` **大小写不敏感** |
|
|
146
|
+
| **未知模型** | 稳定 hash → 中性 fallback 色(不用 `success`) |
|
|
147
|
+
| **配置** | v1 无 `cache-hit.json` 项;在代码中扩展 `MODEL_FAMILY_RULES` / `MODEL_BRAND_HEX` |
|
|
148
|
+
|
|
149
|
+
实现:`agents-view.tsx` 调用 `formatSubAgentLabel`、`modelRowColor`;`TuiMetricRow` 使用 `labelFg` / `valueFg`。
|
|
150
|
+
|
|
133
151
|
## 聚合与刷新
|
|
134
152
|
|
|
135
153
|
### 何时重算
|
|
@@ -223,7 +241,7 @@ flowchart TD
|
|
|
223
241
|
| 指标切换 | 累计 / 最近 N 轮 / 滑动窗口;与时间轴 Phase 3 联动 | timeline.md § Phase 3 |
|
|
224
242
|
| 子 agent | 递归子 session、按 agent 类型过滤 | timeline.md § 风险;侧栏另议 |
|
|
225
243
|
|
|
226
|
-
|
|
244
|
+
实现日志时使用 `message.updated` 事件直接驱动的 `handleMessage()`;落盘异步、勿阻塞 TUI(见 timeline.md)。
|
|
227
245
|
|
|
228
246
|
## 插件缓存
|
|
229
247
|
|
package/docs/zh-CN/timeline.md
CHANGED
|
@@ -153,7 +153,7 @@ export function buildCallRecords(
|
|
|
153
153
|
1. 可选 `rotateMaxBytes`:写**前**若当日活跃文件 ≥ 阈值 → 链式 rename(见 § 轮转与清理)。
|
|
154
154
|
2. `appendFile` 一行 JSON。
|
|
155
155
|
3. 可选 `maxLinesPerFile`:写**后**读回活跃文件,只保留最后 N 行(**删行**,不生成 `.1`)。
|
|
156
|
-
4.
|
|
156
|
+
4. 事件驱动:`message.updated` → `handleMessage()` → fire-and-forget `appendFile`。无轮询,无去重。
|
|
157
157
|
|
|
158
158
|
## 轮转与清理
|
|
159
159
|
|
|
@@ -193,11 +193,11 @@ export function buildCallRecords(
|
|
|
193
193
|
|
|
194
194
|
午夜后自动写入新文件名;昨日文件保留,直至上述清理策略删除。
|
|
195
195
|
|
|
196
|
-
###
|
|
196
|
+
### 收集
|
|
197
197
|
|
|
198
|
-
-
|
|
199
|
-
-
|
|
200
|
-
-
|
|
198
|
+
- `message.updated` 事件携带完整 `Message` 对象。collector 直接订阅事件——无轮询,无去重。
|
|
199
|
+
- 切换主 session:`resetForRootChange()` 清空内存缓存;新 session 的事件自然到达。
|
|
200
|
+
- 重启安全:启动前的消息已在上次 session 中写入 JSONL。无需回放,无需扫描。
|
|
201
201
|
|
|
202
202
|
## 运行时接入
|
|
203
203
|
|
|
@@ -205,20 +205,18 @@ export function buildCallRecords(
|
|
|
205
205
|
sequenceDiagram
|
|
206
206
|
participant E as message.updated
|
|
207
207
|
participant H as sidebar-host
|
|
208
|
-
participant
|
|
208
|
+
participant C as timeline/collector
|
|
209
209
|
participant W as timeline/writer
|
|
210
210
|
|
|
211
|
-
E->>H:
|
|
212
|
-
H->>
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
B->>W: append JSONL
|
|
211
|
+
E->>H: { sessionID, info: Message }
|
|
212
|
+
H->>C: handleMessage(sessionID, info)
|
|
213
|
+
alt assistant and complete
|
|
214
|
+
C->>W: append JSONL(fire-and-forget)
|
|
216
215
|
end
|
|
217
216
|
```
|
|
218
217
|
|
|
219
|
-
- **与 `child-session-sync` 分工**:子 id 列表仍由 `session.list`
|
|
220
|
-
-
|
|
221
|
-
- **作用域**:只记录「当前 TUI 绑定的 `rootSessionId`」及其子 session;落盘路径按**当天**不变,切换主 session 仍写同一日文件。
|
|
218
|
+
- **与 `child-session-sync` 分工**:子 id 列表仍由 `session.list` 负责;时间轴从事件中写 main 和 child session。
|
|
219
|
+
- 无 debounce,无轮询。**作用域**:当前 TUI root session + 其子 session。
|
|
222
220
|
|
|
223
221
|
## UI(分阶段)
|
|
224
222
|
|
|
@@ -234,15 +232,20 @@ tail -f $LOG
|
|
|
234
232
|
jq -r 'select(.rootSessionId=="YOUR_ROOT") | [.created,.scope,.hitPercent,.cost]|@tsv' $LOG
|
|
235
233
|
```
|
|
236
234
|
|
|
237
|
-
|
|
235
|
+
**画图 / 分析(可选脚本)** — 见 [scripts/README.md](../../scripts/README.md):
|
|
238
236
|
|
|
239
237
|
```bash
|
|
240
238
|
python3 -c "import json,sys; r=[json.loads(x) for x in open(sys.argv[1]) if x.strip()]; h=[x['hitPercent'] for x in r if x.get('hitPercent') is not None]; print(f\"{len(r)} calls, avg hit {sum(h)/len(h):.1f}%\")" $LOG
|
|
241
239
|
|
|
242
240
|
bun scripts/plot-hit-rate.ts $LOG -o /tmp/hit.svg
|
|
243
241
|
bun scripts/plot-hit-rate.ts $LOG --by-root -o /tmp/hit-multi.svg
|
|
242
|
+
|
|
243
|
+
# 交互式 HTML 仪表盘(筛选、Chart.js);加 --open 才会打开浏览器
|
|
244
|
+
bun scripts/timeline-dashboard.ts --open
|
|
244
245
|
```
|
|
245
246
|
|
|
247
|
+
默认日志目录与插件 `timeline.dir` 一致(`~/.local/share/opencode/logs/cache-hit/`)。
|
|
248
|
+
|
|
246
249
|
### Phase 2 — 侧栏「Timeline」折叠段
|
|
247
250
|
|
|
248
251
|
- 在 `widget.tsx` 增加 `TuiSection`,展示最近 `maxMemoryRows` 条(窄屏每行一条):
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-cache-hit",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "OpenCode TUI sidebar: prompt cache hit rate, tokens & cost with sub-agent rollup. Works with opencode-visual-cache; optional per-call JSONL timeline.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/scripts/README.md
CHANGED
|
@@ -20,6 +20,69 @@ Export TSV for spreadsheets (time fields are ISO 8601 strings):
|
|
|
20
20
|
jq -r 'select(.hitPercent!=null) | [.completedAt,.scope,.hitPercent,.cost]|@tsv' logs/timeline-2026-05-31.jsonl
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
+
## `timeline-dashboard.ts` (Bun, no install)
|
|
24
|
+
|
|
25
|
+
Interactive HTML dashboard with charts, filters, and data tables:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
bun scripts/timeline-dashboard.ts # auto-detect logs/ (no browser)
|
|
29
|
+
bun scripts/timeline-dashboard.ts --open # write HTML, then open browser
|
|
30
|
+
bun scripts/timeline-dashboard.ts -o /tmp/report.html # custom output path
|
|
31
|
+
bun scripts/timeline-dashboard.ts ~/logs/timeline-*.jsonl # globs expanded by script
|
|
32
|
+
bun scripts/timeline-dashboard.ts --output /tmp/report.html --open # combined
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Default output: `/tmp/timeline-dashboard-YYYY-MM-DD-HHmmss.html` (timestamp suffix to avoid overwrites).
|
|
36
|
+
|
|
37
|
+
**Browser:** not opened by default; pass `--open` (macOS `open`, Linux `xdg-open`, Windows `start`).
|
|
38
|
+
|
|
39
|
+
**Features:**
|
|
40
|
+
- Summary cards follow active filters (records, tokens, cost, avg hit rate)
|
|
41
|
+
- Time / session / scope / model / text search filters
|
|
42
|
+
- 3 Chart.js charts: token volume (stacked bar), hit rate + cost (dual axis), duration (bar)
|
|
43
|
+
- Session summary table (mixed main+child scope shown as `main+child`)
|
|
44
|
+
- Per-call detail table with expandable rows (all JSONL fields)
|
|
45
|
+
- Embedded data — no server needed, just open the HTML file
|
|
46
|
+
|
|
47
|
+
**How it works:**
|
|
48
|
+
|
|
49
|
+
1. Reads `timeline-*.jsonl` and rotation backups `timeline-*.jsonl.N` from the default log dir (`~/.local/share/opencode/logs/cache-hit/`) or user-supplied paths/globs
|
|
50
|
+
2. Parses each JSONL line (`schema: 1` validation), sorts by `completedAt` / `created`
|
|
51
|
+
3. Aggregates per-session statistics in the browser when filters change
|
|
52
|
+
4. Generates a self-contained HTML file with:
|
|
53
|
+
- All data embedded as JSON in a `<script>` tag (`<` escaped for safety)
|
|
54
|
+
- Chart.js 4.4.7 from CDN (`cdn.jsdelivr.net`)
|
|
55
|
+
- Vanilla JS for interactivity (no framework dependency)
|
|
56
|
+
5. Optional `--open` opens the output file in the default browser
|
|
57
|
+
|
|
58
|
+
**Avg hit rate:** excludes `skippedForHit` rows (same rule as `plot-hit-rate.ts`); null `hitPercent` still appear in tables/charts.
|
|
59
|
+
|
|
60
|
+
**Cost display:** reads `currency` / `costUnit` / `rate` from `~/.config/opencode/cache-hit.json` when present (same as the TUI sidebar). **No config file** → defaults (`CNY` display, `USD` JSONL unit, rate `6.77`). Invalid or partial cost fields are normalized; corrupt config falls back without failing the script. JSONL always stores raw `cost` in `costUnit` (usually USD).
|
|
61
|
+
|
|
62
|
+
**Note:** The generated HTML is self-contained except Chart.js CDN. Re-run the script to refresh data (static snapshot).
|
|
63
|
+
|
|
64
|
+
### Design approaches
|
|
65
|
+
|
|
66
|
+
**Option A (current — static HTML):**
|
|
67
|
+
Data is embedded into the HTML at build time by Bun. The browser just renders.
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
bun scripts/timeline-dashboard.ts → /tmp/timeline-dashboard-YYYY-MM-DD-HHmmss.html (self-contained)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
- Pros: No server needed, zero runtime deps, can email/share the file
|
|
74
|
+
- Cons: Snapshot only — re-run to refresh data
|
|
75
|
+
|
|
76
|
+
**Option B (live server):**
|
|
77
|
+
Starts an HTTP server; the browser fetches JSONL via `fetch("/api/logs")`.
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
bun scripts/timeline-dashboard.ts serve → http://localhost:PORT
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
- Pros: Live reload — refresh the page to see new logs
|
|
84
|
+
- Cons: Must keep a process running, cannot send the page as a file
|
|
85
|
+
|
|
23
86
|
## `plot-hit-rate.ts` (Bun, no install)
|
|
24
87
|
|
|
25
88
|
Terminal ASCII chart; optional SVG (`open /tmp/hit.svg`):
|