dsh-all-usage 1.2.0 → 1.2.1
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/CHANGELOG.md +16 -0
- package/README.en.md +6 -3
- package/README.md +6 -3
- package/lib/aggregation.js +8 -0
- package/lib/client.js +1 -1
- package/lib/plugin.js +1 -0
- package/lib/pricing-runtime.js +31 -1
- package/lib/pricing.js +28 -3
- package/package.json +3 -3
package/lib/plugin.js
CHANGED
|
@@ -235,6 +235,7 @@ function apply(ctx) {
|
|
|
235
235
|
Object.assign(host, { commitPendingStats, markStatsChanged, resetSyncState, beginSync, noteSyncError, syncSnapshot })
|
|
236
236
|
|
|
237
237
|
host.pricingSnapshot = (...args) => host.pricing.pricingSnapshot(...args)
|
|
238
|
+
host.pricingHealth = (...args) => host.pricing.pricingHealth(...args)
|
|
238
239
|
host.syncSnapshot = (...args) => syncSnapshot(...args)
|
|
239
240
|
host.aggregation = createAggregation(host)
|
|
240
241
|
host.ledger = createLedger(host)
|
package/lib/pricing-runtime.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { COST_SCHEMA_VERSION, builtinTemporalProfileFor, calculateCost, fetchHolidayCalendar, fetchModelsDevCatalog, normalizeCostSnapshot, normalizeModelId, normalizePricingState, officialProviderIds, serializeCostAggregate, serializePricingState, temporalPlanFor } from './pricing.js'
|
|
2
2
|
import { billingInstantOf, contextTimeIsFresh, contextTimeKey, validEventTime } from './usage-core.js'
|
|
3
|
+
import { unclassifiedFirstPartyRoute } from './pricing.js'
|
|
3
4
|
|
|
4
5
|
const LEDGER_VERSION = 3
|
|
5
6
|
// One price table row per usage identity; the cap keeps the panel payload bounded.
|
|
@@ -26,6 +27,29 @@ export function createPricingRuntime(host) {
|
|
|
26
27
|
const persistLedgerRecord = (...args) => host.ledger.persistLedgerRecord(...args)
|
|
27
28
|
const drainLedgerWrites = (...args) => host.ledger.drainLedgerWrites(...args)
|
|
28
29
|
const nextLedgerRevision = (...args) => host.ledger.nextLedgerRevision(...args)
|
|
30
|
+
// Diagnostic: a DeepSeek-looking route that resolved to something other than
|
|
31
|
+
// first-party would lose the peak/off-peak band and quietly under-count. Counting
|
|
32
|
+
// it puts the route into /status, so the next DSH route is noticed without a
|
|
33
|
+
// release instead of waiting for someone to compare a bill.
|
|
34
|
+
const unclassifiedRoutes = new Map()
|
|
35
|
+
const MAX_UNCLASSIFIED_ROUTES = 8
|
|
36
|
+
let lastReconcile = null
|
|
37
|
+
function noteUnclassifiedRoute(identity, resolved) {
|
|
38
|
+
const routeId = unclassifiedFirstPartyRoute(identity, resolved)
|
|
39
|
+
if (routeId === null) return
|
|
40
|
+
const entry = unclassifiedRoutes.get(routeId)
|
|
41
|
+
if (entry === undefined) {
|
|
42
|
+
unclassifiedRoutes.set(routeId, { provider: routeId, count: 1, lastAt: Date.now() })
|
|
43
|
+
while (unclassifiedRoutes.size > MAX_UNCLASSIFIED_ROUTES) unclassifiedRoutes.delete(unclassifiedRoutes.keys().next().value)
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
entry.count += 1
|
|
47
|
+
entry.lastAt = Date.now()
|
|
48
|
+
}
|
|
49
|
+
function pricingHealth() {
|
|
50
|
+
const routes = [...unclassifiedRoutes.values()].map((entry) => ({ ...entry })).sort((a, b) => b.count - a.count)
|
|
51
|
+
return { lastReconcile: lastReconcile === null ? null : { ...lastReconcile }, unclassifiedRoutes: routes }
|
|
52
|
+
}
|
|
29
53
|
let pricingMutation = Promise.resolve()
|
|
30
54
|
function serializePricingMutation(task) {
|
|
31
55
|
const next = pricingMutation.then(task, task)
|
|
@@ -267,6 +291,7 @@ export function createPricingRuntime(host) {
|
|
|
267
291
|
overrideCount: pricingStateSnapshot.overrides.length,
|
|
268
292
|
mappingCount: pricingStateSnapshot.mappings.length,
|
|
269
293
|
configured: pricingStateSnapshot.catalogEntries.length > 0 || pricingStateSnapshot.overrides.length > 0 || pricingStateSnapshot.mappings.length > 0,
|
|
294
|
+
health: pricingHealth(),
|
|
270
295
|
usedModels: usedPricingModelsCached(tierSchedules, temporalSchedules, detailed),
|
|
271
296
|
cost: serializeCostAggregate(state.totals.cost),
|
|
272
297
|
}
|
|
@@ -534,6 +559,7 @@ export function createPricingRuntime(host) {
|
|
|
534
559
|
async function reconcileTemporalPricing(options = {}) {
|
|
535
560
|
const stats = { considered: 0, reconciled: 0 }
|
|
536
561
|
await visitPricingRows(temporalCostVisitor(stats, options.force === true), true)
|
|
562
|
+
lastReconcile = { at: Date.now(), backfill: null, temporal: { ...stats } }
|
|
537
563
|
return stats
|
|
538
564
|
}
|
|
539
565
|
async function reconcileUsageCosts(options = {}) {
|
|
@@ -543,7 +569,9 @@ export function createPricingRuntime(host) {
|
|
|
543
569
|
if (options.backfill === true) visitors.push(backfillCostVisitor(backfill))
|
|
544
570
|
if (options.temporal !== false) visitors.push(temporalCostVisitor(temporal, options.force === true))
|
|
545
571
|
await visitPricingRows(visitors, true)
|
|
546
|
-
|
|
572
|
+
const result = { backfill: { ...backfill, remaining: pricingRemaining() }, temporal: { ...temporal } }
|
|
573
|
+
lastReconcile = { at: Date.now(), backfill: { ...result.backfill }, temporal: { ...result.temporal } }
|
|
574
|
+
return result
|
|
547
575
|
}
|
|
548
576
|
function updatePricingState(raw, backfill, repriceTemporal = false) {
|
|
549
577
|
return serializePricingMutation(() => updatePricingStateOnce(raw, backfill, repriceTemporal))
|
|
@@ -608,6 +636,8 @@ export function createPricingRuntime(host) {
|
|
|
608
636
|
backfillUnpricedCosts,
|
|
609
637
|
reconcileTemporalPricing,
|
|
610
638
|
reconcileUsageCosts,
|
|
639
|
+
noteUnclassifiedRoute,
|
|
640
|
+
pricingHealth,
|
|
611
641
|
updatePricingState,
|
|
612
642
|
schedulePricingSync,
|
|
613
643
|
loadHolidayCalendar
|
package/lib/pricing.js
CHANGED
|
@@ -953,7 +953,30 @@ function findMappedEntry(mapping, state) {
|
|
|
953
953
|
return null
|
|
954
954
|
}
|
|
955
955
|
|
|
956
|
-
|
|
956
|
+
// DSH *route* ids (the session header's `config.provider`) that bill DeepSeek's own
|
|
957
|
+
// account. These are route ids, NOT catalog vendor ids — the vendor list lives in
|
|
958
|
+
// OFFICIAL_PROVIDER_RULES above, and `deepseek` happens to appear in both namespaces.
|
|
959
|
+
// A route missing here loses the peak/off-peak band and is priced at the catalog
|
|
960
|
+
// (off-peak) rate all day, so unclassifiedFirstPartyRoute() reports any DeepSeek-looking
|
|
961
|
+
// route that resolved to `other` instead of letting it under-count quietly.
|
|
962
|
+
const DEEPSEEK_FIRST_PARTY_ROUTES = new Set(['deepseek', 'deepseek-official', 'deepseek-account'])
|
|
963
|
+
const DEEPSEEK_ROUTE_PREFIX = 'deepseek-'
|
|
964
|
+
function isDeepSeekFirstPartyRoute(providerId) {
|
|
965
|
+
return DEEPSEEK_FIRST_PARTY_ROUTES.has(normalizeProvider(providerId))
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* The route id of a usage identity that looks like DSH's own DeepSeek line but did not
|
|
969
|
+
* resolve as first-party, or null. Only DeepSeek models count: a `deepseek-*` route
|
|
970
|
+
* pointing at another vendor's model is not a pricing gap.
|
|
971
|
+
*/
|
|
972
|
+
function unclassifiedFirstPartyRoute(identity, resolved) {
|
|
973
|
+
const providerId = normalizeProvider(identity && identity.provider)
|
|
974
|
+
if (providerId === '' || !providerId.startsWith(DEEPSEEK_ROUTE_PREFIX)) return null
|
|
975
|
+
if (isDeepSeekFirstPartyRoute(providerId)) return null
|
|
976
|
+
const route = isRecord(resolved) ? resolved.temporalRoute : undefined
|
|
977
|
+
if (route === 'official' || route === 'mapped') return null
|
|
978
|
+
return identityModels(identity).some((model) => builtinTemporalProfileFor(model) !== null) ? providerId : null
|
|
979
|
+
}
|
|
957
980
|
|
|
958
981
|
function attachTemporalProfile(entry, mapped, identity) {
|
|
959
982
|
if (entry === null || entry === undefined || !isRecord(entry)) return null
|
|
@@ -967,7 +990,7 @@ function attachTemporalProfile(entry, mapped, identity) {
|
|
|
967
990
|
// resolution and must never be silently frozen into an override.
|
|
968
991
|
const explicit = profile !== null
|
|
969
992
|
let builtin = false
|
|
970
|
-
if (profile === null &&
|
|
993
|
+
if (profile === null && isDeepSeekFirstPartyRoute(entry.providerId)) {
|
|
971
994
|
profile = builtinTemporalProfileFor(entry.modelId)
|
|
972
995
|
builtin = profile !== null
|
|
973
996
|
}
|
|
@@ -978,7 +1001,7 @@ function attachTemporalProfile(entry, mapped, identity) {
|
|
|
978
1001
|
// is the first-party provider, or when the user explicitly mapped this route
|
|
979
1002
|
// to the DeepSeek official entry. OpenRouter and other resellers never get
|
|
980
1003
|
// the DeepSeek temporal band automatically.
|
|
981
|
-
const route =
|
|
1004
|
+
const route = isDeepSeekFirstPartyRoute(providerId) && isDeepSeekFirstPartyRoute(identityProvider) ? 'official' : mapped !== undefined && mapped !== null ? 'mapped' : 'other'
|
|
982
1005
|
return {
|
|
983
1006
|
route,
|
|
984
1007
|
explicit,
|
|
@@ -1449,6 +1472,8 @@ export {
|
|
|
1449
1472
|
isCostAccumulator,
|
|
1450
1473
|
modelPricingCandidates,
|
|
1451
1474
|
officialProviderIds,
|
|
1475
|
+
isDeepSeekFirstPartyRoute,
|
|
1476
|
+
unclassifiedFirstPartyRoute,
|
|
1452
1477
|
normalizeCostSnapshot,
|
|
1453
1478
|
normalizeModelId,
|
|
1454
1479
|
normalizePricingState,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-all-usage",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "DeepSeek Harness usage dashboard with model, provider, workspace, cache, balance, and CSV insights",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dsh": {
|
|
33
33
|
"compatibility": {
|
|
34
|
-
"runtime": ">=0.1.1-rc.1 <0.1.5-0 || >=0.1.5-rc.1 <0.1.6-0 || >=0.1.7-rc.2 <0.1.8-0 || >=0.2.0-rc.2 <0.2.1-0 || 0.2.1-alpha.2",
|
|
35
|
-
"verified": ["0.2.1-alpha.2", "0.2.0-rc.2", "0.1.7-rc.2", "0.1.5-rc.2", "0.1.5-rc.1", "0.1.1-rc.2", "0.1.1-rc.1"]
|
|
34
|
+
"runtime": ">=0.1.1-rc.1 <0.1.5-0 || >=0.1.5-rc.1 <0.1.6-0 || >=0.1.7-rc.2 <0.1.8-0 || >=0.2.0-rc.2 <0.2.1-0 || 0.2.1-alpha.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
|
|
35
|
+
"verified": ["0.2.1-rc.2", "0.2.1-rc.1", "0.2.1-alpha.2", "0.2.0-rc.2", "0.1.7-rc.2", "0.1.5-rc.2", "0.1.5-rc.1", "0.1.1-rc.2", "0.1.1-rc.1"]
|
|
36
36
|
},
|
|
37
37
|
"bundle": {
|
|
38
38
|
"patch": "./cordis.patch.yml"
|