@goodandready/dsh-cost-meter 0.8.17 → 0.8.18
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/lib/client.js +56 -9
- package/lib/tariff.js +22 -0
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -961,6 +961,28 @@ window.__ModuleLoader__.load({
|
|
|
961
961
|
return usd
|
|
962
962
|
}
|
|
963
963
|
|
|
964
|
+
function cacheSavingsBySlots(slots, schedule) {
|
|
965
|
+
let savedUsd = 0
|
|
966
|
+
let missedUsd = 0
|
|
967
|
+
let readTokens = 0
|
|
968
|
+
for (const key of Object.keys(slots || {})) {
|
|
969
|
+
const minute = (Number(key) * 30 + 15) % 1440
|
|
970
|
+
const rates = ratesAt(schedule, minute)
|
|
971
|
+
const u = slots[key] || {}
|
|
972
|
+
const cRead = Number(u.cacheReadTokens) || 0
|
|
973
|
+
if (cRead > 0 && rates && rates.input > rates.cacheHit) {
|
|
974
|
+
readTokens += cRead
|
|
975
|
+
const missed = (cRead / 1e6) * (rates.input || 0)
|
|
976
|
+
const hit = (cRead / 1e6) * (rates.cacheHit || 0)
|
|
977
|
+
savedUsd += Math.max(0, missed - hit)
|
|
978
|
+
missedUsd += missed
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
const percent = missedUsd > 0 ? Math.round((savedUsd / missedUsd) * 100) : 0
|
|
982
|
+
return { savedUsd, missedUsd, readTokens, percent }
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
|
|
964
986
|
function spendUsd(usage, rates) {
|
|
965
987
|
const u = usage || {}
|
|
966
988
|
const perMillion =
|
|
@@ -1193,17 +1215,36 @@ window.__ModuleLoader__.load({
|
|
|
1193
1215
|
.join(', ')
|
|
1194
1216
|
const nowLocal = hhmmInZone(minute, zone, now)
|
|
1195
1217
|
|
|
1196
|
-
// Calculate cache savings amount
|
|
1218
|
+
// Calculate cache savings amount across all used models (#92)
|
|
1197
1219
|
let cacheSavedAmount = 0
|
|
1198
1220
|
let cacheSavedPercent = 0
|
|
1199
|
-
if (hasUsage
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1221
|
+
if (hasUsage) {
|
|
1222
|
+
if (perModel.length > 0) {
|
|
1223
|
+
let totalSavedUsd = 0
|
|
1224
|
+
let totalMissedUsd = 0
|
|
1225
|
+
for (const m of perModel) {
|
|
1226
|
+
const own = schedules[m.key]
|
|
1227
|
+
const sched = own && own.schedule ? own.schedule : schedule
|
|
1228
|
+
if (sched) {
|
|
1229
|
+
const res = cacheSavingsBySlots(m.slots || (m.usage ? { 0: m.usage } : {}), sched)
|
|
1230
|
+
totalSavedUsd += res.savedUsd
|
|
1231
|
+
totalMissedUsd += res.missedUsd
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
if (totalSavedUsd > 0) {
|
|
1235
|
+
cacheSavedAmount = totalSavedUsd * fx
|
|
1236
|
+
cacheSavedPercent = totalMissedUsd > 0 ? Math.round((totalSavedUsd / totalMissedUsd) * 100) : 0
|
|
1237
|
+
}
|
|
1238
|
+
} else if (usage && Number(usage.cacheReadTokens) > 0 && active.input > active.cacheHit) {
|
|
1239
|
+
const tokensRead = Number(usage.cacheReadTokens)
|
|
1240
|
+
const missedCost = (tokensRead / 1e6) * active.input
|
|
1241
|
+
const hitCost = (tokensRead / 1e6) * active.cacheHit
|
|
1242
|
+
cacheSavedAmount = Math.max(0, (missedCost - hitCost) * fx)
|
|
1243
|
+
cacheSavedPercent = missedCost > 0 ? Math.round(((missedCost - hitCost) / missedCost) * 100) : 0
|
|
1244
|
+
}
|
|
1205
1245
|
}
|
|
1206
1246
|
|
|
1247
|
+
|
|
1207
1248
|
// Copy summary report to clipboard
|
|
1208
1249
|
const copyReport = () => {
|
|
1209
1250
|
const lines = [
|
|
@@ -1255,9 +1296,11 @@ window.__ModuleLoader__.load({
|
|
|
1255
1296
|
key: 'chip', type: 'button',
|
|
1256
1297
|
className: 'dcm-chip',
|
|
1257
1298
|
onClick: () => setOpen((v) => !v),
|
|
1258
|
-
title: budgetExceeded
|
|
1299
|
+
title: (budgetExceeded
|
|
1259
1300
|
? t('budget.exceeded', { limit: money(budgetThreshold * fx, currency) })
|
|
1260
|
-
: (flat ? 'Flat tariff' : cheapNow ? t('offpeak') : t('peak'))
|
|
1301
|
+
: (flat ? 'Flat tariff' : cheapNow ? t('offpeak') : t('peak'))) +
|
|
1302
|
+
(cacheSavedAmount > 0 ? ' · ' + t('savings.cache_saved', { amount: money(cacheSavedAmount, currency), percent: cacheSavedPercent }) : ''),
|
|
1303
|
+
|
|
1261
1304
|
}, [
|
|
1262
1305
|
h('span', { key: 'dot', className: dotClass }),
|
|
1263
1306
|
h('span', { key: 'sum' }, hasUsage ? '≈ ' + money(total, currency) : '—'),
|
|
@@ -1480,8 +1523,12 @@ window.__ModuleLoader__.load({
|
|
|
1480
1523
|
minutesUntilChange,
|
|
1481
1524
|
extremes,
|
|
1482
1525
|
ratesAt,
|
|
1526
|
+
spendBySlots,
|
|
1527
|
+
cacheSavingsBySlots,
|
|
1483
1528
|
fetchWithTimeout,
|
|
1484
1529
|
}
|
|
1530
|
+
|
|
1485
1531
|
return module.exports
|
|
1486
1532
|
},
|
|
1487
1533
|
})
|
|
1534
|
+
|
package/lib/tariff.js
CHANGED
|
@@ -252,3 +252,25 @@ export function resolveTariff(rawCfg, index, route) {
|
|
|
252
252
|
}
|
|
253
253
|
return { schedule: null, source: 'none', matchedId: null }
|
|
254
254
|
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Calculate saved cost from prompt cache hits vs regular input price (#92).
|
|
258
|
+
* Returns { savedUsd, readTokens, percent }
|
|
259
|
+
*/
|
|
260
|
+
export function calculateCacheSavings(usage, rates) {
|
|
261
|
+
const u = usage || {}
|
|
262
|
+
const r = rates || {}
|
|
263
|
+
const readTokens = Number(u.cacheReadTokens) || 0
|
|
264
|
+
const inputRate = Number(r.input) || 0
|
|
265
|
+
const cacheHitRate = Number(r.cacheHit) || 0
|
|
266
|
+
if (readTokens <= 0 || inputRate <= cacheHitRate) {
|
|
267
|
+
return { savedUsd: 0, readTokens: 0, percent: 0 }
|
|
268
|
+
}
|
|
269
|
+
const missedUsd = (readTokens / 1e6) * inputRate
|
|
270
|
+
const hitUsd = (readTokens / 1e6) * cacheHitRate
|
|
271
|
+
const savedUsd = Math.max(0, missedUsd - hitUsd)
|
|
272
|
+
const percent = missedUsd > 0 ? Math.round((savedUsd / missedUsd) * 100) : 0
|
|
273
|
+
return { savedUsd, readTokens, percent }
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-cost-meter",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.18",
|
|
4
4
|
"description": "DeepSeek Harness plugin: a session cost chip in the conversation header - live token usage by bucket, peak/off-peak tariff, and a countdown to the next tariff switch.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|