@goodandready/dsh-subscriptions 0.6.44 → 0.6.45

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/accounts.js CHANGED
@@ -1,3 +1,46 @@
1
+ import { readFileSync, writeFileSync, mkdirSync } from "node:fs"
2
+ import { homedir } from "node:os"
3
+ import { join } from "node:path"
4
+
5
+ function resolveQuarantineFilePath() {
6
+ const base = process.env.DSH_HOME || join(homedir(), '.dsh')
7
+ return join(base, 'subscriptions', 'quarantines.json')
8
+ }
9
+
10
+ function loadQuarantinesFromFile(map) {
11
+ try {
12
+ const p = resolveQuarantineFilePath()
13
+ const content = readFileSync(p, 'utf8')
14
+ const json = JSON.parse(content)
15
+ const now = Date.now()
16
+ if (json && typeof json === 'object') {
17
+ for (const [k, v] of Object.entries(json)) {
18
+ if (v && Number(v.until) > now) {
19
+ map.set(k, v)
20
+ }
21
+ }
22
+ }
23
+ } catch { /* best-effort */ }
24
+ }
25
+
26
+ function saveQuarantinesToFile(map) {
27
+ try {
28
+ const p = resolveQuarantineFilePath()
29
+ mkdirSync(join(p, '..'), { recursive: true })
30
+ const obj = {}
31
+ const now = Date.now()
32
+ for (const [k, v] of map.entries()) {
33
+ if (v && Number(v.until) > now) {
34
+ obj[k] = v
35
+ }
36
+ }
37
+ writeFileSync(p, JSON.stringify(obj), 'utf8')
38
+ } catch { /* best-effort */ }
39
+ }
40
+
41
+ const sharedQuarantines = new Map()
42
+ loadQuarantinesFromFile(sharedQuarantines)
43
+
1
44
  import { needsWarmupProbe, enterProbing, resolveWarmupSuccess, resolveWarmupFailure, STATUS_QUARANTINE, STATUS_PROBING } from "./quarantine.js"
2
45
  import { fetchWithTimeout, isTimeoutError } from "./http.js"
3
46
  import { credentialRef } from '@deepseek-ai/dsh-credentials'
@@ -62,7 +105,7 @@ export function vendorConfig(provider, cfg) {
62
105
  export function createAccountStore({ credentials, getConfig, fetchImpl, fetchForRef, onLimitNotice }) {
63
106
  const cooldowns = new Map()
64
107
  const quotas = new Map()
65
- const quarantines = new Map()
108
+ const quarantines = sharedQuarantines
66
109
  const writeQueues = new Map()
67
110
  const credentialVersions = new Map()
68
111
  const clearedRefs = new Set()
@@ -183,6 +226,7 @@ export function createAccountStore({ credentials, getConfig, fetchImpl, fetchFor
183
226
  quarantineReason: quarantines.get(ref) ? quarantines.get(ref).reason : null,
184
227
  cooldownFamilies: cooldowns.get(ref) && cooldowns.get(ref).families ? cooldowns.get(ref).families : null,
185
228
  quota: quotas.get(ref) || null,
229
+ pacePerHour: quotas.get(ref)?.pacePerHour || 0,
186
230
  usage: windows.get(ref) || base.usage || null,
187
231
  health: health.get(ref) || null,
188
232
  healthScore: computeHealthScore(health.get(ref) || null),
@@ -218,6 +262,7 @@ export function createAccountStore({ credentials, getConfig, fetchImpl, fetchFor
218
262
  quarantineUntil: inQ ? q.until : 0,
219
263
  quarantineReason: inQ ? q.reason : null,
220
264
  label: slot.label || info.label,
265
+ pacePerHour: info.pacePerHour || 0,
221
266
  })
222
267
  }
223
268
  return out
@@ -417,6 +462,7 @@ export function createAccountStore({ credentials, getConfig, fetchImpl, fetchFor
417
462
  attempts: Number(attempts) || 1,
418
463
  status: STATUS_QUARANTINE,
419
464
  })
465
+ saveQuarantinesToFile(quarantines)
420
466
  },
421
467
  getQuarantine(ref, now = Date.now()) {
422
468
  const q = quarantines.get(ref)
@@ -456,6 +502,7 @@ export function createAccountStore({ credentials, getConfig, fetchImpl, fetchFor
456
502
  },
457
503
  releaseQuarantine(ref) {
458
504
  quarantines.delete(ref)
505
+ saveQuarantinesToFile(quarantines)
459
506
  },
460
507
  rememberRequest(ref) { requestCounts.set(ref, (requestCounts.get(ref) || 0) + 1) },
461
508
  getRequestCount(ref) { return requestCounts.get(ref) || 0 },
@@ -18,6 +18,7 @@ export function createAdapterManager({
18
18
  syncCustomVendors,
19
19
  syncOllama,
20
20
  stripLegacySlots,
21
+ ollamaFallback,
21
22
  }) {
22
23
  let handle
23
24
  let adapterDisposed = false
@@ -58,6 +59,8 @@ export function createAdapterManager({
58
59
  fetchImpl: fetch,
59
60
  fetchForRef,
60
61
  hideDeprecatedModels: () => Boolean(live().hideDeprecatedModels),
62
+ cascadingFallback: (...args) => cascadeFallbackStream(...args),
63
+ ollamaFallback: typeof ollamaFallback === 'function' ? ollamaFallback : async function* ({ err }) { throw err },
61
64
  })
62
65
 
63
66
  async function* cascadeFallbackStream({ options, provider, err, visited = new Set() }) {
package/lib/index.js CHANGED
@@ -302,6 +302,7 @@ export function apply(ctx, rawConfig) {
302
302
  syncCustomVendors,
303
303
  syncOllama,
304
304
  stripLegacySlots,
305
+ ollamaFallback: ollamaFallbackStream,
305
306
  })
306
307
  const { syncAdapter, reconcileSlots, refreshModels, cascadeFallbackStream } = adapterManager
307
308
  adapterManager.registerAdapterLifecycle()
package/lib/rotate.js CHANGED
@@ -81,7 +81,11 @@ export function pickAccount(accounts, nowMs, opts) {
81
81
  const pinned = list.find((a) => (a.ref || a.id) === pinnedRef)
82
82
  if (pinned && pinned.hasToken && !isQuarantined(pinned, now)) {
83
83
  const isCooldown = pinned.cooldownUntil && Number(pinned.cooldownUntil) > now && cooldownBlocks(pinned, opts && opts.family)
84
- if (!isCooldown && !isQuotaExhausted(pinned) && !isUsageExhausted(pinned)) {
84
+ const isPinnedZero = Boolean(pinned.quota && (
85
+ (pinned.quota.remaining != null && Number(pinned.quota.remaining) <= 0) ||
86
+ (pinned.quota.remainingPercent != null && Number(pinned.quota.remainingPercent) <= 0)
87
+ ) && (!pinned.quota.resetAt || Number(pinned.quota.resetAt) > now))
88
+ if (!isCooldown && !isPinnedZero && !isQuotaExhausted(pinned) && !isUsageExhausted(pinned)) {
85
89
  return pinned
86
90
  }
87
91
  }
@@ -116,8 +120,12 @@ export function pickAccount(accounts, nowMs, opts) {
116
120
  const isCooldown = acc.cooldownUntil && Number(acc.cooldownUntil) > now && cooldownBlocks(acc, opts && opts.family)
117
121
  const inQuarantine = isQuarantined(acc, now)
118
122
  const isHardExhausted = (acc.usagePercent != null && Number(acc.usagePercent) >= 100 && (!acc.quota || !acc.quota.resetAt || Number(acc.quota.resetAt) > now))
123
+ const isZeroQuota = Boolean(acc.quota && (
124
+ (acc.quota.remaining != null && Number(acc.quota.remaining) <= 0) ||
125
+ (acc.quota.remainingPercent != null && Number(acc.quota.remainingPercent) <= 0)
126
+ ) && (!acc.quota.resetAt || Number(acc.quota.resetAt) > now))
119
127
 
120
- if (isCooldown || inQuarantine || isHardExhausted) {
128
+ if (isCooldown || inQuarantine || isHardExhausted || isZeroQuota) {
121
129
  continue
122
130
  }
123
131
 
@@ -27,6 +27,7 @@ export async function* streamWithRotation({
27
27
  sessionId: options && options.sessionId,
28
28
  tag: options && options.tag,
29
29
  vip: options && options.vip,
30
+ autoPacing: options && (options.autoPacing !== undefined ? options.autoPacing : (options.config && options.config.autoPacing)),
30
31
  })
31
32
 
32
33
  if (!account || tried.has(account.ref || account.id)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-subscriptions",
3
- "version": "0.6.44",
3
+ "version": "0.6.45",
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",