dsh-recent-usage-metrics 0.3.1 → 0.3.3
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/CHANGELOG.md +14 -0
- package/README.en.md +1 -1
- package/README.md +1 -1
- package/client/client.js +56 -68
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## 0.3.3
|
|
2
|
+
|
|
3
|
+
- 输出 Token 列的悬浮提示里,输入 token 改为带千分位的完整数字(`370,683 tok`),
|
|
4
|
+
不再是缩写。单元格本身仍缩写以保持对齐,提示存在的意义正是补回被缩写省掉的精度。
|
|
5
|
+
|
|
6
|
+
## 0.3.2
|
|
7
|
+
|
|
8
|
+
- 修复:速度列的悬浮提示常常拿不到模型名,或显示成别的模型。
|
|
9
|
+
原来从统计折叠的 `request/header` 事件推断,但那种事件只在模型或工具集变化时才写——
|
|
10
|
+
真实会话里 3139 次请求只对应 24 条,覆盖率约 1%,其余步骤只能借用几小时前的旧值。
|
|
11
|
+
- 改为读轨迹域的 assistant 节点:它把 `assistant/message.source` 原样复制成
|
|
12
|
+
`providerMetadata`,每步都有,是客户端半能拿到的唯一逐步事实。
|
|
13
|
+
- 没有轨迹节点的步骤保持空值,不再借用旧 header 猜测。
|
|
14
|
+
|
|
1
15
|
## 0.3.1
|
|
2
16
|
|
|
3
17
|
- 修复:切换模型后的第一步,抽屉速度列的悬浮提示显示了上一个模型。
|
package/README.en.md
CHANGED
|
@@ -151,7 +151,7 @@ Every drawer column and every header carries a hint:
|
|
|
151
151
|
| --- | --- |
|
|
152
152
|
| Clock column | the full timestamp `11-06 10:47:41.828` |
|
|
153
153
|
| Speed column | the model that served that row |
|
|
154
|
-
| Tokens column | input tokens and cache hit rate |
|
|
154
|
+
| Tokens column | input tokens (with thousands separators, e.g. `370,683 tok`) and cache hit rate |
|
|
155
155
|
| Headers | what the column measures (the `#` header draws the four state glyphs) |
|
|
156
156
|
|
|
157
157
|
The hints are a real overlay rather than `title`: `title` can only show plain text, and the
|
package/README.md
CHANGED
|
@@ -135,7 +135,7 @@ DSH 的极客级请求指标。它在 **设置 → 通用 → 性能与用量**
|
|
|
135
135
|
| --- | --- |
|
|
136
136
|
| 时间列 | 完整时间戳 `11-06 10:47:41.828` |
|
|
137
137
|
| 速度列 | 该行使用的模型 |
|
|
138
|
-
| 输出 Token 列 | 输入 token
|
|
138
|
+
| 输出 Token 列 | 输入 token(带千分位,如 `370,683 tok`)与缓存命中率 |
|
|
139
139
|
| 表头 | 该列的含义(`#` 一列用图标展示四种状态) |
|
|
140
140
|
|
|
141
141
|
提示是自建的浮层而不是 `title`:`title` 只能显示纯文本,而 `#` 列的说明要画出图标。
|
package/client/client.js
CHANGED
|
@@ -1172,72 +1172,31 @@ window.__ModuleLoader__.load({
|
|
|
1172
1172
|
/**
|
|
1173
1173
|
* Which model answered each step, keyed `turn:step`.
|
|
1174
1174
|
*
|
|
1175
|
-
*
|
|
1176
|
-
*
|
|
1177
|
-
*
|
|
1175
|
+
* Read from the Trajectory domain's assistant nodes, which carry `providerMetadata` copied
|
|
1176
|
+
* straight from `assistant/message.source`. That is the only per-step source of truth the
|
|
1177
|
+
* client half can reach:
|
|
1178
1178
|
*
|
|
1179
|
-
*
|
|
1179
|
+
* - The statistics fold puts no provider/model on a step, and its `request-header` rows are
|
|
1180
|
+
* written only when the routed model or tool set CHANGES — 24 of them for 3139 requests in
|
|
1181
|
+
* a real session, so they describe about 1% of steps and mislabel the rest.
|
|
1182
|
+
* - The Chat snapshot's assistant nodes drop the source entirely.
|
|
1180
1183
|
*
|
|
1181
|
-
*
|
|
1182
|
-
* `header.time <= step.startTime` misses the header that belongs to this request and
|
|
1183
|
-
* falls back to the PREVIOUS request's model. That showed the wrong provider on the first
|
|
1184
|
-
* step after a model switch.
|
|
1185
|
-
* 2. Not every step issues a new header: consecutive steps reuse the routed model, and then
|
|
1186
|
-
* the newest earlier header is the correct answer.
|
|
1187
|
-
*
|
|
1188
|
-
* So the window is tried first — every header from this step up to the next step's start —
|
|
1189
|
-
* and the newest earlier header is the fallback. Measured against `assistant/message.source`
|
|
1190
|
-
* on a real 3119-step log: the time-only rule matched 99.8%, this one matches 100%.
|
|
1191
|
-
*
|
|
1192
|
-
* `model/selection` is deliberately not used: it records what the user PICKED and can precede
|
|
1193
|
-
* the request that actually carries it, so it would name a model that was not called.
|
|
1184
|
+
* A compaction is not covered here: it has its own `compaction.model`.
|
|
1194
1185
|
*/
|
|
1195
|
-
function
|
|
1196
|
-
const turns = statsSnapshot?.turns
|
|
1186
|
+
function modelIndexFromTrajectory(trajectorySnapshot) {
|
|
1197
1187
|
const index = new Map()
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
if (
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
headers.sort((left, right) => left.time - right.time)
|
|
1211
|
-
}
|
|
1212
|
-
if (headers.length === 0) continue
|
|
1213
|
-
|
|
1214
|
-
const starts = []
|
|
1215
|
-
for (const step of turn.steps) {
|
|
1216
|
-
const at = numberOrNull(step.startTime)
|
|
1217
|
-
if (at === null || typeof step.step !== 'number') continue
|
|
1218
|
-
starts.push({ at, step: step.step })
|
|
1219
|
-
}
|
|
1220
|
-
if (starts.length === 0) continue
|
|
1221
|
-
starts.sort((left, right) => left.at - right.at)
|
|
1222
|
-
|
|
1223
|
-
for (let i = 0; i < starts.length; i += 1) {
|
|
1224
|
-
const from = starts[i].at
|
|
1225
|
-
const until = i + 1 < starts.length ? starts[i + 1].at : Number.POSITIVE_INFINITY
|
|
1226
|
-
let chosen = null
|
|
1227
|
-
for (const header of headers) {
|
|
1228
|
-
if (header.time < from) continue
|
|
1229
|
-
if (header.time >= until) break
|
|
1230
|
-
chosen = header
|
|
1231
|
-
}
|
|
1232
|
-
if (chosen === null) {
|
|
1233
|
-
for (const header of headers) {
|
|
1234
|
-
if (header.time < from) chosen = header
|
|
1235
|
-
else break
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
if (chosen === null) continue
|
|
1239
|
-
index.set(`${turn.turn}:${starts[i].step}`, chosen)
|
|
1240
|
-
}
|
|
1188
|
+
const nodes = trajectorySnapshot?.eventNodes
|
|
1189
|
+
if (!Array.isArray(nodes)) return index
|
|
1190
|
+
for (const node of nodes) {
|
|
1191
|
+
if (node === null || typeof node !== 'object') continue
|
|
1192
|
+
if (node.kind !== 'assistant') continue
|
|
1193
|
+
const meta = node.providerMetadata
|
|
1194
|
+
if (meta === null || typeof meta !== 'object') continue
|
|
1195
|
+
const model = typeof meta.model === 'string' && meta.model !== '' ? meta.model : null
|
|
1196
|
+
const provider = typeof meta.provider === 'string' && meta.provider !== '' ? meta.provider : null
|
|
1197
|
+
if (model === null && provider === null) continue
|
|
1198
|
+
if (typeof node.turn !== 'number' || typeof node.step !== 'number') continue
|
|
1199
|
+
index.set(`${node.turn}:${node.step}`, { provider, model })
|
|
1241
1200
|
}
|
|
1242
1201
|
return index
|
|
1243
1202
|
}
|
|
@@ -1295,9 +1254,8 @@ window.__ModuleLoader__.load({
|
|
|
1295
1254
|
* *after* selection: capping here at ten and then dropping the live step left the drawer
|
|
1296
1255
|
* showing nine rows for as long as a request was streaming.
|
|
1297
1256
|
*/
|
|
1298
|
-
function historyFromStats(statsSnapshot) {
|
|
1257
|
+
function historyFromStats(statsSnapshot, models) {
|
|
1299
1258
|
const steps = statsSteps(statsSnapshot)
|
|
1300
|
-
const models = modelIndexFromStats(statsSnapshot)
|
|
1301
1259
|
const rows = []
|
|
1302
1260
|
for (const entry of steps) {
|
|
1303
1261
|
// A step only has a row once it has started; `step/start` is what supplies this.
|
|
@@ -1305,7 +1263,7 @@ window.__ModuleLoader__.load({
|
|
|
1305
1263
|
if (!statsStepHasFigures(entry.metrics)) continue
|
|
1306
1264
|
const row = readingOfStatsStep(entry)
|
|
1307
1265
|
// Attribute the request to the model that served it, for the speed column's tooltip.
|
|
1308
|
-
const owner = models.get(row.key)
|
|
1266
|
+
const owner = models === undefined ? undefined : models.get(row.key)
|
|
1309
1267
|
if (owner !== undefined) {
|
|
1310
1268
|
row.model = owner.model
|
|
1311
1269
|
row.provider = owner.provider
|
|
@@ -1571,6 +1529,29 @@ window.__ModuleLoader__.load({
|
|
|
1571
1529
|
return approximate ? t('approxTokensUnit', { value: rounded }) : t('tokensUnit', { value: rounded })
|
|
1572
1530
|
}
|
|
1573
1531
|
|
|
1532
|
+
/**
|
|
1533
|
+
* A whole token count with thousands separators, for a hover hint.
|
|
1534
|
+
*
|
|
1535
|
+
* The cell itself has to stay narrow, so it abbreviates (`4.9k tok`). The hint has room, and
|
|
1536
|
+
* a reader comparing an input of 370683 against one of 1914 needs the exact digits — `370k`
|
|
1537
|
+
* and `371k` would hide the difference.
|
|
1538
|
+
*
|
|
1539
|
+
* The separator is a comma in both languages this plugin ships, so it is written out rather
|
|
1540
|
+
* than derived from the host locale: that keeps the hint identical in zh and en, and avoids
|
|
1541
|
+
* depending on a locale tag the slot props do not provide.
|
|
1542
|
+
*/
|
|
1543
|
+
function tokensExact(value, t) {
|
|
1544
|
+
if (value === null || value === undefined || !Number.isFinite(value)) return t('none')
|
|
1545
|
+
const digits = String(Math.round(Math.abs(value)))
|
|
1546
|
+
let grouped = ''
|
|
1547
|
+
for (let i = 0; i < digits.length; i += 1) {
|
|
1548
|
+
const fromEnd = digits.length - i
|
|
1549
|
+
grouped += digits[i]
|
|
1550
|
+
if (fromEnd > 1 && (fromEnd - 1) % 3 === 0) grouped += ','
|
|
1551
|
+
}
|
|
1552
|
+
return t('tokensUnit', { value: value < 0 ? `-${grouped}` : grouped })
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1574
1555
|
function clockText(at) {
|
|
1575
1556
|
const date = new Date(at)
|
|
1576
1557
|
if (Number.isNaN(date.getTime())) return '—'
|
|
@@ -1692,7 +1673,11 @@ window.__ModuleLoader__.load({
|
|
|
1692
1673
|
/** The tokens column's hint: the input side, which the cell itself does not show. */
|
|
1693
1674
|
function tokensHintText(row, t) {
|
|
1694
1675
|
const none = t('none')
|
|
1695
|
-
|
|
1676
|
+
/*
|
|
1677
|
+
* The input is spelled out with thousands separators; the cell above it abbreviates. The
|
|
1678
|
+
* hint exists precisely for the cases where the abbreviation loses information.
|
|
1679
|
+
*/
|
|
1680
|
+
const input = row.context === null || row.context === undefined ? none : tokensExact(row.context, t)
|
|
1696
1681
|
const hit =
|
|
1697
1682
|
row.cacheHit === null || row.cacheHit === undefined
|
|
1698
1683
|
? none
|
|
@@ -2999,8 +2984,11 @@ window.__ModuleLoader__.load({
|
|
|
2999
2984
|
* what made the drawer lag the log.
|
|
3000
2985
|
*/
|
|
3001
2986
|
const statsRows = useMemo(
|
|
3002
|
-
() =>
|
|
3003
|
-
|
|
2987
|
+
() =>
|
|
2988
|
+
sessionStats === undefined
|
|
2989
|
+
? undefined
|
|
2990
|
+
: historyFromStats(sessionStats, modelIndexFromTrajectory(trajectory)),
|
|
2991
|
+
[sessionStats, trajectory],
|
|
3004
2992
|
)
|
|
3005
2993
|
/*
|
|
3006
2994
|
* Fallback for a composition without the statistics target: derive rows from the Chat
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-recent-usage-metrics",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "Geek level for the Performance & usage setting: a live reading of the newest request (first-token latency, speed, tokens, total, amortised) plus a drawer listing the last ten, all derived from the shipped Statistics view data",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|