@goodandready/dsh-cost-meter 0.8.9 → 0.8.10
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 +96 -37
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -38,21 +38,40 @@ 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.host === host) return true
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
if (host && url.host.toLowerCase() === host.toLowerCase()) return true
|
|
52
|
+
if (isLoopback(url.hostname) && isLoopback(remote)) return true
|
|
53
|
+
return false
|
|
54
|
+
} catch {
|
|
55
|
+
return false
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const referer = request.headers?.['referer']
|
|
60
|
+
if (referer) {
|
|
61
|
+
try {
|
|
62
|
+
const url = new URL(referer)
|
|
63
|
+
if (host && url.host.toLowerCase() === host.toLowerCase()) return true
|
|
64
|
+
if (isLoopback(url.hostname) && isLoopback(remote)) return true
|
|
65
|
+
return false
|
|
66
|
+
} catch {
|
|
67
|
+
return false
|
|
54
68
|
}
|
|
55
69
|
}
|
|
70
|
+
|
|
71
|
+
if (site === 'same-site') return false
|
|
72
|
+
if (site === 'same-origin') return true
|
|
73
|
+
if (isLoopback(remote)) return true
|
|
74
|
+
|
|
56
75
|
return false
|
|
57
76
|
}
|
|
58
77
|
|
|
@@ -167,15 +186,35 @@ export function slotOf(timeMs) {
|
|
|
167
186
|
return Math.floor((d.getUTCHours() * 60 + d.getUTCMinutes()) / 30)
|
|
168
187
|
}
|
|
169
188
|
|
|
170
|
-
const
|
|
189
|
+
const modelSlotsSchema = zod.record(zod.string(), bucketSchema)
|
|
190
|
+
|
|
191
|
+
const costByModelViewSchema = zod.object({
|
|
192
|
+
models: zod.array(zod.object({
|
|
193
|
+
provider: zod.string(),
|
|
194
|
+
model: zod.string(),
|
|
195
|
+
slots: modelSlotsSchema,
|
|
196
|
+
}).strict()),
|
|
197
|
+
}).strict()
|
|
198
|
+
|
|
199
|
+
const costByModelStateSchema = zod.object({
|
|
200
|
+
route: zod.object({
|
|
201
|
+
provider: zod.string(),
|
|
202
|
+
model: zod.string(),
|
|
203
|
+
}).nullable(),
|
|
204
|
+
byKey: zod.record(zod.string(), modelSlotsSchema),
|
|
205
|
+
last: zod.object({
|
|
206
|
+
turn: zod.number().int().nonnegative(),
|
|
207
|
+
step: zod.number().int().nonnegative(),
|
|
208
|
+
key: zod.string(),
|
|
209
|
+
slot: zod.string(),
|
|
210
|
+
buckets: bucketSchema,
|
|
211
|
+
}).nullable(),
|
|
212
|
+
}).strict()
|
|
213
|
+
|
|
214
|
+
export const usageByModelProjection = {
|
|
171
215
|
key: 'costByModel',
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
provider: zod.string(),
|
|
175
|
-
model: zod.string(),
|
|
176
|
-
slots: zod.record(zod.string(), bucketSchema),
|
|
177
|
-
})),
|
|
178
|
-
}).strict(),
|
|
216
|
+
stateVersion: 2,
|
|
217
|
+
stateSchema: costByModelStateSchema,
|
|
179
218
|
init: () => ({ route: null, byKey: {}, last: null }),
|
|
180
219
|
apply: (state, event) => {
|
|
181
220
|
if (event.type === 'request/header') {
|
|
@@ -211,17 +250,19 @@ const usageByModelProjection = {
|
|
|
211
250
|
last: { turn: sample.turn, step: sample.step, key, slot, buckets },
|
|
212
251
|
}
|
|
213
252
|
},
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
253
|
+
wire: {
|
|
254
|
+
viewSchema: costByModelViewSchema,
|
|
255
|
+
view: (state) => ({
|
|
256
|
+
models: Object.keys(state.byKey).map((key) => {
|
|
257
|
+
const slash = key.indexOf('/')
|
|
258
|
+
return {
|
|
259
|
+
provider: key.slice(0, slash),
|
|
260
|
+
model: key.slice(slash + 1),
|
|
261
|
+
slots: state.byKey[key],
|
|
262
|
+
}
|
|
263
|
+
}),
|
|
222
264
|
}),
|
|
223
|
-
}
|
|
224
|
-
stateVersion: 2,
|
|
265
|
+
},
|
|
225
266
|
}
|
|
226
267
|
|
|
227
268
|
/** The agent's default route, read off settings descriptors when available. */
|
|
@@ -309,19 +350,37 @@ export function apply(ctx, config) {
|
|
|
309
350
|
}
|
|
310
351
|
}
|
|
311
352
|
|
|
312
|
-
// Live config from
|
|
313
|
-
ctx.
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
353
|
+
// Live config reaction from GUI / settings service
|
|
354
|
+
ctx.on('settings/updated', (ns, next) => {
|
|
355
|
+
if (ns === NS && next) {
|
|
356
|
+
current = { ...config, ...next }
|
|
357
|
+
clearResolveCache()
|
|
358
|
+
}
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
ctx.on('settings/document-updated', (ns) => {
|
|
362
|
+
if (!ns || ns === NS) {
|
|
318
363
|
clearResolveCache()
|
|
319
|
-
}
|
|
364
|
+
}
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
ctx.inject(['settings'], (sctx) => {
|
|
368
|
+
if (typeof sctx.settings?.describe === 'function') {
|
|
369
|
+
try {
|
|
370
|
+
const desc = sctx.settings.describe(NS)
|
|
371
|
+
if (desc?.value && typeof desc.value === 'object') {
|
|
372
|
+
current = { ...config, ...desc.value }
|
|
373
|
+
clearResolveCache()
|
|
374
|
+
}
|
|
375
|
+
} catch {
|
|
376
|
+
/* namespace might not have descriptor yet */
|
|
377
|
+
}
|
|
378
|
+
}
|
|
320
379
|
})
|
|
321
380
|
|
|
322
381
|
// Hook the session projection so the widget can show spend breakdown.
|
|
323
|
-
ctx.inject(['
|
|
324
|
-
pctx.
|
|
382
|
+
ctx.inject(['sessionProjections'], (pctx) => {
|
|
383
|
+
pctx.sessionProjections.register(usageByModelProjection)
|
|
325
384
|
})
|
|
326
385
|
|
|
327
386
|
// 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.10",
|
|
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",
|