@goodandready/dsh-cost-meter 0.8.9 → 0.8.11
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 +106 -37
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -38,21 +38,50 @@ export function isLoopback(value) {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
export function isTrustedCaller(request) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const site = request
|
|
44
|
-
if (site === 'same-origin' || site === 'same-site') return true
|
|
41
|
+
if (!request) return false
|
|
42
|
+
const remote = request.socket?.remoteAddress || request.connection?.remoteAddress
|
|
43
|
+
const site = request.headers?.['sec-fetch-site']
|
|
45
44
|
if (site === 'cross-site') return false
|
|
46
|
-
|
|
47
|
-
const host = request?.headers?.['host']
|
|
48
|
-
|
|
45
|
+
|
|
46
|
+
const host = request.headers?.['x-forwarded-host'] || request.headers?.['host']
|
|
47
|
+
const origin = request.headers?.['origin']
|
|
48
|
+
if (origin) {
|
|
49
49
|
try {
|
|
50
50
|
const url = new URL(origin)
|
|
51
|
-
if (url.
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
if (url.protocol === 'http:' || url.protocol === 'https:') {
|
|
52
|
+
if (host && url.host.toLowerCase() === host.toLowerCase()) return true
|
|
53
|
+
if (isLoopback(url.hostname) && isLoopback(remote)) return true
|
|
54
|
+
return false
|
|
55
|
+
}
|
|
56
|
+
// Non-http/https origin (e.g. dsh-app://app in DSH Desktop)
|
|
57
|
+
if (isLoopback(remote)) return true
|
|
58
|
+
return false
|
|
59
|
+
} catch {
|
|
60
|
+
return false
|
|
54
61
|
}
|
|
55
62
|
}
|
|
63
|
+
|
|
64
|
+
const referer = request.headers?.['referer']
|
|
65
|
+
if (referer) {
|
|
66
|
+
try {
|
|
67
|
+
const url = new URL(referer)
|
|
68
|
+
if (url.protocol === 'http:' || url.protocol === 'https:') {
|
|
69
|
+
if (host && url.host.toLowerCase() === host.toLowerCase()) return true
|
|
70
|
+
if (isLoopback(url.hostname) && isLoopback(remote)) return true
|
|
71
|
+
return false
|
|
72
|
+
}
|
|
73
|
+
// Non-http/https referer (e.g. dsh-app://app/ in DSH Desktop)
|
|
74
|
+
if (isLoopback(remote)) return true
|
|
75
|
+
return false
|
|
76
|
+
} catch {
|
|
77
|
+
return false
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (site === 'same-site') return false
|
|
82
|
+
if (site === 'same-origin') return true
|
|
83
|
+
if (isLoopback(remote)) return true
|
|
84
|
+
|
|
56
85
|
return false
|
|
57
86
|
}
|
|
58
87
|
|
|
@@ -167,15 +196,35 @@ export function slotOf(timeMs) {
|
|
|
167
196
|
return Math.floor((d.getUTCHours() * 60 + d.getUTCMinutes()) / 30)
|
|
168
197
|
}
|
|
169
198
|
|
|
170
|
-
const
|
|
199
|
+
const modelSlotsSchema = zod.record(zod.string(), bucketSchema)
|
|
200
|
+
|
|
201
|
+
const costByModelViewSchema = zod.object({
|
|
202
|
+
models: zod.array(zod.object({
|
|
203
|
+
provider: zod.string(),
|
|
204
|
+
model: zod.string(),
|
|
205
|
+
slots: modelSlotsSchema,
|
|
206
|
+
}).strict()),
|
|
207
|
+
}).strict()
|
|
208
|
+
|
|
209
|
+
const costByModelStateSchema = zod.object({
|
|
210
|
+
route: zod.object({
|
|
211
|
+
provider: zod.string(),
|
|
212
|
+
model: zod.string(),
|
|
213
|
+
}).nullable(),
|
|
214
|
+
byKey: zod.record(zod.string(), modelSlotsSchema),
|
|
215
|
+
last: zod.object({
|
|
216
|
+
turn: zod.number().int().nonnegative(),
|
|
217
|
+
step: zod.number().int().nonnegative(),
|
|
218
|
+
key: zod.string(),
|
|
219
|
+
slot: zod.string(),
|
|
220
|
+
buckets: bucketSchema,
|
|
221
|
+
}).nullable(),
|
|
222
|
+
}).strict()
|
|
223
|
+
|
|
224
|
+
export const usageByModelProjection = {
|
|
171
225
|
key: 'costByModel',
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
provider: zod.string(),
|
|
175
|
-
model: zod.string(),
|
|
176
|
-
slots: zod.record(zod.string(), bucketSchema),
|
|
177
|
-
})),
|
|
178
|
-
}).strict(),
|
|
226
|
+
stateVersion: 2,
|
|
227
|
+
stateSchema: costByModelStateSchema,
|
|
179
228
|
init: () => ({ route: null, byKey: {}, last: null }),
|
|
180
229
|
apply: (state, event) => {
|
|
181
230
|
if (event.type === 'request/header') {
|
|
@@ -211,17 +260,19 @@ const usageByModelProjection = {
|
|
|
211
260
|
last: { turn: sample.turn, step: sample.step, key, slot, buckets },
|
|
212
261
|
}
|
|
213
262
|
},
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
263
|
+
wire: {
|
|
264
|
+
viewSchema: costByModelViewSchema,
|
|
265
|
+
view: (state) => ({
|
|
266
|
+
models: Object.keys(state.byKey).map((key) => {
|
|
267
|
+
const slash = key.indexOf('/')
|
|
268
|
+
return {
|
|
269
|
+
provider: key.slice(0, slash),
|
|
270
|
+
model: key.slice(slash + 1),
|
|
271
|
+
slots: state.byKey[key],
|
|
272
|
+
}
|
|
273
|
+
}),
|
|
222
274
|
}),
|
|
223
|
-
}
|
|
224
|
-
stateVersion: 2,
|
|
275
|
+
},
|
|
225
276
|
}
|
|
226
277
|
|
|
227
278
|
/** The agent's default route, read off settings descriptors when available. */
|
|
@@ -309,19 +360,37 @@ export function apply(ctx, config) {
|
|
|
309
360
|
}
|
|
310
361
|
}
|
|
311
362
|
|
|
312
|
-
// Live config from
|
|
313
|
-
ctx.
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
363
|
+
// Live config reaction from GUI / settings service
|
|
364
|
+
ctx.on('settings/updated', (ns, next) => {
|
|
365
|
+
if (ns === NS && next) {
|
|
366
|
+
current = { ...config, ...next }
|
|
367
|
+
clearResolveCache()
|
|
368
|
+
}
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
ctx.on('settings/document-updated', (ns) => {
|
|
372
|
+
if (!ns || ns === NS) {
|
|
318
373
|
clearResolveCache()
|
|
319
|
-
}
|
|
374
|
+
}
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
ctx.inject(['settings'], (sctx) => {
|
|
378
|
+
if (typeof sctx.settings?.describe === 'function') {
|
|
379
|
+
try {
|
|
380
|
+
const desc = sctx.settings.describe(NS)
|
|
381
|
+
if (desc?.value && typeof desc.value === 'object') {
|
|
382
|
+
current = { ...config, ...desc.value }
|
|
383
|
+
clearResolveCache()
|
|
384
|
+
}
|
|
385
|
+
} catch {
|
|
386
|
+
/* namespace might not have descriptor yet */
|
|
387
|
+
}
|
|
388
|
+
}
|
|
320
389
|
})
|
|
321
390
|
|
|
322
391
|
// Hook the session projection so the widget can show spend breakdown.
|
|
323
|
-
ctx.inject(['
|
|
324
|
-
pctx.
|
|
392
|
+
ctx.inject(['sessionProjections'], (pctx) => {
|
|
393
|
+
pctx.sessionProjections.register(usageByModelProjection)
|
|
325
394
|
})
|
|
326
395
|
|
|
327
396
|
// Route 1: POST /dsh-cost-meter/refresh (manual catalog reload)
|
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.11",
|
|
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",
|