@goodandready/dsh-cost-meter 0.8.16 → 0.8.17

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/index.js CHANGED
@@ -21,19 +21,16 @@ import z from '@deepseek-ai/schemastery'
21
21
 
22
22
  // Ensure .volatile() helper exists on Schemastery schema prototype for DSH 0.2
23
23
  try {
24
- const schemaProto = Object.getPrototypeOf(z.boolean())
25
- if (schemaProto && typeof schemaProto.volatile !== 'function') {
26
- schemaProto.volatile = function () {
27
- return typeof this.extra === 'function' ? this.extra('volatile', true) : this
28
- }
24
+ const p = Object.getPrototypeOf(z.boolean())
25
+ if (p && typeof p.volatile !== 'function') {
26
+ p.volatile = function () { return typeof this.extra === 'function' ? this.extra('volatile', true) : this }
29
27
  }
30
- } catch {
31
- // safe fallback
32
- }
28
+ } catch {}
29
+
33
30
  export * from './tariff.js'
34
31
  export * from './projection.js'
35
32
  export { STATE_PATH, REFRESH_PATH } from './routes.js'
36
- import { indexCatalog, resolveTariff, matchModel } from './tariff.js'
33
+ import { indexCatalog, resolveTariff, matchModel, unwrapVolatile } from './tariff.js'
37
34
  import { usageByModelProjection } from './projection.js'
38
35
 
39
36
  export const name = '@goodandready/dsh-cost-meter'
@@ -43,12 +40,30 @@ const NS = 'dsh-cost-meter'
43
40
  export function isLoopback(value) {
44
41
  const address = value?.toLowerCase().replace(/^\[|\]$/g, '')
45
42
  return address === 'localhost' || address === 'localhost.' || address === '::1'
46
- || address?.startsWith('127.') === true
47
- || address?.startsWith('::ffff:127.') === true
43
+ || address?.startsWith('127.') === true || address?.startsWith('::ffff:127.') === true
44
+ }
45
+
46
+ export function getConnection(ctx) {
47
+ try {
48
+ const f = ctx?.reflect?.get
49
+ ? (ctx.reflect.get('connection') || ctx?.connection)
50
+ : (ctx?.get ? (ctx.get('connection') || ctx?.connection) : Reflect.get(ctx || {}, 'connection'))
51
+ return f || (ctx?.root ? getConnection(ctx.root) : undefined)
52
+ } catch {
53
+ return undefined
54
+ }
48
55
  }
49
56
 
50
- export function isTrustedCaller(request) {
57
+ export function isTrustedCaller(request, ctx = null) {
51
58
  if (!request) return false
59
+ const conn = getConnection(ctx) || request?.connectionService || request?.connection
60
+ if (conn && typeof conn.requestRejection === 'function') {
61
+ try {
62
+ if (conn.requestRejection(request) !== undefined) return false
63
+ } catch {
64
+ return false
65
+ }
66
+ }
52
67
  const remote = request.socket?.remoteAddress || request.connection?.remoteAddress
53
68
  const site = request.headers?.['sec-fetch-site']
54
69
  if (site === 'cross-site') return false
@@ -95,6 +110,24 @@ export function isTrustedCaller(request) {
95
110
  return false
96
111
  }
97
112
 
113
+ export function normalizeConfig(raw) {
114
+ const u = unwrapVolatile(raw) || {}
115
+ return {
116
+ ...u,
117
+ currency: typeof u.currency === 'string' && u.currency ? u.currency : '$',
118
+ usdRate: Number(u.usdRate) > 0 ? Number(u.usdRate) : 1,
119
+ displayTimeZone: typeof u.displayTimeZone === 'string' && u.displayTimeZone ? u.displayTimeZone : 'Europe/Moscow',
120
+ budgetThreshold: Number(u.budgetThreshold) || 0,
121
+ useOpenRouter: u.useOpenRouter !== false,
122
+ refreshHours: Number(u.refreshHours) || 24,
123
+ deepseekPeakPrices: u.deepseekPeakPrices || {},
124
+ modelMap: u.modelMap || {},
125
+ prices: u.prices || {},
126
+ manualPeakWindowsUtc: Array.isArray(u.manualPeakWindowsUtc) ? u.manualPeakWindowsUtc : [],
127
+ manualOffPeakMultiplier: Number(u.manualOffPeakMultiplier) || 1,
128
+ }
129
+ }
130
+
98
131
  const CATALOG_URL = 'https://openrouter.ai/api/v1/models'
99
132
  const CACHE_FILE = join(process.env.DSH_HOME || join(homedir(), '.dsh'), 'storages', 'dsh-cost-meter-catalog.json')
100
133
 
@@ -109,47 +142,15 @@ const PriceRow = z.object({
109
142
  export const Config = z.object({
110
143
  currency: z.string().description('Currency symbol shown in the widget.').default('$').volatile(),
111
144
  usdRate: z.number().description('Units of `currency` per USD. 1 keeps everything in USD.').default(1).volatile(),
112
- displayTimeZone: z
113
- .string()
114
- .description('IANA zone the widget shows tariff hours in.')
115
- .default('Europe/Moscow')
116
- .volatile(),
117
- budgetThreshold: z
118
- .number()
119
- .description('Optional session budget threshold in USD. 0 disables threshold warnings.')
120
- .default(0)
121
- .volatile(),
122
- deepseekPeakPrices: z
123
- .dict(PriceRow)
124
- .description('Override the built-in DeepSeek peak rates, by model id.')
125
- .default({})
126
- .volatile(),
127
- useOpenRouter: z
128
- .boolean()
129
- .description('Pull rates and discount windows from the public OpenRouter catalog.')
130
- .default(true)
131
- .volatile(),
145
+ displayTimeZone: z.string().description('IANA zone the widget shows tariff hours in.').default('Europe/Moscow').volatile(),
146
+ budgetThreshold: z.number().description('Optional session budget threshold in USD. 0 disables threshold warnings.').default(0).volatile(),
147
+ deepseekPeakPrices: z.dict(PriceRow).description('Override the built-in DeepSeek peak rates, by model id.').default({}).volatile(),
148
+ useOpenRouter: z.boolean().description('Pull rates and discount windows from the public OpenRouter catalog.').default(true).volatile(),
132
149
  refreshHours: z.number().description('How often to refetch the catalog.').default(24).volatile(),
133
- modelMap: z
134
- .dict(z.string())
135
- .description('Route override: "provider/model" -> OpenRouter model id.')
136
- .default({})
137
- .volatile(),
138
- prices: z
139
- .dict(PriceRow)
140
- .description('Manual rates by "provider/model" (or "*"). Win over the catalog.')
141
- .default({})
142
- .volatile(),
143
- manualPeakWindowsUtc: z
144
- .array(z.string())
145
- .description('Peak windows HH:MM-HH:MM UTC, used only with manual prices.')
146
- .default([])
147
- .volatile(),
148
- manualOffPeakMultiplier: z
149
- .number()
150
- .description('Multiplier outside the manual peak windows.')
151
- .default(1)
152
- .volatile(),
150
+ modelMap: z.dict(z.string()).description('Route override: "provider/model" -> OpenRouter model id.').default({}).volatile(),
151
+ prices: z.dict(PriceRow).description('Manual rates by "provider/model" (or "*"). Win over the catalog.').default({}).volatile(),
152
+ manualPeakWindowsUtc: z.array(z.string()).description('Peak windows HH:MM-HH:MM UTC, used only with manual prices.').default([]).volatile(),
153
+ manualOffPeakMultiplier: z.number().description('Multiplier outside the manual peak windows.').default(1).volatile(),
153
154
  })
154
155
 
155
156
  // Per-model usage projection is defined in and exported from ./projection.js
@@ -180,9 +181,8 @@ export function splitRoute(key) {
180
181
  }
181
182
 
182
183
  export function apply(ctx, config) {
183
- let current = config
184
+ let current = normalizeConfig(config)
184
185
  let catalog = { index: { byId: {}, byLowerId: {}, bySuffix: {} }, fetchedAt: 0, error: null }
185
- let lastManualRefreshAt = 0
186
186
 
187
187
  // Server-side cache for resolved tariffs: key -> { schedule, source, matchedId }
188
188
  const resolveCache = new Map()
@@ -255,14 +255,14 @@ export function apply(ctx, config) {
255
255
  }
256
256
  const row = readRow()
257
257
  if (row?.value && typeof row.value === 'object') {
258
- current = { ...config, ...row.value }
258
+ current = normalizeConfig({ ...config, ...row.value })
259
259
  clearResolveCache()
260
260
  }
261
261
  ctx.on('settings/document-updated', (ns) => {
262
262
  if (ns && ns !== NS) return
263
263
  const fresh = readRow()
264
264
  if (fresh?.value && typeof fresh.value === 'object') {
265
- current = { ...config, ...fresh.value }
265
+ current = normalizeConfig({ ...config, ...fresh.value })
266
266
  }
267
267
  clearResolveCache()
268
268
  })
@@ -282,7 +282,8 @@ export function apply(ctx, config) {
282
282
  refresh,
283
283
  splitRoute,
284
284
  defaultRoute,
285
- getCurrentConfig: () => current ?? config,
285
+ getCurrentConfig: () => (current ? normalizeConfig(current) : normalizeConfig(config)),
286
286
  getCatalog: () => catalog,
287
287
  })
288
288
  }
289
+
package/lib/routes.js CHANGED
@@ -26,7 +26,7 @@ export function registerRoutes(ctx, options) {
26
26
  path: REFRESH_PATH,
27
27
  method: 'POST',
28
28
  handler: async (req, res) => {
29
- if (!isTrustedCaller(req)) {
29
+ if (!isTrustedCaller(req, ctx)) {
30
30
  res.writeHead(403, { 'Content-Type': 'application/json' })
31
31
  res.end(JSON.stringify({ error: 'forbidden' }))
32
32
  return
@@ -61,7 +61,7 @@ export function registerRoutes(ctx, options) {
61
61
  path: STATE_PATH,
62
62
  method: 'GET',
63
63
  handler: async (req, res) => {
64
- if (!isTrustedCaller(req)) {
64
+ if (!isTrustedCaller(req, ctx)) {
65
65
  res.writeHead(403, { 'Content-Type': 'application/json' })
66
66
  res.end(JSON.stringify({ error: 'forbidden' }))
67
67
  return
@@ -94,10 +94,10 @@ export function registerRoutes(ctx, options) {
94
94
  }
95
95
 
96
96
  const payloadObj = {
97
- currency: cfg.currency,
98
- usdRate: cfg.usdRate,
99
- displayTimeZone: cfg.displayTimeZone || 'Europe/Moscow',
100
- budgetThreshold: Number(cfg.budgetThreshold) || 0,
97
+ currency: typeof cfg?.currency === 'string' && cfg.currency ? cfg.currency : '$',
98
+ usdRate: Number(cfg?.usdRate) > 0 ? Number(cfg.usdRate) : 1,
99
+ displayTimeZone: typeof cfg?.displayTimeZone === 'string' && cfg.displayTimeZone ? cfg.displayTimeZone : 'Europe/Moscow',
100
+ budgetThreshold: Number(cfg?.budgetThreshold) || 0,
101
101
  route,
102
102
  source: own.source,
103
103
  matchedId: own.matchedId,
@@ -140,3 +140,4 @@ export function registerRoutes(ctx, options) {
140
140
  }), 'dsh-cost-meter: updater')
141
141
  }
142
142
  }
143
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-cost-meter",
3
- "version": "0.8.16",
3
+ "version": "0.8.17",
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",