@goodandready/dsh-subscriptions 0.7.49 → 0.7.50
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/analytics.js +22 -0
- package/lib/client.panels.js +6 -5
- package/lib/index.js +1 -0
- package/lib/native-tools.js +16 -6
- package/package.json +1 -1
package/lib/analytics.js
CHANGED
|
@@ -342,6 +342,27 @@ export function calculateQuotaForecast(accounts = [], entries = [], nowMs = Date
|
|
|
342
342
|
return forecast
|
|
343
343
|
}
|
|
344
344
|
|
|
345
|
+
/** #789: 24h request heatmap grouped by account ref and model. */
|
|
346
|
+
export function calculateAccountModelHeatmap(entries = [], nowMs = Date.now()) {
|
|
347
|
+
const ONE_HOUR = 3600000
|
|
348
|
+
const cutoff = nowMs - 24 * ONE_HOUR
|
|
349
|
+
const rows = new Map()
|
|
350
|
+
for (const entry of entries) {
|
|
351
|
+
if (!entry || !entry.ts || entry.ts < cutoff || entry.ts > nowMs) continue
|
|
352
|
+
const ref = String(entry.ref || entry.accountRef || entry.provider || 'unknown')
|
|
353
|
+
const model = String(entry.model || 'unknown')
|
|
354
|
+
const key = ref + '\u0000' + model
|
|
355
|
+
let row = rows.get(key)
|
|
356
|
+
if (!row) {
|
|
357
|
+
row = { ref, provider: entry.provider || '', model, hours: Array(24).fill(0) }
|
|
358
|
+
rows.set(key, row)
|
|
359
|
+
}
|
|
360
|
+
const bucket = 23 - Math.floor((nowMs - entry.ts) / ONE_HOUR)
|
|
361
|
+
if (bucket >= 0 && bucket < 24) row.hours[bucket]++
|
|
362
|
+
}
|
|
363
|
+
return [...rows.values()]
|
|
364
|
+
}
|
|
365
|
+
|
|
345
366
|
export function calculateAnalytics(entries = [], accounts = [], nowMs = Date.now()) {
|
|
346
367
|
let totalPrompt = 0, totalCompletion = 0, totalCacheRead = 0
|
|
347
368
|
for (const r of entries) {
|
|
@@ -360,6 +381,7 @@ export function calculateAnalytics(entries = [], accounts = [], nowMs = Date.now
|
|
|
360
381
|
cache: calculateCacheMetrics(entries),
|
|
361
382
|
performance: calculatePerformanceMetrics(entries),
|
|
362
383
|
hourly: calculateHourlyBuckets(entries, nowMs),
|
|
384
|
+
heatmap: calculateAccountModelHeatmap(entries, nowMs),
|
|
363
385
|
accounts: calculateAccountBreakdown(entries, accounts),
|
|
364
386
|
forecast: calculateQuotaForecast(accounts, entries, nowMs),
|
|
365
387
|
valueDelivered,
|
package/lib/client.panels.js
CHANGED
|
@@ -291,12 +291,13 @@ window.__ModuleLoader__.load({
|
|
|
291
291
|
h('label', { className: 'dsub-sub' }, h('input', { type: 'checkbox', checked: compact, onChange: (e) => setCompact(e.target.checked) }), ' ' + t('compact')),
|
|
292
292
|
),
|
|
293
293
|
h('div', { className: 'dsub-h' }, t('heatmap')),
|
|
294
|
-
h('div', { className: 'dsub-row', style: { gap: 2 } },
|
|
295
|
-
(
|
|
294
|
+
((analytics && analytics.heatmap) || []).filter((row) => row.provider === selected).slice(0, 12).map((row) => h('div', { key: row.ref + ':' + row.model, className: 'dsub-row', style: { gap: 2 } },
|
|
295
|
+
h('span', { className: 'dsub-dim', style: { width: '130px', minWidth: '130px', overflow: 'hidden', textOverflow: 'ellipsis' } }, row.ref + ' · ' + row.model),
|
|
296
|
+
(row.hours || []).map((count, i) => h('span', {
|
|
296
297
|
key: i,
|
|
297
|
-
title:
|
|
298
|
-
style: { display: 'inline-block', width: 8, height: 18, background: 'rgba(88,166,255,' + Math.min(1, (
|
|
299
|
-
}))),
|
|
298
|
+
title: count + ' req',
|
|
299
|
+
style: { display: 'inline-block', width: 8, height: 18, background: 'rgba(88,166,255,' + Math.min(1, (count || 0) / 10 + 0.1) + ')' },
|
|
300
|
+
})))),
|
|
300
301
|
h('div', { className: 'dsub-row' },
|
|
301
302
|
h('span', { className: 'dsub-h' }, t('modelEditor') + ' · ' + t('discovered') + ' ' + models.length + ' · ' + t('shown') + ' ' + shown.length),
|
|
302
303
|
h('button', { type: 'button', className: 'dsub-mini', onClick: () => setEditing(!editing) }, t('editModels')),
|
package/lib/index.js
CHANGED
|
@@ -481,6 +481,7 @@ export function apply(ctx, rawConfig) {
|
|
|
481
481
|
ctx.effect(() => registerSubscriptionTools(ctx, {
|
|
482
482
|
subscriptions,
|
|
483
483
|
images: () => ctx.subscriptionImages,
|
|
484
|
+
attachments: () => (typeof ctx.get === 'function' ? ctx.get('attachments') : null),
|
|
484
485
|
}, { enabled: live().nativeTools || process.env.DSH_SUBSCRIPTIONS_NATIVE_TOOLS === '1' }), 'dsh-subscriptions: native tools')
|
|
485
486
|
ctx.effect(() => () => clearSessionPins(), 'dsh-subscriptions: session pins cleanup')
|
|
486
487
|
|
package/lib/native-tools.js
CHANGED
|
@@ -44,18 +44,26 @@ function outputDir(exec) {
|
|
|
44
44
|
return path.join(cwd, 'generated', 'subscriptions')
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
async function saveImages(images, exec) {
|
|
47
|
+
async function saveImages(images, exec, attachments) {
|
|
48
48
|
const dir = outputDir(exec)
|
|
49
49
|
await mkdir(dir, { recursive: true })
|
|
50
50
|
const out = []
|
|
51
|
+
const stored = []
|
|
51
52
|
for (let i = 0; i < images.length; i += 1) {
|
|
52
53
|
const image = images[i]
|
|
53
54
|
if (!image || typeof image.b64_json !== 'string') continue
|
|
55
|
+
const data = Buffer.from(image.b64_json, 'base64')
|
|
54
56
|
const file = path.join(dir, `image-${Date.now()}-${i}.png`)
|
|
55
|
-
await writeFile(file,
|
|
57
|
+
await writeFile(file, data)
|
|
56
58
|
out.push(file)
|
|
59
|
+
if (attachments && typeof attachments.saveImages === 'function') {
|
|
60
|
+
try {
|
|
61
|
+
const refs = await attachments.saveImages([{ data, mediaType: 'image/png', name: `image-${i + 1}.png` }])
|
|
62
|
+
for (const ref of refs || []) stored.push(ref)
|
|
63
|
+
} catch { /* file output still works */ }
|
|
64
|
+
}
|
|
57
65
|
}
|
|
58
|
-
return out
|
|
66
|
+
return { files: out, attachments: stored }
|
|
59
67
|
}
|
|
60
68
|
|
|
61
69
|
export function registerSubscriptionTools(ctx, deps, options = {}) {
|
|
@@ -109,9 +117,11 @@ export function registerSubscriptionTools(ctx, deps, options = {}) {
|
|
|
109
117
|
referenceImages: references.refs,
|
|
110
118
|
signal: exec && exec.signal,
|
|
111
119
|
})
|
|
112
|
-
const
|
|
113
|
-
if (!files.length) throw new Error('image response contained no images')
|
|
114
|
-
|
|
120
|
+
const saved = await saveImages(rows, exec, deps.attachments && deps.attachments())
|
|
121
|
+
if (!saved.files.length) throw new Error('image response contained no images')
|
|
122
|
+
const lines = saved.files.map((file) => `Generated image: ${file}`)
|
|
123
|
+
for (const ref of saved.attachments) lines.push(`Attachment: ${ref}`)
|
|
124
|
+
return textResult(lines.join('\n'))
|
|
115
125
|
} catch (err) {
|
|
116
126
|
lastError = err
|
|
117
127
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-subscriptions",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.50",
|
|
4
4
|
"description": "Use ChatGPT Codex, Claude, Grok, Antigravity, Kimi, GLM, Cursor, Kiro, Copilot, Qwen, ERNIE, Spark, JetBrains, Perplexity, Replit, and Cody subscriptions as DeepSeek Harness LLM providers via OAuth.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|