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/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)
@@ -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
- return { backfill: { ...backfill, remaining: pricingRemaining() }, temporal }
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
- const DEEPSEEK_OFFICIAL_PROVIDERS = ['deepseek', 'deepseek-official']
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 && DEEPSEEK_OFFICIAL_PROVIDERS.includes(normalizeProvider(entry.providerId))) {
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 = DEEPSEEK_OFFICIAL_PROVIDERS.includes(providerId) && DEEPSEEK_OFFICIAL_PROVIDERS.includes(identityProvider) ? 'official' : mapped !== undefined && mapped !== null ? 'mapped' : 'other'
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.0",
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"