@goodandready/dsh-subscriptions 0.7.48 → 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/account-features.js +4 -0
- package/lib/adapter.js +9 -5
- package/lib/analytics.js +22 -0
- package/lib/client.js +12 -0
- package/lib/client.panels.js +36 -8
- package/lib/index.js +1 -0
- package/lib/native-tools.js +16 -6
- package/package.json +1 -1
package/lib/account-features.js
CHANGED
|
@@ -69,6 +69,10 @@ export function createUsageLoader({ load } = {}) {
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
export function supportsFastTier(model) {
|
|
73
|
+
return /(?:^|[-_.])(gpt-5|gpt-6|codex|o3|o4)(?:[-_.]|$)/i.test(String(model || ''))
|
|
74
|
+
}
|
|
75
|
+
|
|
72
76
|
/** #802: explicit child choice > subagent default > session choice > global. */
|
|
73
77
|
export function resolveSpeed({ childSpeed, sessionSpeed, subagentSpeed, modelSupportsFast = true } = {}) {
|
|
74
78
|
const pick = [childSpeed, subagentSpeed, sessionSpeed].find((value) => value === 'standard' || value === 'fast')
|
package/lib/adapter.js
CHANGED
|
@@ -6,7 +6,7 @@ import { LlmAdapter, LlmError, attributionHeaders } from '@deepseek-ai/dsh-llm'
|
|
|
6
6
|
import { displayName } from './refs.js'
|
|
7
7
|
import { applyModelSettings, bestAccountPlan } from './model-settings.js'
|
|
8
8
|
import { getSpeedMode } from './speed-mode.js'
|
|
9
|
-
import { resolveSpeed } from './account-features.js'
|
|
9
|
+
import { resolveSpeed, supportsFastTier } from './account-features.js'
|
|
10
10
|
import { getVendor } from './vendors/index.js'
|
|
11
11
|
import { modelCatalog } from './messages.js'
|
|
12
12
|
import { streamWithRotation } from './stream-rotate.js'
|
|
@@ -204,13 +204,17 @@ export class SubscriptionAdapter extends LlmAdapter {
|
|
|
204
204
|
}
|
|
205
205
|
const poolEnabled = typeof deps.poolEnabled === 'function' ? deps.poolEnabled() !== false : true
|
|
206
206
|
const defaultRef = typeof deps.defaultAccountRef === 'function' ? deps.defaultAccountRef(provider) : null
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
...(provider === 'codex' && resolveSpeed({
|
|
207
|
+
const effectiveSpeed = provider === 'codex'
|
|
208
|
+
? resolveSpeed({
|
|
210
209
|
childSpeed: options.speed,
|
|
211
210
|
sessionSpeed: getSpeedMode(sessionId),
|
|
212
211
|
subagentSpeed: (options.subagent || options.parentSessionId) && typeof deps.subagentSpeed === 'function' ? deps.subagentSpeed() : undefined,
|
|
213
|
-
|
|
212
|
+
modelSupportsFast: supportsFastTier(options.model),
|
|
213
|
+
})
|
|
214
|
+
: 'standard'
|
|
215
|
+
const streamOptions = {
|
|
216
|
+
...hydratedOptions,
|
|
217
|
+
...(provider === 'codex' && effectiveSpeed === 'fast' ? { service_tier: 'priority' } : {}),
|
|
214
218
|
...(effectiveReasoning.effort ? { reasoningEffort: effectiveReasoning.effort } : {}),
|
|
215
219
|
...(effectiveReasoning.budgetTokens ? { thinkingBudget: effectiveReasoning.budgetTokens } : {}),
|
|
216
220
|
// #830: kill switch. With no default account, use a sentinel that
|
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.js
CHANGED
|
@@ -1089,6 +1089,7 @@ window.__ModuleLoader__.load({
|
|
|
1089
1089
|
const ProviderPacksPanel = lazyPanel('ProviderPacksPanel')
|
|
1090
1090
|
const SourcesPanel = lazyPanel('SourcesPanel')
|
|
1091
1091
|
const AccountsDashboardPanel = lazyPanel('AccountsDashboardPanel')
|
|
1092
|
+
const SpeedToggle = lazyPanel('SpeedToggle')
|
|
1092
1093
|
|
|
1093
1094
|
// #839 model-list helpers
|
|
1094
1095
|
function modelListSearchText(model) {
|
|
@@ -3900,6 +3901,17 @@ h('div', { className: 'dsub-row' },
|
|
|
3900
3901
|
}, 'dsh-subscriptions: locales')
|
|
3901
3902
|
setT(ctx.locale.bind(NS))
|
|
3902
3903
|
|
|
3904
|
+
ctx.effect(() => {
|
|
3905
|
+
if (!ctx.slots || typeof ctx.slots.inject !== 'function') return () => {}
|
|
3906
|
+
return ctx.slots.inject('conversation.input.right', () => ctx.slots.register({
|
|
3907
|
+
name: 'conversation.input.right',
|
|
3908
|
+
id: 'subscriptions-speed',
|
|
3909
|
+
order: 50,
|
|
3910
|
+
locale: NS,
|
|
3911
|
+
inject: () => ({}),
|
|
3912
|
+
}, SpeedToggle))
|
|
3913
|
+
}, 'dsh-subscriptions: composer speed toggle')
|
|
3914
|
+
|
|
3903
3915
|
ctx.effect(() => {
|
|
3904
3916
|
const cleanups = []
|
|
3905
3917
|
const registerLegacySeats = () => {
|
package/lib/client.panels.js
CHANGED
|
@@ -280,8 +280,8 @@ window.__ModuleLoader__.load({
|
|
|
280
280
|
h('div', { className: 'dsub-row' },
|
|
281
281
|
h('span', { className: 'dsub-h' }, '#' + row.index + ' ' + (row.label || row.email || row.ref)),
|
|
282
282
|
h('span', { className: 'dsub-pillTag' }, row.quarantineUntil > Date.now() ? t('statusQuarantined') : (row.cooldownUntil > Date.now() ? t('statusCooling') : t('statusHealthy')))),
|
|
283
|
-
h('div', { className: 'dsub-sub' }, t('quotaUsed') + ': ' + (row.usagePercent == null ? '—' : row.usagePercent + '%') + ' · ' + t('requests') + ' ' + (row.requests || 0) + ' · ' + t('errors') + ' ' + (row.errors || 0)),
|
|
284
|
-
h('div', { className: 'dsub-row' },
|
|
283
|
+
compact ? null : h('div', { className: 'dsub-sub' }, t('quotaUsed') + ': ' + (row.usagePercent == null ? '—' : row.usagePercent + '%') + ' · ' + t('requests') + ' ' + (row.requests || 0) + ' · ' + t('errors') + ' ' + (row.errors || 0)),
|
|
284
|
+
compact ? null : h('div', { className: 'dsub-row' },
|
|
285
285
|
h('button', { type: 'button', className: 'dsub-mini', onClick: () => loadUsage(row) }, u && u.loading ? '…' : t('loadUsage')),
|
|
286
286
|
u && u.error ? h('span', { className: 'dsub-bad' }, u.error) : null,
|
|
287
287
|
u && u.value ? h('span', { className: 'dsub-dim' }, JSON.stringify(u.value).slice(0, 180)) : null),
|
|
@@ -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')),
|
|
@@ -337,6 +338,33 @@ window.__ModuleLoader__.load({
|
|
|
337
338
|
)
|
|
338
339
|
}
|
|
339
340
|
|
|
340
|
-
|
|
341
|
+
// #802: composer Speed toggle, scoped to the current session.
|
|
342
|
+
function SpeedToggle(props) {
|
|
343
|
+
const sessionId = (props && (props.sessionId || (props.session && props.session.id))) || 'default'
|
|
344
|
+
const [mode, setMode] = React.useState('standard')
|
|
345
|
+
React.useEffect(() => {
|
|
346
|
+
fetch('/dsh-subscriptions/speed?sessionId=' + encodeURIComponent(sessionId), { cache: 'no-store' })
|
|
347
|
+
.then((res) => res.json())
|
|
348
|
+
.then((out) => setMode((out && out.mode) || 'standard'))
|
|
349
|
+
.catch(() => {})
|
|
350
|
+
}, [sessionId])
|
|
351
|
+
const toggle = () => {
|
|
352
|
+
const next = mode === 'fast' ? 'standard' : 'fast'
|
|
353
|
+
setMode(next)
|
|
354
|
+
fetch('/dsh-subscriptions/speed', {
|
|
355
|
+
method: 'POST',
|
|
356
|
+
headers: { 'content-type': 'application/json' },
|
|
357
|
+
body: JSON.stringify({ sessionId, mode: next }),
|
|
358
|
+
}).then((res) => res.json()).then((out) => { if (out && out.mode) setMode(out.mode) }).catch(() => {})
|
|
359
|
+
}
|
|
360
|
+
return h('button', {
|
|
361
|
+
type: 'button',
|
|
362
|
+
className: 'dsub-mini',
|
|
363
|
+
title: 'Codex Speed',
|
|
364
|
+
onClick: toggle,
|
|
365
|
+
}, mode === 'fast' ? '⚡ FAST' : 'Speed')
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return { WebhookTestButton, VaultSnapshotsPanel, ProviderPacksPanel, SourcesPanel, AccountsDashboardPanel, SpeedToggle }
|
|
341
369
|
}
|
|
342
370
|
})
|
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",
|