@ljwei-stak/dsh-model-router 0.23.1 → 0.25.0

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.
@@ -17,9 +17,13 @@ import { mkdir, open, readFile, rename, stat, unlink, writeFile } from 'node:fs/
17
17
  import { homedir } from 'node:os'
18
18
  import { dirname, isAbsolute, join, resolve } from 'node:path'
19
19
  import { archiveSpending, sanitizeArchivedSpending } from './run-ledger.mjs'
20
+ import { sanitizeBudgetReservations } from './budget-reservations.mjs'
21
+ import { archiveSubscriptionUsage, invalidArchivedSubscriptionUsage, invalidSubscriptionAttempts,
22
+ sanitizeArchivedSubscriptionUsage } from './subscription-usage.mjs'
20
23
 
21
24
  export const MAX_RUNS = 200
22
- const STATE_VERSION = 1
25
+ // Older Hosts must refuse v3 instead of dropping the separate usage archive.
26
+ const STATE_VERSION = 3
23
27
 
24
28
  /** Same DSH home rule as the Host: DSH_HOME (with ~ expansion) or ~/.dsh. */
25
29
  export function resolveStateHome(env = process.env, home = homedir(), cwd = process.cwd()) {
@@ -41,7 +45,10 @@ const LOCK_STALE_MS = 15_000
41
45
  const LOCK_TIMEOUT_MS = 10_000
42
46
 
43
47
  function emptyState() {
44
- return { version: STATE_VERSION, historyIncomplete: false, onboarding: { completedAt: null }, health: null, quota: {}, authFailures: {}, runs: [], dynamicData: { liveBench: null, pricing: null, status: {}, revision: '' }, archivedSpending: sanitizeArchivedSpending(), notices: [], terminalSessions: [] }
48
+ return { version: STATE_VERSION, historyIncomplete: false, subscriptionUsageIncomplete: false,
49
+ onboarding: { completedAt: null }, health: null, quota: {}, authFailures: {}, runs: [], budgetReservations: {},
50
+ dynamicData: { liveBench: null, pricing: null, status: {}, revision: '' }, archivedSpending: sanitizeArchivedSpending(),
51
+ archivedSubscriptionUsage: sanitizeArchivedSubscriptionUsage(), notices: [], terminalSessions: [] }
45
52
  }
46
53
 
47
54
  function sanitizeNotices(value) {
@@ -51,9 +58,17 @@ function sanitizeNotices(value) {
51
58
  }
52
59
 
53
60
  function retainRuns(state, maxRuns) {
54
- const removed = Math.max(0, state.runs.length - maxRuns)
55
- state.archivedSpending = archiveSpending(state.runs.slice(0, removed), state.archivedSpending)
56
- state.runs = state.runs.slice(removed)
61
+ // A partial run must stay addressable until its hold is reconciled. Archiving
62
+ // it and later upserting a checkpoint would count its receipts twice.
63
+ const protectedIds = new Set(Object.values(state.budgetReservations ?? {}).map(entry => entry.runId))
64
+ let remove = Math.max(0, state.runs.length - Math.max(maxRuns, protectedIds.size))
65
+ const evicted = []
66
+ state.runs = state.runs.filter(run => {
67
+ if (remove > 0 && !protectedIds.has(run.id)) { remove -= 1; evicted.push(run); return false }
68
+ return true
69
+ })
70
+ state.archivedSpending = archiveSpending(evicted, state.archivedSpending)
71
+ state.archivedSubscriptionUsage = archiveSubscriptionUsage(evicted, state.archivedSubscriptionUsage)
57
72
  return state
58
73
  }
59
74
 
@@ -94,28 +109,38 @@ function invalidRunSpending(run) {
94
109
  }
95
110
 
96
111
  function sanitize(value, maxRuns) {
97
- if (!value || typeof value !== 'object' || Array.isArray(value) || value.version !== STATE_VERSION
112
+ if (!value || typeof value !== 'object' || Array.isArray(value) || ![1, 2, STATE_VERSION].includes(value.version)
98
113
  || !Array.isArray(value.runs)) {
99
114
  const error = new Error('模型路由状态版本或结构不受支持;原文件未修改,请先恢复或升级兼容版本。')
100
115
  error.code = 'ROUTER_STATE_UNSUPPORTED'
101
116
  throw error
102
117
  }
103
118
  const runs = value.runs.filter(run => run && typeof run.id === 'string')
119
+ const held = sanitizeBudgetReservations(value.budgetReservations)
104
120
  return retainRuns({
105
121
  version: STATE_VERSION,
106
122
  // Unlike bounded notices, an incomplete-history marker cannot silently
107
123
  // expire. Restoring a verified state file is required to recover budgets.
108
124
  historyIncomplete: value.historyIncomplete === true || runs.length !== value.runs.length
109
- || invalidArchivedSpending(value.archivedSpending) || runs.some(invalidRunSpending)
125
+ || invalidArchivedSpending(value.archivedSpending) || runs.some(invalidRunSpending) || held.invalid
126
+ || Object.values(held.reservations).some(entry => !runs.some(run => run.id === entry.runId))
110
127
  || sanitizeNotices(value.notices).some(notice => notice.kind === 'state-corrupt'),
128
+ subscriptionUsageIncomplete: value.subscriptionUsageIncomplete === true
129
+ || invalidArchivedSubscriptionUsage(value.archivedSubscriptionUsage)
130
+ || runs.some(run => invalidArchivedSubscriptionUsage(run.priorAttemptSubscriptionUsage)
131
+ || [...(Array.isArray(run.packages) ? run.packages : []), ...(Array.isArray(run.reviews) ? run.reviews : [])]
132
+ .some(item => invalidSubscriptionAttempts(item?.subscriptionAttempts))),
111
133
  onboarding: { completedAt: Number.isFinite(value.onboarding?.completedAt) ? value.onboarding.completedAt : null },
112
134
  health: value.health && Array.isArray(value.health.tools) ? value.health : null,
113
135
  quota: value.quota && typeof value.quota === 'object' && !Array.isArray(value.quota) ? value.quota : {},
114
136
  authFailures: Object.fromEntries(Object.entries(value.authFailures && typeof value.authFailures === 'object' && !Array.isArray(value.authFailures) ? value.authFailures : {})
115
137
  .filter(([, entry]) => Number.isFinite(entry?.at))),
116
138
  runs,
139
+ budgetReservations: held.reservations,
117
140
  dynamicData: value.dynamicData && typeof value.dynamicData === 'object' && !Array.isArray(value.dynamicData) ? value.dynamicData : { liveBench: null, pricing: null, status: {}, revision: '' },
118
141
  archivedSpending: sanitizeArchivedSpending(value.archivedSpending),
142
+ archivedSubscriptionUsage: sanitizeArchivedSubscriptionUsage(value.archivedSubscriptionUsage,
143
+ { legacySpending: value.archivedSpending }),
119
144
  notices: sanitizeNotices(value.notices),
120
145
  terminalSessions: sanitizeTerminalSessions(value.terminalSessions),
121
146
  }, maxRuns)
@@ -172,7 +197,10 @@ async function acquireLock(lockFile, { staleMs = LOCK_STALE_MS, timeoutMs = LOCK
172
197
  if (await readFile(lockFile, 'utf8').catch(() => null) === owner) await unlink(lockFile).catch(() => {})
173
198
  }
174
199
  } catch (error) {
175
- if (error?.code !== 'EEXIST') throw error
200
+ // Windows can report a sharing/delete-pending race as EPERM while
201
+ // another owner closes and removes its lock. Retry within the same
202
+ // bounded wait; never treat this as permission to remove a live lock.
203
+ if (error?.code !== 'EEXIST' && !(process.platform === 'win32' && error?.code === 'EPERM')) throw error
176
204
  const info = await stat(lockFile).catch(() => null)
177
205
  const owner = await readFile(lockFile, 'utf8').catch(() => null)
178
206
  if (info && owner !== null && now() - info.mtimeMs > staleMs && ownerIsDead(ownerPid(owner))) {
@@ -6,6 +6,7 @@
6
6
 
7
7
  import { reasoningExecutionFields, unsupportedCliReasoning } from './reasoning-execution.mjs'
8
8
  import { packageSelectionFields } from './package-assignments.mjs'
9
+ import { archiveSubscriptionUsage, sanitizeArchivedSubscriptionUsage, sanitizeSubscriptionAttempts } from './subscription-usage.mjs'
9
10
 
10
11
  export const MAX_STORED_ANSWER = 4_000
11
12
  export const MAX_STORED_TASK = 20_000
@@ -13,6 +14,7 @@ export const MAX_STORED_TASK = 20_000
13
14
  export const MAX_QUALITY_BIAS = 0.04
14
15
 
15
16
  const finite = value => typeof value === 'number' && Number.isFinite(value)
17
+ const receiptTimestamp = value => finite(value) && value >= 0 && Number.isFinite(new Date(value).getTime())
16
18
  const nonEmptyText = value => (typeof value === 'string' && value.length > 0 ? value : null)
17
19
  const routeKey = (provider, model) => `${String(provider ?? '')}\u0000${String(model ?? '')}`
18
20
 
@@ -127,7 +129,7 @@ export function actualCost(result, pricing) {
127
129
  export function billingOf(result, { loginBilling = null, apiKeyPresent = false } = {}) {
128
130
  if (!result || result.blocked) return null
129
131
  // The subscription-first executor states the channel it billed explicitly.
130
- if (result.billing === 'api' || result.billing === 'subscription') return result.billing
132
+ if (result.billing === 'api' || result.billing === 'subscription' || result.billing === 'unknown') return result.billing
131
133
  if (result.channel !== 'official-cli') return 'api'
132
134
  if (result.credentialSource === 'configured-api-key' || result.credentialSource === 'process-environment') return 'api'
133
135
  if (!result.credentialSource && apiKeyPresent) return 'api'
@@ -136,6 +138,7 @@ export function billingOf(result, { loginBilling = null, apiKeyPresent = false }
136
138
 
137
139
  /** Cost fields for storage: subscription runs keep the figure as `referenceCostUsd` only. */
138
140
  export function billedCost(result, pricing, billing) {
141
+ if (billing === 'unknown') return { costUsd: null, costSource: 'billing-unknown', billing, referenceCostUsd: null }
139
142
  const cost = actualCost(result, pricing)
140
143
  if (billing !== 'subscription') return { ...cost, billing: billing ?? 'api', referenceCostUsd: null }
141
144
  return { costUsd: null, costSource: cost.costSource, billing, referenceCostUsd: cost.costUsd }
@@ -372,6 +375,7 @@ function storedPackage(planned, result, pricing, finishedAt, billingFor = billin
372
375
  ...(result?.billingMode ? { billingMode: String(result.billingMode) } : {}),
373
376
  ...(result?.billingSwitch ? { billingSwitch: { ...result.billingSwitch } } : {}),
374
377
  ...(result?.subscriptionRoute ? { subscriptionRoute: { provider: String(result.subscriptionRoute.provider ?? ''), model: String(result.subscriptionRoute.model ?? '') } } : {}),
378
+ ...(result?.subscriptionAttempts !== undefined ? { subscriptionAttempts: sanitizeSubscriptionAttempts(result.subscriptionAttempts) } : {}),
375
379
  error: result?.ok ? null : (result?.error ?? null),
376
380
  ...(!result?.ok && result?.diagnostic ? { diagnostic: String(result.diagnostic).slice(0, 2_000) } : {}),
377
381
  ...(Number.isInteger(result?.exitCode) ? { exitCode: result.exitCode } : {}),
@@ -382,7 +386,7 @@ function storedPackage(planned, result, pricing, finishedAt, billingFor = billin
382
386
  // silently rewrite historical spend calculated from usage.
383
387
  pricingSnapshot: pricing ? { ...pricing } : null,
384
388
  ...cost,
385
- finishedAt,
389
+ finishedAt: receiptTimestamp(result?.finishedAt) ? result.finishedAt : finishedAt,
386
390
  review: planned.review ?? null,
387
391
  rating: planned.rating ?? null,
388
392
  }
@@ -442,6 +446,8 @@ export function mergeRerun(run, execution, { rerunIds = [], pricingFor = () => n
442
446
  // Retrying replaces the visible result, but earlier attempts still incurred
443
447
  // charges (or unknown usage). Keep their calendar totals before replacement.
444
448
  const replaced = run.packages.filter(stored => retried.has(stored.id) && results.some(item => item.id === stored.id))
449
+ run.priorAttemptSubscriptionUsage = archiveSubscriptionUsage([{ createdAt: run.createdAt, packages: replaced }],
450
+ sanitizeArchivedSubscriptionUsage(run.priorAttemptSubscriptionUsage, { legacySpending: run.priorAttemptSpending }))
445
451
  run.priorAttemptSpending = archiveSpending([{ createdAt: run.createdAt, packages: replaced }], run.priorAttemptSpending)
446
452
  run.packages = run.packages.map(stored => {
447
453
  const result = results.find(item => item.id === stored.id)
@@ -459,6 +465,7 @@ export function storedResults(run) {
459
465
  id: item.id, name: item.name, ok: item.ok, provider: item.provider, model: item.model,
460
466
  channel: item.channel, answer: item.answer, error: item.error, fallback: item.fallback,
461
467
  blocked: item.blocked, finishedAt: item.finishedAt,
468
+ ...(item.subscriptionAttempts !== undefined ? { subscriptionAttempts: sanitizeSubscriptionAttempts(item.subscriptionAttempts) } : {}),
462
469
  ...packageSelectionFields(item),
463
470
  ...reasoningExecutionFields(item),
464
471
  ...(finite(item.durationMs) && item.durationMs >= 0 ? { durationMs: item.durationMs } : {}),
@@ -482,12 +489,17 @@ export function teamExecutionResults(plan, execution) {
482
489
  id: result.id,
483
490
  ok: result.status === 'succeeded',
484
491
  cancelled: result.status === 'cancelled',
492
+ ...(result.notStarted === true || result.status === 'unsupported' ? { notStarted: true } : {}),
493
+ ...(result.blocked === true ? { blocked: true } : {}),
494
+ ...(result.billing === 'api' || result.billing === 'subscription' || result.billing === 'unknown' ? { billing: result.billing } : {}),
495
+ ...(typeof result.credentialSource === 'string' ? { credentialSource: result.credentialSource } : {}),
485
496
  provider: result.provider,
486
497
  model: result.recommendedModel,
487
498
  ...unsupportedCliReasoning({ reasoningEffort: result.recommendedReasoningEffort }),
488
499
  ...reasoningExecutionFields(result),
489
500
  ...(finite(result.durationMs) && result.durationMs >= 0 ? { durationMs: result.durationMs } : {}),
490
501
  ...(finite(result.inputChars) && result.inputChars >= 0 ? { inputChars: result.inputChars } : {}),
502
+ ...(receiptTimestamp(result.finishedAt) ? { finishedAt: result.finishedAt } : {}),
491
503
  requestedCliModel: result.requestedCliModel ?? null,
492
504
  actualModel: result.actualModel ?? null,
493
505
  ...(result.reportedModel !== undefined ? { reportedModel: result.reportedModel } : {}),
@@ -495,6 +507,7 @@ export function teamExecutionResults(plan, execution) {
495
507
  ...(typeof result.source === 'string' ? { source: result.source } : {}),
496
508
  ...(typeof result.sandboxEnforcement === 'string' ? { sandboxEnforcement: result.sandboxEnforcement } : {}),
497
509
  ...(result.usageIncomplete === true ? { usageIncomplete: true } : {}),
510
+ ...(result.subscriptionAttempts !== undefined ? { subscriptionAttempts: sanitizeSubscriptionAttempts(result.subscriptionAttempts) } : {}),
498
511
  ...(typeof result.executionEnvironment === 'string' ? { executionEnvironment: result.executionEnvironment } : {}),
499
512
  channel: 'official-cli',
500
513
  toolId: result.toolId ?? null,
@@ -541,6 +554,10 @@ export function buildToolRunRecord({ toolId, toolLabel, provider, model, cliMode
541
554
  status: result?.status ?? 'pending',
542
555
  packages: [{
543
556
  id: packageId, ok: succeeded, cancelled: result?.status === 'cancelled',
557
+ ...(result?.notStarted === true || result?.status === 'unsupported' ? { notStarted: true } : {}),
558
+ ...(result?.blocked === true ? { blocked: true } : {}),
559
+ ...(result?.billing === 'api' || result?.billing === 'subscription' || result?.billing === 'unknown' ? { billing: result.billing } : {}),
560
+ ...(typeof result?.credentialSource === 'string' ? { credentialSource: result.credentialSource } : {}),
544
561
  provider: plan.directRoute.provider, model: plan.directRoute.model,
545
562
  channel: 'official-cli', toolId,
546
563
  requestedCliModel: Object.hasOwn(result ?? {}, 'requestedModel') ? result.requestedModel : cliModel,
@@ -550,6 +567,8 @@ export function buildToolRunRecord({ toolId, toolLabel, provider, model, cliMode
550
567
  ...(typeof result?.source === 'string' ? { source: result.source } : {}),
551
568
  ...(typeof result?.sandboxEnforcement === 'string' ? { sandboxEnforcement: result.sandboxEnforcement } : {}),
552
569
  ...(result?.usageIncomplete === true ? { usageIncomplete: true } : {}),
570
+ ...(result?.subscriptionAttempts !== undefined ? { subscriptionAttempts: sanitizeSubscriptionAttempts(result.subscriptionAttempts) } : {}),
571
+ ...(receiptTimestamp(result?.finishedAt) ? { finishedAt: result.finishedAt } : {}),
553
572
  answer: String(result?.finalText ?? ''),
554
573
  error: succeeded ? null : String(result?.error ?? result?.reason ?? result?.status ?? '官方 CLI 未成功完成。'),
555
574
  ...(!succeeded && typeof result?.diagnostic === 'string' && result.diagnostic.trim()
@@ -0,0 +1,210 @@
1
+ /** Subscription quantities are evidence of use, never API dollars or remaining quota. */
2
+ const record = value => value !== null && typeof value === 'object' && !Array.isArray(value)
3
+ const counter = value => Number.isSafeInteger(value) && value >= 0
4
+ const timestamp = value => typeof value === 'number' && Number.isFinite(value) && value >= 0 && Number.isFinite(new Date(value).getTime())
5
+ const clean = value => typeof value === 'string' && value.length <= 512 && !/[\u0000-\u001f\u007f]/u.test(value) ? value : null
6
+ const TOKENS = ['inputTokens', 'outputTokens', 'cacheReadTokens', 'cacheWriteTokens']
7
+ const COUNTS = ['calls', 'knownUsageCalls', 'unknownUsageCalls']
8
+ const pad = value => String(value).padStart(2, '0')
9
+ const dayKey = at => { const d = new Date(at); return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}` }
10
+ const monthKey = at => { const d = new Date(at); return `${d.getFullYear()}-${pad(d.getMonth() + 1)}` }
11
+ const DAYS = /^\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])$/u
12
+ const MONTHS = /^\d{4}-(?:0[1-9]|1[0-2])$/u
13
+ export const MAX_SUBSCRIPTION_USAGE_ROUTES = 256
14
+
15
+ export const emptySubscriptionUsage = () => ({ calls: 0, knownUsageCalls: 0, unknownUsageCalls: 0,
16
+ inputTokens: null, outputTokens: null, cacheReadTokens: null, cacheWriteTokens: null })
17
+ const period = value => ({ ...Object.fromEntries(COUNTS.map(field => [field, counter(value?.[field]) ? value[field] : 0])),
18
+ ...Object.fromEntries(TOKENS.map(field => [field, counter(value?.[field]) ? value[field] : null])) })
19
+ const identity = value => ({ provider: clean(value?.provider) || '未知供应商', toolId: clean(value?.toolId), model: clean(value?.model) })
20
+ const keyOf = value => JSON.stringify([value.provider, value.toolId, value.model])
21
+ const emptyBucket = () => ({ total: emptySubscriptionUsage(), byRoute: {} })
22
+ const overflowRoute = { provider: '其他(已合并)', toolId: null, model: null }
23
+
24
+ function addPeriod(target, source) {
25
+ for (const field of COUNTS) target[field] += source[field]
26
+ for (const field of TOKENS) {
27
+ if (source[field] === null) continue
28
+ const total = (target[field] ?? 0) + source[field]
29
+ // A quantity that cannot be represented exactly is not a reported zero.
30
+ target[field] = counter(total) ? total : null
31
+ }
32
+ return target
33
+ }
34
+
35
+ function addRoute(bucket, route, value) {
36
+ let id = keyOf(route)
37
+ if (!Object.hasOwn(bucket.byRoute, id) && Object.keys(bucket.byRoute).length >= MAX_SUBSCRIPTION_USAGE_ROUTES - 1) {
38
+ route = overflowRoute; id = keyOf(route)
39
+ }
40
+ const row = bucket.byRoute[id] ??= { ...route, usage: emptySubscriptionUsage() }
41
+ addPeriod(row.usage, value)
42
+ }
43
+
44
+ function addBucket(target, source) {
45
+ addPeriod(target.total, source.total)
46
+ for (const row of Object.values(source.byRoute)) addRoute(target, identity(row), row.usage)
47
+ }
48
+
49
+ /** Legacy calendar records kept counts, but discarded every token quantity. */
50
+ function legacyArchive(spending) {
51
+ const result = { days: {}, months: {} }
52
+ for (const [kind, pattern] of [['days', DAYS], ['months', MONTHS]]) {
53
+ for (const [key, value] of Object.entries(record(spending?.[kind]) ? spending[kind] : {})) {
54
+ if (!pattern.test(key) || !counter(value?.subscriptionRuns) || value.subscriptionRuns === 0) continue
55
+ const usage = { ...emptySubscriptionUsage(), calls: value.subscriptionRuns, unknownUsageCalls: value.subscriptionRuns }
56
+ const bucket = result[kind][key] = { total: usage, byRoute: {} }
57
+ addRoute(bucket, { provider: '历史订阅(用量未保存)', toolId: null, model: null }, usage)
58
+ }
59
+ }
60
+ return result
61
+ }
62
+
63
+ /** Bounded quantities and route identifiers only; no answers, prompts, prices or credentials. */
64
+ export function sanitizeArchivedSubscriptionUsage(value, { legacySpending } = {}) {
65
+ if (value === undefined || value === null) value = legacyArchive(legacySpending)
66
+ const result = { days: {}, months: {} }
67
+ for (const [kind, pattern, limit] of [['days', DAYS, 90], ['months', MONTHS, 24]]) {
68
+ const entries = Object.entries(record(value?.[kind]) ? value[kind] : {}).filter(([key, bucket]) => pattern.test(key) && record(bucket))
69
+ .sort(([a], [b]) => a.localeCompare(b)).slice(-limit)
70
+ for (const [key, bucket] of entries) {
71
+ const next = result[kind][key] = { total: period(bucket.total), byRoute: {} }
72
+ for (const row of Object.values(record(bucket.byRoute) ? bucket.byRoute : {})) {
73
+ if (!record(row)) continue
74
+ addRoute(next, identity(row), period(row.usage))
75
+ }
76
+ }
77
+ }
78
+ return result
79
+ }
80
+
81
+ export function invalidArchivedSubscriptionUsage(value) {
82
+ if (value === undefined) return false
83
+ if (!record(value)) return true
84
+ const invalidPeriod = value => !record(value)
85
+ || COUNTS.some(field => !counter(value[field]))
86
+ || TOKENS.some(field => value[field] !== null && !counter(value[field]))
87
+ || value.knownUsageCalls + value.unknownUsageCalls !== value.calls
88
+ for (const [kind, pattern] of [['days', DAYS], ['months', MONTHS]]) {
89
+ if (!record(value[kind])) return true
90
+ for (const [key, bucket] of Object.entries(value[kind])) {
91
+ if (!pattern.test(key) || !record(bucket) || invalidPeriod(bucket.total) || !record(bucket.byRoute)) return true
92
+ for (const row of Object.values(bucket.byRoute)) {
93
+ if (!record(row) || !clean(row.provider) || (row.toolId !== null && !clean(row.toolId))
94
+ || (row.model !== null && !clean(row.model)) || invalidPeriod(row.usage)) return true
95
+ }
96
+ }
97
+ }
98
+ return false
99
+ }
100
+
101
+ /** Only these fields from a subscription receipt can enter the durable ledger. */
102
+ export function sanitizeSubscriptionAttempts(value) {
103
+ if (value === undefined) return undefined
104
+ if (!Array.isArray(value)) return [{ usage: null, usageIncomplete: true, unknownEvidence: true }]
105
+ const attempts = value.slice(0, 16).map(item => {
106
+ if (!record(item)) return { usage: null, usageIncomplete: true, unknownEvidence: true }
107
+ return { ...Object.fromEntries(['provider', 'toolId', 'model'].filter(field => Object.hasOwn(item, field))
108
+ .map(field => [field, clean(item[field])])),
109
+ usage: record(item.usage)
110
+ ? Object.fromEntries(TOKENS.filter(field => Object.hasOwn(item.usage, field)).map(field => [field,
111
+ counter(item.usage[field]) ? item.usage[field] : null])) : null,
112
+ ...(item.ran === false ? { ran: false } : {}), ...(item.notStarted === true ? { notStarted: true } : {}),
113
+ ...(item.blocked === true ? { blocked: true } : {}),
114
+ ...(item.usageIncomplete === true || record(item.usage) && TOKENS.some(field => item.usage[field] !== undefined
115
+ && item.usage[field] !== null && !counter(item.usage[field])) ? { usageIncomplete: true } : {}),
116
+ ...(timestamp(item.finishedAt) ? { finishedAt: item.finishedAt } : {}),
117
+ ...(item.unknownEvidence === true ? { unknownEvidence: true } : {}) }
118
+ })
119
+ if (value.length > 16) attempts.push({ usage: null, usageIncomplete: true, unknownEvidence: true })
120
+ return attempts
121
+ }
122
+
123
+ export function invalidSubscriptionAttempts(value) {
124
+ if (value === undefined) return false
125
+ return !Array.isArray(value) || value.length > 16 || value.some(item => !record(item) || item.unknownEvidence === true
126
+ || (item.usage !== undefined && item.usage !== null && (!record(item.usage)
127
+ || TOKENS.some(field => item.usage[field] !== undefined && item.usage[field] !== null && !counter(item.usage[field])))))
128
+ }
129
+
130
+ function began(item) {
131
+ const contradictory = record(item.usage) && TOKENS.some(field => item.usage[field] !== undefined
132
+ && item.usage[field] !== null && item.usage[field] !== 0)
133
+ return contradictory || !(item.ran === false || item.notStarted === true || item.blocked === true)
134
+ }
135
+
136
+ function* packageEntries(item, fallbackAt) {
137
+ if (!record(item)) return
138
+ // An explicit vector is authoritative: an API result can still include its
139
+ // earlier subscription attempt; [] means no subscription was attempted.
140
+ const attempts = Object.hasOwn(item, 'subscriptionAttempts') ? sanitizeSubscriptionAttempts(item.subscriptionAttempts)
141
+ : item.billing === 'subscription' && item.ran === true ? [item] : []
142
+ for (const attempt of attempts) {
143
+ if (!began(attempt)) continue
144
+ const usage = record(attempt.usage) ? attempt.usage : null
145
+ const complete = attempt.usageIncomplete !== true && attempt.unknownEvidence !== true
146
+ && counter(usage?.inputTokens) && counter(usage?.outputTokens)
147
+ && TOKENS.every(field => usage[field] === undefined || usage[field] === null || counter(usage[field]))
148
+ const value = { ...emptySubscriptionUsage(), calls: 1, knownUsageCalls: complete ? 1 : 0, unknownUsageCalls: complete ? 0 : 1 }
149
+ for (const field of TOKENS) if (counter(usage?.[field])) value[field] = usage[field]
150
+ const when = timestamp(attempt.finishedAt) ? attempt.finishedAt : timestamp(item.finishedAt) ? item.finishedAt : fallbackAt
151
+ yield { when, route: identity(Object.fromEntries(['provider', 'toolId', 'model'].map(field => [field,
152
+ Object.hasOwn(attempt, field) ? attempt[field] : item[field]]))), usage: value }
153
+ }
154
+ }
155
+
156
+ function* usageEntries(runs) {
157
+ for (const run of Array.isArray(runs) ? runs : []) {
158
+ for (const item of [...(Array.isArray(run?.packages) ? run.packages : []), ...(Array.isArray(run?.reviews) ? run.reviews : [])]) {
159
+ yield* packageEntries(item, run?.createdAt)
160
+ }
161
+ }
162
+ }
163
+
164
+ function addArchive(target, source) {
165
+ for (const kind of ['days', 'months']) {
166
+ for (const [key, bucket] of Object.entries(source[kind])) addBucket(target[kind][key] ??= emptyBucket(), bucket)
167
+ }
168
+ }
169
+
170
+ function priorArchive(run) {
171
+ return sanitizeArchivedSubscriptionUsage(run?.priorAttemptSubscriptionUsage, { legacySpending: run?.priorAttemptSpending })
172
+ }
173
+
174
+ /** Fold only evicted/replaced records. Retained records remain live. */
175
+ export function archiveSubscriptionUsage(runs, archived) {
176
+ const result = sanitizeArchivedSubscriptionUsage(archived)
177
+ for (const run of Array.isArray(runs) ? runs : []) addArchive(result, priorArchive(run))
178
+ for (const { when, route, usage } of usageEntries(runs)) {
179
+ if (!timestamp(when)) continue
180
+ for (const [kind, key] of [['days', dayKey(when)], ['months', monthKey(when)]]) {
181
+ const bucket = result[kind][key] ??= emptyBucket()
182
+ addPeriod(bucket.total, usage); addRoute(bucket, route, usage)
183
+ }
184
+ }
185
+ return sanitizeArchivedSubscriptionUsage(result)
186
+ }
187
+
188
+ /** Known quantities only. Missing usage remains explicit instead of becoming zero. */
189
+ export function subscriptionUsage(runs, at = Date.now(), archived = null) {
190
+ const all = archiveSubscriptionUsage(runs, archived)
191
+ const daily = all.days[dayKey(at)] ?? emptyBucket(), monthly = all.months[monthKey(at)] ?? emptyBucket()
192
+ const keys = new Set([...Object.keys(daily.byRoute), ...Object.keys(monthly.byRoute)])
193
+ return { today: period(daily.total), month: period(monthly.total),
194
+ byRoute: [...keys].map(key => ({ ...identity(monthly.byRoute[key] ?? daily.byRoute[key]),
195
+ today: period(daily.byRoute[key]?.usage), month: period(monthly.byRoute[key]?.usage) })) }
196
+ }
197
+
198
+ export function subscriptionUsageForPackage(item) {
199
+ const result = emptySubscriptionUsage()
200
+ for (const { usage } of packageEntries(item)) addPeriod(result, usage)
201
+ return result
202
+ }
203
+
204
+ /** Includes prior retry attempts, without summing both daily and monthly copies. */
205
+ export function subscriptionUsageForRun(run) {
206
+ const result = emptySubscriptionUsage()
207
+ for (const bucket of Object.values(priorArchive(run).months)) addPeriod(result, bucket.total)
208
+ for (const { usage } of usageEntries([run])) addPeriod(result, usage)
209
+ return result
210
+ }