@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 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 && usage && Number(usage.cacheReadTokens) > 0 && active.input > active.cacheHit) {
1200
- const tokensRead = Number(usage.cacheReadTokens)
1201
- const missedCost = (tokensRead / 1e6) * active.input
1202
- const hitCost = (tokensRead / 1e6) * active.cacheHit
1203
- cacheSavedAmount = Math.max(0, (missedCost - hitCost) * fx)
1204
- cacheSavedPercent = missedCost > 0 ? Math.round(((missedCost - hitCost) / missedCost) * 100) : 0
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.17",
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",