opencode-metrics-plugin 0.1.5 → 0.2.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 +108 -390
- package/dist/index.d.ts +5 -51
- package/dist/index.js +5 -61
- package/dist/metrics/dirs.d.ts +23 -0
- package/dist/{dirs.js → metrics/dirs.js} +4 -10
- package/dist/metrics/engine/engine.d.ts +15 -0
- package/dist/{metrics-engine.js → metrics/engine/engine.js} +33 -146
- package/dist/{metrics-handlers.d.ts → metrics/engine/handlers.d.ts} +1 -1
- package/dist/{metrics-handlers.js → metrics/engine/handlers.js} +2 -2
- package/dist/metrics/engine/state.d.ts +79 -0
- package/dist/{metrics-types.js → metrics/engine/state.js} +1 -19
- package/dist/{event-logger.d.ts → metrics/eventlog/event-logger.d.ts} +0 -5
- package/dist/{event-logger.js → metrics/eventlog/event-logger.js} +2 -18
- package/dist/metrics/index.d.ts +9 -0
- package/dist/metrics/index.js +6 -0
- package/dist/metrics/runtime.d.ts +25 -0
- package/dist/metrics/runtime.js +28 -0
- package/dist/metrics/snapshot/flush.d.ts +6 -0
- package/dist/{metrics-output.js → metrics/snapshot/flush.js} +6 -290
- package/dist/metrics/snapshot/merge.d.ts +9 -0
- package/dist/metrics/snapshot/merge.js +282 -0
- package/dist/{metrics-steps.d.ts → metrics/snapshot/steps.d.ts} +1 -1
- package/dist/{metrics-steps.js → metrics/snapshot/steps.js} +1 -1
- package/dist/{metrics-types.d.ts → metrics/types.d.ts} +3 -81
- package/dist/metrics/types.js +20 -0
- package/dist/plugin.d.ts +22 -0
- package/dist/plugin.js +49 -0
- package/dist/{logger.d.ts → shared/log.d.ts} +2 -0
- package/dist/{logger.js → shared/log.js} +9 -2
- package/package.json +8 -4
- package/dist/backfill-cli.d.ts +0 -1
- package/dist/backfill-cli.js +0 -74
- package/dist/backfill.d.ts +0 -58
- package/dist/backfill.js +0 -550
- package/dist/dirs.d.ts +0 -34
- package/dist/metrics-engine.d.ts +0 -37
- package/dist/metrics-output.d.ts +0 -11
- package/dist/summary-store.d.ts +0 -150
- package/dist/summary-store.js +0 -560
- /package/dist/{compile-analyzer.d.ts → metrics/analysis/hvigor.d.ts} +0 -0
- /package/dist/{compile-analyzer.js → metrics/analysis/hvigor.js} +0 -0
package/README.md
CHANGED
|
@@ -1,390 +1,108 @@
|
|
|
1
|
-
# opencode-metrics-plugin
|
|
2
|
-
|
|
3
|
-
**opencode
|
|
4
|
-
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
│
|
|
20
|
-
│ 每轮 session.idle /
|
|
21
|
-
│ │
|
|
22
|
-
│
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
### 触发时机
|
|
112
|
-
|
|
113
|
-
- **CLI 全量**:`node dist/backfill-cli.js --config engines.json [--agent <name>] [--since <ms|ISO>] [--dry-run] [--db <path>]`
|
|
114
|
-
配置文件(JSON,支持 `//` 注释):`{ "dbPath"?, "engines": [{ "label", "metricsDir", "agents"?, "cwdPrefixes"?, "summaryFile"? }], "fallback"?, "filter"? }`(summaryFile 缺省 = 全局共享库,与 live 同库)
|
|
115
|
-
- **启动增量**:`createMetricsRuntime({ backfillOnStart: true, ... })`,各引擎目录 `.backfill-state` 水位记录进度,插件加载时自动补齐"插件未运行期间"的本引擎会话(仅当 scope 可静态判定:`mode: all` 或配置了 `agents`/`cwdPrefixes` 时生效)
|
|
116
|
-
|
|
117
|
-
### 保真度
|
|
118
|
-
|
|
119
|
-
| 指标 | 回填可重建 |
|
|
120
|
-
|---|---|
|
|
121
|
-
| tokens / cacheHitRate / 轮次 / 时长 / 工具分布 / steps / skills / 子代理 | 完整 |
|
|
122
|
-
| 代码编译统计(hvigor)/ anomaly / compaction 细节 | 置零或缺省 |
|
|
123
|
-
|
|
124
|
-
## 摘要/详情双表(summary.db)
|
|
125
|
-
|
|
126
|
-
插件在快照文件之外自动维护一个 sqlite 摘要库:**缺省为全局共享路径**(`%LOCALAPPDATA%/opencode-metrics-plugin/Log/summary.db`,不随 metricsDir 派生——本机多宿主自动聚合到同一个库);需要隔离时经 `dirs.summaryFile` 精确指定。表结构与 tnnotix 既有库完全一致(显示字段不变):
|
|
127
|
-
|
|
128
|
-
- `session_summaries`:`session_id / task_id / agent / model / directory / title / first_user_message / step_count / tool_calls / total_tokens_{input,output,cache_read,cache_write,reasoning} / total_tokens / duration / created_at / source_file / ingested_at`(索引:`created_at` / `task_id` / `model`)
|
|
129
|
-
- `session_details`:`session_id / full_data`(完整快照 JSON 原文)
|
|
130
|
-
|
|
131
|
-
写入时机:
|
|
132
|
-
|
|
133
|
-
| 时机 | 模式 | 说明 |
|
|
134
|
-
|---|---|---|
|
|
135
|
-
| live flush(每轮 `session.idle`) | force | 双表与快照文件同拍最新;进行中会话每轮自动刷新,首轮结束即可查到 |
|
|
136
|
-
| `backfillFromOpencode` | soft | 已有 `total_tokens > 0` 的行不覆盖,live 永远赢 |
|
|
137
|
-
| `reindexDir(metricsDir)` | soft + prune | 扫描存量 JSON 重建;`prune: true` 清理文件已删的孤儿行 |
|
|
138
|
-
|
|
139
|
-
> 依赖 `node:sqlite`(Node >= 22.5)。不可用时实时统计与快照文件照常工作,仅摘要库停用(进程内只告警一次)。
|
|
140
|
-
|
|
141
|
-
## API 使用指导
|
|
142
|
-
|
|
143
|
-
### createMetricsRuntime —— 一体封装(推荐入口)
|
|
144
|
-
|
|
145
|
-
目录 + 事件日志 + 引擎 + 可选启动回填的一体组装,宿主通常只需这一个函数。
|
|
146
|
-
|
|
147
|
-
```ts
|
|
148
|
-
import { createMetricsRuntime } from 'opencode-metrics-plugin'
|
|
149
|
-
|
|
150
|
-
const rt = createMetricsRuntime({
|
|
151
|
-
enabled: true, // 总开关(默认 true)
|
|
152
|
-
scope: { mode: 'all' }, // 统计范围(见「策略」)
|
|
153
|
-
dirs: { // 可选,实例目录(三层解析)
|
|
154
|
-
metricsDir: '...', eventsDir: '...', logFile: '...', summaryFile: '...',
|
|
155
|
-
},
|
|
156
|
-
onFlush: (sessionId, snapshot) => {
|
|
157
|
-
// 每轮 flush 后回调(snapshot 为本轮快照;宿主可在此上报自有系统)
|
|
158
|
-
},
|
|
159
|
-
eventLogging: true, // 是否写 events/<id>.log(默认 true),与引擎共用 scope 门控
|
|
160
|
-
backfillOnStart: true, // 启动增量回填(见上)
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
rt.event({ event }) // 挂到宿主 hooks.event:async ({ event }) => rt.event({ event })
|
|
164
|
-
rt.engine // 底层引擎(claimSession / flush / dispose ...)
|
|
165
|
-
rt.eventLogger // 事件记录器(null 当 eventLogging:false)
|
|
166
|
-
rt.dirs // 解析后的完整目录集(含派生的 summaryFile)
|
|
167
|
-
rt.eventLogger?.flush() // 退出前手动刷事件日志缓冲
|
|
168
|
-
rt.engine.dispose() // 退出前兜底刷所有未落盘会话(快照+双表)
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
### createMetricsEngine —— 手动组装引擎
|
|
172
|
-
|
|
173
|
-
需要自己控制事件日志/生命周期时使用(harmony-code 即此方式)。
|
|
174
|
-
|
|
175
|
-
```ts
|
|
176
|
-
import { createMetricsEngine } from 'opencode-metrics-plugin'
|
|
177
|
-
|
|
178
|
-
const engine = createMetricsEngine({
|
|
179
|
-
enabled: true,
|
|
180
|
-
scope: { mode: 'all', agents: ['harmonyos-plugin'] },
|
|
181
|
-
dirs: { metricsDir: '...', eventsDir: '...' }, // 构造时三层解析并固化;缺省回退进程级默认
|
|
182
|
-
onFlush: (sessionId, snapshot) => {},
|
|
183
|
-
})
|
|
184
|
-
// 兼容旧布尔入参:createMetricsEngine(true) === scope all;false === none
|
|
185
|
-
|
|
186
|
-
engine.ingest(event) // 喂事件:{ type, properties }
|
|
187
|
-
engine.ingestPrompt(sessionId, modelId, system) // 喂 system prompt(string[])
|
|
188
|
-
engine.claimSession(sessionId, { agent }) // declared/none 模式下显式纳入某会话
|
|
189
|
-
engine.scopeOf(sessionId) // 查询会话是否在统计范围
|
|
190
|
-
engine.shouldTrack(sessionId, type, props) // 归属判定(供 event-logger 等共用同一门控)
|
|
191
|
-
engine.isSubAgent(sessionId) // 是否子代理会话
|
|
192
|
-
engine.flush(sessionId) // 手动触发单会话落盘(快照文件 + summary.db)
|
|
193
|
-
engine.dispose() // 兜底刷全部未落盘会话并清空状态
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
### createEventLogger —— 原始事件流记录
|
|
197
|
-
|
|
198
|
-
```ts
|
|
199
|
-
import { createEventLogger } from 'opencode-metrics-plugin'
|
|
200
|
-
|
|
201
|
-
const logger = createEventLogger(true, false, {
|
|
202
|
-
eventsDir: '.../events', // 实例目录;缺省回退进程级默认
|
|
203
|
-
isTracked: (sessionId, event) => engine.shouldTrack(sessionId, event.type, event.properties),
|
|
204
|
-
// 与引擎共用同一 scope 判定:引擎不统计的会话不写事件日志
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
logger.log(event) // 喂事件(子代理事件自动并入父日志)
|
|
208
|
-
logger.logPrompt(sessionId, type, content)
|
|
209
|
-
logger.isSubAgent(sessionId)
|
|
210
|
-
logger.flush() // 刷缓冲到磁盘
|
|
211
|
-
logger.dispose()
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
### backfillFromOpencode —— 历史回填
|
|
215
|
-
|
|
216
|
-
见「历史回填」章节完整示例与选项说明。
|
|
217
|
-
|
|
218
|
-
### querySummaries / countSummaries —— 摘要筛选(列表数据源)
|
|
219
|
-
|
|
220
|
-
```ts
|
|
221
|
-
import { querySummaries, countSummaries } from 'opencode-metrics-plugin'
|
|
222
|
-
|
|
223
|
-
const rows = querySummaries(
|
|
224
|
-
{
|
|
225
|
-
agent: 'harmonyos-plugin', // 单值精确;多值用 agents: [...]
|
|
226
|
-
agents: ['harmonyos-plugin', 'harmonyos-convert'],
|
|
227
|
-
taskId: 'task_42', // 任务分组(先经 setSessionTaskId 标记)
|
|
228
|
-
directoryPrefix: 'D:/company/app2hm', // 反斜杠/大小写自动归一
|
|
229
|
-
model: 'glm-4.6',
|
|
230
|
-
sessionIds: ['ses_a', 'ses_b'], // 会话 id 名单(按集合聚合汇总用)
|
|
231
|
-
since: '2026-01-01', // created_at 下界(ISO 或毫秒时间戳)
|
|
232
|
-
until: Date.now(), // 上界
|
|
233
|
-
limit: 20,
|
|
234
|
-
offset: 0,
|
|
235
|
-
},
|
|
236
|
-
{ metricsDir: '.../Log/metrics' }, // 可选:指定实例目录(缺省走三层解析)
|
|
237
|
-
)
|
|
238
|
-
// rows: SessionSummary[](created_at 倒序)—— id / taskId / agent / model / directory / title /
|
|
239
|
-
// stepCount / toolCalls / totalTokens{input,output,cacheRead,cacheWrite,reasoning,total} /
|
|
240
|
-
// duration / createdAt / firstUserMessage / filePath ...
|
|
241
|
-
|
|
242
|
-
const total = countSummaries({ agent: 'harmonyos-plugin' }, { metricsDir: '.../Log/metrics' })
|
|
243
|
-
// 同条件总数,配合 limit/offset 做分页
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
### getDetail —— 单会话详情(详情页数据源)
|
|
247
|
-
|
|
248
|
-
```ts
|
|
249
|
-
import { getDetail } from 'opencode-metrics-plugin'
|
|
250
|
-
|
|
251
|
-
const detail = getDetail('ses_xxx', { metricsDir: '.../Log/metrics' })
|
|
252
|
-
// detail: SessionDetail —— steps / rounds / tools / codeStats / planning / skills /
|
|
253
|
-
// systemPrompts;子代理默认摘要化(steps 剥离为 stepCount,载荷更轻)
|
|
254
|
-
|
|
255
|
-
const full = getDetail('ses_xxx', { metricsDir: '.../Log/metrics' }, { fullSubagents: true })
|
|
256
|
-
// fullSubagents: true 保留完整子代理 steps(需直接渲染子代理树的宿主用)
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
数据源优先级:`session_details.full_data` → 快照文件回退;返回 `null` 表示会话不存在。
|
|
260
|
-
|
|
261
|
-
### getSubagentSteps —— 子代理 steps 懒加载
|
|
262
|
-
|
|
263
|
-
```ts
|
|
264
|
-
const steps = getSubagentSteps('ses_xxx', 0, { metricsDir: '.../Log/metrics' })
|
|
265
|
-
// StepData[] | null:DB full_data → 文件回退;配合 getDetail 摘要化模式按需取
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
### getSessionRaw —— 原始快照 JSON(下载能力)
|
|
269
|
-
|
|
270
|
-
```ts
|
|
271
|
-
const rawJson = getSessionRaw('ses_xxx', { metricsDir: '.../Log/metrics' })
|
|
272
|
-
// string | null:快照文件优先 → DB full_data 回退
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
### setSessionTaskId —— 任务分组标记
|
|
276
|
-
|
|
277
|
-
```ts
|
|
278
|
-
setSessionTaskId('ses_xxx', 'task_42', { metricsDir: '.../Log/metrics' })
|
|
279
|
-
// boolean:是否命中行(false = 摘要库中无该会话);taskId 传 null 可清除分组
|
|
280
|
-
const group = querySummaries({ taskId: 'task_42' }, { metricsDir: '.../Log/metrics' })
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
### removeSession —— 删除会话行(宿主删除链路)
|
|
284
|
-
|
|
285
|
-
```ts
|
|
286
|
-
const { deleted, sourceFile } = removeSession('ses_xxx', { metricsDir: '.../Log/metrics' })
|
|
287
|
-
// 事务内删双表行;sourceFile 为被删行的 source_file,供宿主继续清理快照文件/事件日志
|
|
288
|
-
// 行不存在或库不可用时 deleted=false(幂等,可安全重试)
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
### reindexDir —— 存量重建 + 孤儿清理
|
|
292
|
-
|
|
293
|
-
```ts
|
|
294
|
-
const { scanned, upserted, pruned } = reindexDir('.../Log/metrics', {
|
|
295
|
-
summaryFile: '可选,缺省全局共享库',
|
|
296
|
-
prune: true, // 清理 source_file 已不存在于磁盘的孤儿行(双表同删)
|
|
297
|
-
})
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
典型场景:
|
|
301
|
-
- 升级接入后把历史快照文件一次性登记进摘要库
|
|
302
|
-
- 删除文件后清理双表孤儿行——注意共享库语义下 prune 清理的是**全库**孤儿(含其他宿主目录已消失的快照),属预期行为
|
|
303
|
-
|
|
304
|
-
### upsertSessionSnapshot —— 手动入库(自定义数据源)
|
|
305
|
-
|
|
306
|
-
```ts
|
|
307
|
-
upsertSessionSnapshot(summaryFile, snapshot, filePath, 'force')
|
|
308
|
-
// summaryFile: 库路径;snapshot: MetricsOutput;filePath: 记入 source_file 的快照路径
|
|
309
|
-
// 'force' 总是刷新(live 语义);'soft' 不覆盖已有 total_tokens>0 的行(backfill 语义)
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
宿主从自有管道拿到快照对象时可直接入库;返回 boolean 表示是否成功。
|
|
313
|
-
|
|
314
|
-
### 目录 API
|
|
315
|
-
|
|
316
|
-
```ts
|
|
317
|
-
import { defaultDirs, configureDirs, getDirs, resolveDirs, getSummaryFile } from 'opencode-metrics-plugin'
|
|
318
|
-
|
|
319
|
-
defaultDirs('my-app') // 兜底目录集:envPaths('my-app').log 下 metrics/ events/ plugin.log summary.db
|
|
320
|
-
// 不传应用名则为 'opencode-metrics-plugin'(本包默认,即全局共享库所在)
|
|
321
|
-
configureDirs({ summaryFile: '...' }) // 只改摘要库落点(进程级);其余保持默认
|
|
322
|
-
configureDirs({ metricsDir: '...' }) // 改快照目录不影响 summaryFile(仍为全局库,除非显式配置)
|
|
323
|
-
getDirs() // 当前进程级默认(defaultDirs + configureDirs 合并结果)
|
|
324
|
-
resolveDirs(partial) // 三层合并:defaultDirs → configureDirs → partial;summaryFile 显式值优先,缺省全局
|
|
325
|
-
getSummaryFile() // 进程级默认摘要库路径
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
落点语义(关键):
|
|
329
|
-
```
|
|
330
|
-
宿主传了 metricsDir='.../hm-plugin/Log/metrics' 时:
|
|
331
|
-
.../hm-plugin/Log/metrics/<sessionId>.json ← 快照按宿主隔离
|
|
332
|
-
%LOCALAPPDATA%/opencode-metrics-plugin/Log/
|
|
333
|
-
├── summary.db ← 摘要库恒为全局(缺省),多宿主聚合
|
|
334
|
-
└── metrics/ events/ plugin.log ← 仅当宿主连 metricsDir 也没配时的兜底
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
### 辅助函数
|
|
338
|
-
|
|
339
|
-
```ts
|
|
340
|
-
import {
|
|
341
|
-
extractSummaryFields, // (snapshot, filePath) → SummaryFields:快照→摘要列提取(入库同口径)
|
|
342
|
-
snapshotToSummary, // (snapshot, filePath?) → SessionSummary:快照→列表摘要(含 main/subagent tokens 拆分)
|
|
343
|
-
detailFromRaw, // (raw, { keepSubagentSteps? }) → SessionDetail:快照→详情(默认子代理摘要化)
|
|
344
|
-
detailFullFromRaw, // (raw) → SessionDetail:保留完整子代理 steps 的详情
|
|
345
|
-
sumTokens, // (TokenUsage[]) → TokenUsage:逐元素累加
|
|
346
|
-
subtractTokens, // (a, b) → TokenUsage:相减(剥离子代理 token)
|
|
347
|
-
toIsoString, // (string|number|Date) → ISO 字符串 | null
|
|
348
|
-
firstUserMessageOf, // (rounds) → string | undefined:首条用户消息(兼容 string/string[])
|
|
349
|
-
openSummaryDb, // (file) → DatabaseSync | null:打开/复用摘要库连接(WAL + 幂等建表)
|
|
350
|
-
closeSummaryDbs, // () => void:关闭全部缓存连接(宿主删除目录前调用,避免 Windows 句柄占用)
|
|
351
|
-
} from 'opencode-metrics-plugin'
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
## 类型导出
|
|
355
|
-
|
|
356
|
-
- 快照/状态:`MetricsOutput` / `SessionMetrics*` / `TokenUsage` / `StepData` / `RoundSnapshot` / `SubAgentOutput` / `PlanningMetrics` / `SessionMeta`
|
|
357
|
-
- 查询:`SessionSummary` / `SessionDetail` / `SubagentView` / `SummaryFilter` / `SummaryFields` / `ReindexResult` / `UpsertMode` / `RemoveSessionResult`
|
|
358
|
-
- 回填:`BackfillOptions` / `BackfillEngineRule` / `BackfillFilter` / `BackfillResult` / `BackfillEngineStat` / `BackfillMetricsOutput`
|
|
359
|
-
- 目录:`MetricsDirs`;引擎:`MetricsEngineOptions` / `MetricsScope` / `MetricsScopeMode` / `MetricsEngine`;事件:`EventLogger` / `EventLoggerOptions`;运行时:`MetricsRuntime` / `MetricsRuntimeOptions`
|
|
360
|
-
|
|
361
|
-
> 回填与摘要库功能依赖 `node:sqlite`(Node >= 22.5);实时统计与快照文件核心无此要求。
|
|
362
|
-
|
|
363
|
-
## 构建/发布
|
|
364
|
-
|
|
365
|
-
```bash
|
|
366
|
-
npm install
|
|
367
|
-
npm run build # tsc → dist/
|
|
368
|
-
npm test # example/smoke.mts(tsx,53 断言)
|
|
369
|
-
npm pack # 本地发包前检查
|
|
370
|
-
npm publish # 发布到 npm registry
|
|
371
|
-
```
|
|
372
|
-
|
|
373
|
-
## 目录结构
|
|
374
|
-
|
|
375
|
-
```
|
|
376
|
-
src/
|
|
377
|
-
├── index.ts createMetricsRuntime / 查询 API / 全部类型导出
|
|
378
|
-
├── metrics-engine.ts createMetricsEngine(策略 + claimSession + onFlush + 实例目录)
|
|
379
|
-
├── metrics-types.ts 状态/快照类型(MetricsOutput/SessionMetrics*)
|
|
380
|
-
├── metrics-handlers.ts 事件→状态 累加
|
|
381
|
-
├── metrics-output.ts flush 组装快照 + 写盘 + 双表 force 入库(返回快照供 onFlush)
|
|
382
|
-
├── metrics-steps.ts 步骤内容回填(读 events 日志,实例目录)
|
|
383
|
-
├── event-logger.ts events/<sessionId>.log 记录/轮转(实例目录 + isTracked 门控)
|
|
384
|
-
├── compile-analyzer.ts hvigor 输出解析(可选,非编译会话为空)
|
|
385
|
-
├── backfill.ts opencode.db 历史回填(归属路由 + 水位 + source 标记 + 双表 soft 入库)
|
|
386
|
-
├── backfill-cli.ts 回填命令行入口
|
|
387
|
-
├── summary-store.ts 摘要/详情双表(summary.db)+ 全部查询 API + reindex
|
|
388
|
-
├── dirs.ts 目录参数化(defaultDirs/configureDirs/getDirs/resolveDirs/summaryFile 派生)
|
|
389
|
-
└── logger.ts 运行日志(文件 + console 回退)
|
|
390
|
-
```
|
|
1
|
+
# opencode-metrics-plugin
|
|
2
|
+
|
|
3
|
+
**opencode 会话指标插件**:挂上即自动记录每个会话的完整指标(tokens / rounds / steps 全文 / 子代理 / 工具调用 / hvigor 构建统计),每轮对话结束落盘一个快照 JSON。
|
|
4
|
+
|
|
5
|
+
- **开箱即用**:宿主只需在 `opencode.json` 加一行,无需任何配置
|
|
6
|
+
- **全量记录**:集成时刻起的所有会话(含子代理会话,自动并入父会话)
|
|
7
|
+
- **增量 merge**:同会话多轮持续追加,进程重启后 rounds/steps 不丢
|
|
8
|
+
- **dispose 兜底**:未走 `session.idle` 的会话在插件卸载时也会落盘
|
|
9
|
+
- **可编程 API**:`createMetricsEngine` / `createMetricsRuntime` 可脱离插件契约单独使用
|
|
10
|
+
|
|
11
|
+
## 总览
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
opencode 事件流
|
|
15
|
+
│ plugin event hook
|
|
16
|
+
▼
|
|
17
|
+
┌─ runtime(事件记录 + 指标引擎)────────────────────────────┐
|
|
18
|
+
│ events\<sessionId>.log 原始事件流(steps 全文数据源) │
|
|
19
|
+
│ │
|
|
20
|
+
│ 每轮 session.idle / dispose 兜底 │
|
|
21
|
+
│ ├─→ <metricsDir>\<sessionId>.json(快照,增量 merge) │
|
|
22
|
+
│ └─→ onFlush(sessionId, snapshot)(宿主自定义消费) │
|
|
23
|
+
└─────────────────────────────────────────────────────────────┘
|
|
24
|
+
│
|
|
25
|
+
▼
|
|
26
|
+
session-viewer 扫描 metricsDir 解析展示(完全解耦)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 快速开始
|
|
30
|
+
|
|
31
|
+
宿主项目 `opencode.json`:
|
|
32
|
+
|
|
33
|
+
```jsonc
|
|
34
|
+
// 最简
|
|
35
|
+
{ "plugin": ["opencode-metrics-plugin"] }
|
|
36
|
+
|
|
37
|
+
// 带配置
|
|
38
|
+
{
|
|
39
|
+
"plugin": [
|
|
40
|
+
["opencode-metrics-plugin", { "enabled": true, "eventLogging": true }]
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
配置项(全部可选):
|
|
46
|
+
|
|
47
|
+
| 选项 | 类型 | 默认 | 说明 |
|
|
48
|
+
|---|---|---|---|
|
|
49
|
+
| `enabled` | `boolean` | `true` | 总开关,`false` 时不记录任何会话 |
|
|
50
|
+
| `eventLogging` | `boolean` | `true` | 原始事件写盘(`events/<sessionId>.log`);关闭后快照仍生成,但 `steps[].text/reasoning` 为空 |
|
|
51
|
+
| `dirs` | `Partial<MetricsDirs>` | env-paths | 自定义 `metricsDir` / `eventsDir` / `logFile`(多实例同用时建议各传各的) |
|
|
52
|
+
| `onFlush` | `(sessionId, snapshot) => void` | — | 快照落盘后回调(可在此上报自有系统) |
|
|
53
|
+
|
|
54
|
+
## 输出
|
|
55
|
+
|
|
56
|
+
默认目录(appName `opencode-metrics-plugin`,env-paths 规范):
|
|
57
|
+
|
|
58
|
+
| 平台 | 路径 |
|
|
59
|
+
|---|---|
|
|
60
|
+
| Windows | `%LOCALAPPDATA%\opencode-metrics-plugin\Log\metrics\`(events 同级) |
|
|
61
|
+
| Linux | `~/.local/state/opencode-metrics-plugin/metrics/` |
|
|
62
|
+
| macOS | `~/Library/Logs/opencode-metrics-plugin/metrics/` |
|
|
63
|
+
|
|
64
|
+
每个会话一个 `<sessionId>.json`(`MetricsOutput` 结构),核心字段:
|
|
65
|
+
|
|
66
|
+
- `header`:sessionId / 工作目录 / agent / model 及切换分布
|
|
67
|
+
- `systemPrompts`:各模型 system prompt(经 `experimental.chat.system.transform` hook 记录,子代理跳过)
|
|
68
|
+
- `rounds[]`:每轮 duration、首 token 延迟、tokens、工具调用数、用户消息文本
|
|
69
|
+
- `steps[]`:每步全文(text / reasoning 来自事件日志)、tokens、cost、工具明细
|
|
70
|
+
- `subagents[]`:子代理(task/explore 等)steps、tokens、工具统计
|
|
71
|
+
- `codeStats`:hvigorw 构建统计(错误码 / 警告 / 模块耗时 / 修复周期;非 HarmonyOS 会话为空)
|
|
72
|
+
- `planning`:todowrite 规划统计
|
|
73
|
+
|
|
74
|
+
配套可视化:[session-viewer](../session-viewer) 扫描 metricsDir 直接解析展示,无需任何服务端支持。
|
|
75
|
+
|
|
76
|
+
## 可编程 API
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { createMetricsRuntime, configureDirs } from 'opencode-metrics-plugin'
|
|
80
|
+
|
|
81
|
+
const rt = createMetricsRuntime({
|
|
82
|
+
dirs: { metricsDir: '/data/metrics', eventsDir: '/data/events' },
|
|
83
|
+
onFlush: (sessionId, snapshot) => upload(snapshot),
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
// 自托管事件源(非 opencode 插件场景)
|
|
87
|
+
rt.event({ event: { type: 'message.part.updated', properties: { ... } } })
|
|
88
|
+
|
|
89
|
+
// 结束时兜底
|
|
90
|
+
rt.eventLogger?.dispose()
|
|
91
|
+
rt.engine.dispose()
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
更低层可用 `createMetricsEngine`(无事件日志,仅指标累计)。
|
|
95
|
+
|
|
96
|
+
## 开发
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
npm run typecheck # tsc --noEmit
|
|
100
|
+
npm test # 冒烟测试(example/smoke.mts,27 断言)
|
|
101
|
+
npm run build # 清空 dist 后 tsc
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
结构:`src/plugin.ts`(插件入口)→ `src/metrics/`(域:engine / snapshot / eventlog / analysis / runtime)→ `src/shared/log.ts`。`@opencode-ai/plugin` 仅作类型依赖(peer),运行时零依赖(除 env-paths)。
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -1,51 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export {
|
|
5
|
-
export type {
|
|
6
|
-
export { configureDirs, defaultDirs, getDirs, resolveDirs, getSummaryFile } from "./dirs.js";
|
|
7
|
-
export type { MetricsDirs } from "./dirs.js";
|
|
8
|
-
export { createEventLogger } from "./event-logger.js";
|
|
9
|
-
export type { EventLogger, EventLoggerOptions } from "./event-logger.js";
|
|
10
|
-
export { backfillFromOpencode } from "./backfill.js";
|
|
11
|
-
export type { BackfillOptions, BackfillEngineRule, BackfillFilter, BackfillResult, BackfillEngineStat, BackfillMetricsOutput, } from "./backfill.js";
|
|
12
|
-
export { querySummaries, countSummaries, getDetail, getSubagentSteps, getSessionRaw, setSessionTaskId, reindexDir, upsertSessionSnapshot, openSummaryDb, closeSummaryDbs, removeSession, extractSummaryFields, snapshotToSummary, detailFromRaw, detailFullFromRaw, sumTokens, subtractTokens, toIsoString, firstUserMessageOf, } from "./summary-store.js";
|
|
13
|
-
export type { SummaryFilter, SessionSummary, SessionDetail, SubagentView, ReindexResult, UpsertMode, SummaryFields, RemoveSessionResult, } from "./summary-store.js";
|
|
14
|
-
export type * from "./metrics-types.js";
|
|
15
|
-
export interface MetricsRuntimeOptions extends Omit<MetricsEngineOptions, "enabled"> {
|
|
16
|
-
dirs?: Partial<MetricsDirs>;
|
|
17
|
-
enabled?: boolean;
|
|
18
|
-
/** 事件写盘(events/<sessionId>.log);仅记录归属命中本引擎 scope 的会话 */
|
|
19
|
-
eventLogging?: boolean;
|
|
20
|
-
/** 事件记录器额外选项(如外部传入的 isTracked 门控,一般由 runtime 自动注入无需配置) */
|
|
21
|
-
eventLoggerOptions?: Omit<EventLoggerOptions, "eventsDir" | "isTracked">;
|
|
22
|
-
/**
|
|
23
|
-
* 启动时增量回填本引擎归属的历史会话(读 opencode.db,水位存于 metricsDir/.backfill-state.json)。
|
|
24
|
-
* 仅当 scope 可静态判定(mode all,或配置了 agents/cwdPrefixes)时生效,用于补齐"插件未运行期间"的间隙会话。
|
|
25
|
-
*/
|
|
26
|
-
backfillOnStart?: boolean;
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* 组装一整套"通用统计插件"的运行时:事件记录 + 指标引擎。
|
|
30
|
-
* 目录为实例级(不产生全局副作用):defaultDirs → 进程级默认(configureDirs)→ opts.dirs 三层合并。
|
|
31
|
-
* 同一进程可创建多个 runtime(不同插件各配各目录),scope 门控同时作用于引擎计数与事件写盘。
|
|
32
|
-
*/
|
|
33
|
-
export declare function createMetricsRuntime(opts?: MetricsRuntimeOptions): {
|
|
34
|
-
dirs: MetricsDirs;
|
|
35
|
-
eventLogger: import("./event-logger.js").EventLogger | null;
|
|
36
|
-
engine: import("./metrics-types.js").MetricsEngine & {
|
|
37
|
-
claimSession: (sessionId: string, meta?: {
|
|
38
|
-
agent?: string;
|
|
39
|
-
}) => void;
|
|
40
|
-
scopeOf: (sessionId: string) => boolean;
|
|
41
|
-
shouldTrack: (sessionId: string, eventType: string, props: Record<string, unknown> | undefined) => boolean;
|
|
42
|
-
};
|
|
43
|
-
/** opencode event hook:`async ({ event }) => { runtime.event({ event }) }` */
|
|
44
|
-
event(input: {
|
|
45
|
-
event: {
|
|
46
|
-
type: string;
|
|
47
|
-
properties?: Record<string, unknown>;
|
|
48
|
-
};
|
|
49
|
-
}): void;
|
|
50
|
-
};
|
|
51
|
-
export type MetricsRuntime = ReturnType<typeof createMetricsRuntime>;
|
|
1
|
+
export { MetricsPlugin, default } from './plugin.js';
|
|
2
|
+
export type { MetricsPluginOptions } from './plugin.js';
|
|
3
|
+
export * from './metrics/index.js';
|
|
4
|
+
export { setLogLevel, setLogFile, flushLogs } from './shared/log.js';
|
|
5
|
+
export type { LogLevel } from './shared/log.js';
|
package/dist/index.js
CHANGED
|
@@ -1,61 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export {
|
|
6
|
-
export { configureDirs, defaultDirs, getDirs, resolveDirs, getSummaryFile } from "./dirs.js";
|
|
7
|
-
export { createEventLogger } from "./event-logger.js";
|
|
8
|
-
export { backfillFromOpencode } from "./backfill.js";
|
|
9
|
-
export { querySummaries, countSummaries, getDetail, getSubagentSteps, getSessionRaw, setSessionTaskId, reindexDir, upsertSessionSnapshot, openSummaryDb, closeSummaryDbs, removeSession, extractSummaryFields, snapshotToSummary, detailFromRaw, detailFullFromRaw, sumTokens, subtractTokens, toIsoString, firstUserMessageOf, } from "./summary-store.js";
|
|
10
|
-
/**
|
|
11
|
-
* 组装一整套"通用统计插件"的运行时:事件记录 + 指标引擎。
|
|
12
|
-
* 目录为实例级(不产生全局副作用):defaultDirs → 进程级默认(configureDirs)→ opts.dirs 三层合并。
|
|
13
|
-
* 同一进程可创建多个 runtime(不同插件各配各目录),scope 门控同时作用于引擎计数与事件写盘。
|
|
14
|
-
*/
|
|
15
|
-
export function createMetricsRuntime(opts = {}) {
|
|
16
|
-
const dirs = resolveDirs(opts.dirs);
|
|
17
|
-
const engine = createMetricsEngine({
|
|
18
|
-
enabled: opts.enabled,
|
|
19
|
-
scope: opts.scope,
|
|
20
|
-
dirs,
|
|
21
|
-
onFlush: opts.onFlush,
|
|
22
|
-
});
|
|
23
|
-
const eventLogger = (opts.eventLogging ?? true)
|
|
24
|
-
? createEventLogger(true, false, {
|
|
25
|
-
eventsDir: dirs.eventsDir,
|
|
26
|
-
// 事件记录与引擎共用同一归属判定:引擎不统计的会话不写事件日志
|
|
27
|
-
isTracked: (sessionId, event) => engine.shouldTrack(sessionId, event.type, event.properties),
|
|
28
|
-
})
|
|
29
|
-
: null;
|
|
30
|
-
if (opts.backfillOnStart) {
|
|
31
|
-
const scope = opts.scope ?? { mode: "all" };
|
|
32
|
-
const staticallyAttributable = scope.mode === "all" || (scope.agents?.length ?? 0) > 0 || (scope.cwdPrefixes?.length ?? 0) > 0;
|
|
33
|
-
if (staticallyAttributable) {
|
|
34
|
-
setImmediate(() => {
|
|
35
|
-
backfillFromOpencode({
|
|
36
|
-
engines: [{
|
|
37
|
-
label: "self",
|
|
38
|
-
metricsDir: dirs.metricsDir,
|
|
39
|
-
agents: scope.agents,
|
|
40
|
-
cwdPrefixes: scope.cwdPrefixes,
|
|
41
|
-
summaryFile: dirs.summaryFile, // 与 live flush 同库(缺省全局共享库)
|
|
42
|
-
}],
|
|
43
|
-
skipExisting: true,
|
|
44
|
-
incremental: true,
|
|
45
|
-
}).catch((err) => {
|
|
46
|
-
console.error("[opencode-metrics-plugin] 启动增量回填失败:", err);
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return {
|
|
52
|
-
dirs,
|
|
53
|
-
eventLogger,
|
|
54
|
-
engine,
|
|
55
|
-
/** opencode event hook:`async ({ event }) => { runtime.event({ event }) }` */
|
|
56
|
-
event(input) {
|
|
57
|
-
eventLogger?.log(input.event);
|
|
58
|
-
engine.ingest(input.event);
|
|
59
|
-
},
|
|
60
|
-
};
|
|
61
|
-
}
|
|
1
|
+
// opencode-metrics-plugin 包出口
|
|
2
|
+
// 默认导出即 opencode 插件入口(dist/index.js);metrics 域与 shared/log 为可编程 API
|
|
3
|
+
export { MetricsPlugin, default } from './plugin.js';
|
|
4
|
+
export * from './metrics/index.js';
|
|
5
|
+
export { setLogLevel, setLogFile, flushLogs } from './shared/log.js';
|