@goodandready/dsh-cost-meter 0.8.27 → 0.8.28

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/commands.js CHANGED
@@ -60,6 +60,10 @@ export function formatCostReceipt(data) {
60
60
  `| **Output Tokens** | ${formatNumber(output)} |`,
61
61
  ]
62
62
 
63
+ if (tokens.reasoningTokens > 0) {
64
+ lines.push(`| **Thinking / Reasoning** | 🧠 ${formatNumber(tokens.reasoningTokens)} |`)
65
+ }
66
+
63
67
  if (cacheWrite > 0) {
64
68
  lines.push(`| **Cache Write** | ${formatNumber(cacheWrite)} |`)
65
69
  }
package/lib/export.js CHANGED
@@ -40,6 +40,7 @@ export function generateSessionCsv(data) {
40
40
  'Cache_Read_Tokens',
41
41
  'Cache_Hit_Rate_%',
42
42
  'Output_Tokens',
43
+ 'Reasoning_Tokens',
43
44
  'Total_Tokens',
44
45
  'Cost_USD',
45
46
  'Cost_Local',
@@ -51,6 +52,7 @@ export function generateSessionCsv(data) {
51
52
  let sumUncached = 0
52
53
  let sumHit = 0
53
54
  let sumOut = 0
55
+ let sumReasoning = 0
54
56
  let sumTotalTok = 0
55
57
  let sumCostUsd = 0
56
58
 
@@ -60,6 +62,7 @@ export function generateSessionCsv(data) {
60
62
  const uncached = Number(t.uncachedInputTokens) || 0
61
63
  const hit = Number(t.cacheReadTokens) || 0
62
64
  const out = Number(t.outputTokens) || 0
65
+ const reasoning = Number(t.reasoningTokens) || 0
63
66
  const totalTok = uncached + hit + out
64
67
  const hitRate = t.cacheHitRate !== undefined
65
68
  ? t.cacheHitRate
@@ -70,6 +73,7 @@ export function generateSessionCsv(data) {
70
73
  sumUncached += uncached
71
74
  sumHit += hit
72
75
  sumOut += out
76
+ sumReasoning += reasoning
73
77
  sumTotalTok += totalTok
74
78
  sumCostUsd += costUsd
75
79
 
@@ -83,6 +87,7 @@ export function generateSessionCsv(data) {
83
87
  hit,
84
88
  hitRate,
85
89
  out,
90
+ reasoning,
86
91
  totalTok,
87
92
  costUsd.toFixed(6),
88
93
  costLocal.toFixed(4),
@@ -103,6 +108,7 @@ export function generateSessionCsv(data) {
103
108
  sumHit,
104
109
  overallHitRate,
105
110
  sumOut,
111
+ sumReasoning,
106
112
  sumTotalTok,
107
113
  (totalCostUsd || sumCostUsd).toFixed(6),
108
114
  ((totalCostUsd || sumCostUsd) * fx).toFixed(4),
@@ -137,14 +143,17 @@ export function generateSessionJson(data) {
137
143
  let sumUncached = 0
138
144
  let sumHit = 0
139
145
  let sumOut = 0
146
+ let sumReasoning = 0
140
147
 
141
148
  const turnsFormatted = turns.map((t) => {
142
149
  const uncached = Number(t.uncachedInputTokens) || 0
143
150
  const hit = Number(t.cacheReadTokens) || 0
144
151
  const out = Number(t.outputTokens) || 0
152
+ const reasoning = Number(t.reasoningTokens) || 0
145
153
  sumUncached += uncached
146
154
  sumHit += hit
147
155
  sumOut += out
156
+ sumReasoning += reasoning
148
157
  return {
149
158
  turn: t.turn,
150
159
  step: t.step ?? 0,
@@ -153,6 +162,7 @@ export function generateSessionJson(data) {
153
162
  uncachedInput: uncached,
154
163
  cacheRead: hit,
155
164
  output: out,
165
+ reasoningTokens: reasoning,
156
166
  total: uncached + hit + out,
157
167
  cacheHitRate: t.cacheHitRate !== undefined ? t.cacheHitRate : (uncached + hit > 0 ? Math.round((hit / (uncached + hit)) * 100) : 0),
158
168
  },
@@ -180,6 +190,7 @@ export function generateSessionJson(data) {
180
190
  uncachedInput: sumUncached,
181
191
  cacheRead: sumHit,
182
192
  output: sumOut,
193
+ reasoningTokens: sumReasoning,
183
194
  total: sumUncached + sumHit + sumOut,
184
195
  cacheHitRate: overallHitRate,
185
196
  },
@@ -230,7 +241,7 @@ export function renderSummaryCardToCanvas(canvas, data) {
230
241
  } = data || {}
231
242
 
232
243
  const totalCost = costUsd * fx
233
- const costFormatted = totalCost >= 1 ? `${currency}${totalCost.toFixed(2)}` : (totalCost > 0 ? `${currency}${totalCost.toFixed(4)}` : `${currency}0.00`)
244
+ const costFormatted = data?.privacyMode ? `${currency}****` : (totalCost >= 1 ? `${currency}${totalCost.toFixed(2)}` : (totalCost > 0 ? `${currency}${totalCost.toFixed(4)}` : `${currency}0.00`))
234
245
  const uncached = Number(tokens.uncachedInputTokens || tokens.inputTokens) || 0
235
246
  const hit = Number(tokens.cacheReadTokens) || 0
236
247
  const out = Number(tokens.outputTokens) || 0
package/lib/index.js CHANGED
@@ -131,6 +131,9 @@ export function normalizeConfig(raw) {
131
131
  alertBeforeSwitch: u.alertBeforeSwitch !== false,
132
132
  alertLeadMinutes: Math.max(1, Math.min(30, Number(u.alertLeadMinutes) || 5)),
133
133
  browserNotifications: Boolean(u.browserNotifications),
134
+ privacyMode: Boolean(u.privacyMode),
135
+ showHeaderChip: u.showHeaderChip !== false,
136
+ showMessageAction: u.showMessageAction !== false,
134
137
  useOpenRouter: u.useOpenRouter !== false,
135
138
  refreshHours: Number(u.refreshHours) || 24,
136
139
  deepseekPeakPrices: u.deepseekPeakPrices || {},
@@ -162,6 +165,9 @@ export const Config = z.object({
162
165
  alertBeforeSwitch: z.boolean().description('Pre-switch notification and visual alert before tariff change.').default(true).volatile(),
163
166
  alertLeadMinutes: z.number().description('Minutes before tariff change to trigger pre-switch alert.').default(5).volatile(),
164
167
  browserNotifications: z.boolean().description('Enable desktop browser notifications for tariff switches.').default(false).volatile(),
168
+ privacyMode: z.boolean().description('Streamer/Privacy mode: mask financial spend with asterisks while retaining token stats.').default(false).volatile(),
169
+ showHeaderChip: z.boolean().description('Show the cost meter chip in the conversation header.').default(true).volatile(),
170
+ showMessageAction: z.boolean().description('Show the token inspector action button on assistant messages.').default(true).volatile(),
165
171
  deepseekPeakPrices: z.dict(PriceRow).description('Override the built-in DeepSeek peak rates, by model id.').default({}).volatile(),
166
172
  useOpenRouter: z.boolean().description('Pull rates and discount windows from the public OpenRouter catalog.').default(true).volatile(),
167
173
  refreshHours: z.number().description('How often to refetch the catalog.').default(24).volatile(),
package/lib/projection.js CHANGED
@@ -20,6 +20,7 @@ export const bucketSchema = zod.object({
20
20
  outputTokens: zod.number().nonnegative(),
21
21
  cacheReadTokens: zod.number().nonnegative(),
22
22
  cacheWriteTokens: zod.number().nonnegative(),
23
+ reasoningTokens: zod.number().nonnegative().optional(),
23
24
  }).strict()
24
25
 
25
26
  export const turnRecordSchema = zod.object({
@@ -30,6 +31,7 @@ export const turnRecordSchema = zod.object({
30
31
  outputTokens: zod.number().nonnegative().optional(),
31
32
  cacheReadTokens: zod.number().nonnegative().optional(),
32
33
  cacheWriteTokens: zod.number().nonnegative().optional(),
34
+ reasoningTokens: zod.number().nonnegative().optional(),
33
35
  costDeltaUsd: zod.number().nonnegative().optional(),
34
36
  timestamp: zod.number().nonnegative(),
35
37
  durationMs: zod.number().nonnegative().optional(),
@@ -38,11 +40,19 @@ export const turnRecordSchema = zod.object({
38
40
 
39
41
  /** Pull the four buckets out of one provider usage record. */
40
42
  export function bucketsFromUsage(usage) {
43
+ const reasoningTokens = Number(
44
+ usage?.completion_tokens_details?.reasoning_tokens ||
45
+ usage?.reasoningTokens ||
46
+ usage?.reasoning_tokens ||
47
+ usage?.details?.reasoning_tokens ||
48
+ 0
49
+ ) || 0
41
50
  return {
42
51
  uncachedInputTokens: Number(usage.inputTokens) || 0,
43
52
  outputTokens: Number(usage.outputTokens) || 0,
44
53
  cacheReadTokens: Number(usage.cacheReadTokens) || 0,
45
54
  cacheWriteTokens: Number(usage.cacheWriteTokens) || 0,
55
+ reasoningTokens,
46
56
  }
47
57
  }
48
58
 
@@ -180,6 +190,7 @@ export const usageByModelProjection = {
180
190
  outputTokens: buckets.outputTokens,
181
191
  cacheReadTokens: buckets.cacheReadTokens,
182
192
  cacheWriteTokens: buckets.cacheWriteTokens,
193
+ reasoningTokens: buckets.reasoningTokens || 0,
183
194
  costDeltaUsd: nextTurns[existingIndex].costDeltaUsd || 0,
184
195
  timestamp,
185
196
  durationMs: nextTurns[existingIndex].durationMs || durationMs,
@@ -194,6 +205,7 @@ export const usageByModelProjection = {
194
205
  outputTokens: buckets.outputTokens,
195
206
  cacheReadTokens: buckets.cacheReadTokens,
196
207
  cacheWriteTokens: buckets.cacheWriteTokens,
208
+ reasoningTokens: buckets.reasoningTokens || 0,
197
209
  costDeltaUsd: 0,
198
210
  timestamp,
199
211
  durationMs,
package/lib/routes.js CHANGED
@@ -111,6 +111,9 @@ export function registerRoutes(ctx, options) {
111
111
  fxRates: { USD: 1, ...(getCachedFxState().rates || {}) },
112
112
  displayTimeZone: typeof cfg?.displayTimeZone === 'string' && cfg.displayTimeZone ? cfg.displayTimeZone : 'Europe/Moscow',
113
113
  budgetThreshold: Number(cfg?.budgetThreshold) || 0,
114
+ privacyMode: Boolean(cfg?.privacyMode),
115
+ showHeaderChip: cfg?.showHeaderChip !== false,
116
+ showMessageAction: cfg?.showMessageAction !== false,
114
117
  displayMode: cfg?.displayMode || 'auto',
115
118
  alertBeforeSwitch: cfg?.alertBeforeSwitch !== false,
116
119
  alertLeadMinutes: Number(cfg?.alertLeadMinutes) || 5,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-cost-meter",
3
- "version": "0.8.27",
3
+ "version": "0.8.28",
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",