@goodandready/dsh-subscriptions 0.6.70 → 0.6.72
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/README.md +7 -1
- package/README.ru.md +7 -1
- package/README.zh.md +7 -1
- package/lib/accounts-view.js +4 -0
- package/lib/accounts.js +13 -0
- package/lib/adapter-manager.js +2 -0
- package/lib/adapter.js +8 -0
- package/lib/client.js +637 -807
- package/lib/config-schema.js +4 -0
- package/lib/diagnostics.js +2 -0
- package/lib/doctor.js +163 -0
- package/lib/instructions.js +138 -0
- package/lib/quarantine.js +6 -2
- package/lib/reset-clock.js +87 -0
- package/lib/rotate.js +34 -3
- package/lib/routes/accounts.js +72 -0
- package/lib/routes/status.js +99 -5
- package/lib/sentinel.js +136 -0
- package/lib/stream-rotate.js +26 -10
- package/package.json +1 -1
package/lib/routes/status.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { diagnoseVendorError } from '../doctor.js'
|
|
2
|
+
import { INSTRUCTIONS } from '../instructions.js'
|
|
1
3
|
import {
|
|
2
4
|
getModelReasoningCapabilities,
|
|
3
5
|
setSessionReasoning,
|
|
@@ -505,9 +507,13 @@ export function registerStatusRoutes(ctx, state) {
|
|
|
505
507
|
blob = await store.loadBlob(ref)
|
|
506
508
|
blob = await store.ensureFresh(provider, blob, ref)
|
|
507
509
|
} catch (e) {
|
|
508
|
-
const latencyMs = Date.now() - t0
|
|
510
|
+
const latencyMs = Math.max(1, Date.now() - t0)
|
|
511
|
+
const latencyGrade = latencyMs < 250 ? 'good' : (latencyMs < 750 ? 'fair' : 'poor')
|
|
512
|
+
if (typeof store.rememberPing === "function") store.rememberPing(ref, { latencyMs, latencyGrade })
|
|
513
|
+
const docVerdict = diagnoseVendorError(e, provider, blob)
|
|
514
|
+
if (typeof store.rememberDoctorVerdict === "function") store.rememberDoctorVerdict(ref, docVerdict)
|
|
509
515
|
history.add({ provider, ref, path: "/smoke", status: 401, ms: latencyMs })
|
|
510
|
-
writeJson(res, 502, { ok: false, provider, index, ref, latencyMs, error: { code: 'auth_failed', message: String(e && e.message || e) } })
|
|
516
|
+
writeJson(res, 502, { ok: false, provider, index, ref, latencyMs, latencyGrade, doctor: docVerdict, error: { code: 'auth_failed', message: String(e && e.message || e) } })
|
|
511
517
|
return
|
|
512
518
|
}
|
|
513
519
|
|
|
@@ -525,7 +531,10 @@ export function registerStatusRoutes(ctx, state) {
|
|
|
525
531
|
}
|
|
526
532
|
try {
|
|
527
533
|
await vendor.check(blob, vendorConfig(provider, live()), probeFetch)
|
|
528
|
-
const latencyMs = Date.now() - t0
|
|
534
|
+
const latencyMs = Math.max(1, Date.now() - t0)
|
|
535
|
+
const latencyGrade = latencyMs < 250 ? 'good' : (latencyMs < 750 ? 'fair' : 'poor')
|
|
536
|
+
if (typeof store.rememberPing === "function") store.rememberPing(ref, { latencyMs, latencyGrade })
|
|
537
|
+
if (typeof store.rememberDoctorVerdict === "function") store.rememberDoctorVerdict(ref, null)
|
|
529
538
|
history.add({ provider, ref, path: "/smoke", status: 200, ms: latencyMs })
|
|
530
539
|
writeJson(res, 200, {
|
|
531
540
|
ok: true,
|
|
@@ -533,6 +542,7 @@ export function registerStatusRoutes(ctx, state) {
|
|
|
533
542
|
index,
|
|
534
543
|
ref,
|
|
535
544
|
latencyMs,
|
|
545
|
+
latencyGrade,
|
|
536
546
|
label: pmL(blob.label),
|
|
537
547
|
email: pmE(blob.email),
|
|
538
548
|
quota: capturedQuota || info.quota || null,
|
|
@@ -540,7 +550,11 @@ export function registerStatusRoutes(ctx, state) {
|
|
|
540
550
|
})
|
|
541
551
|
return
|
|
542
552
|
} catch (e) {
|
|
543
|
-
const latencyMs = Date.now() - t0
|
|
553
|
+
const latencyMs = Math.max(1, Date.now() - t0)
|
|
554
|
+
const latencyGrade = latencyMs < 250 ? 'good' : (latencyMs < 750 ? 'fair' : 'poor')
|
|
555
|
+
if (typeof store.rememberPing === "function") store.rememberPing(ref, { latencyMs, latencyGrade })
|
|
556
|
+
const docVerdict = diagnoseVendorError(e, provider, blob)
|
|
557
|
+
if (typeof store.rememberDoctorVerdict === "function") store.rememberDoctorVerdict(ref, docVerdict)
|
|
544
558
|
history.add({ provider, ref, path: "/smoke", status: 502, ms: latencyMs })
|
|
545
559
|
writeJson(res, 502, {
|
|
546
560
|
ok: false,
|
|
@@ -548,13 +562,18 @@ export function registerStatusRoutes(ctx, state) {
|
|
|
548
562
|
index,
|
|
549
563
|
ref,
|
|
550
564
|
latencyMs,
|
|
565
|
+
latencyGrade,
|
|
566
|
+
doctor: docVerdict,
|
|
551
567
|
error: { code: e && e.code ? e.code : 'VENDOR', message: String(e && e.message || e).slice(0, 300) },
|
|
552
568
|
})
|
|
553
569
|
return
|
|
554
570
|
}
|
|
555
571
|
}
|
|
556
572
|
|
|
557
|
-
const latencyMs = Date.now() - t0
|
|
573
|
+
const latencyMs = Math.max(1, Date.now() - t0)
|
|
574
|
+
const latencyGrade = latencyMs < 250 ? 'good' : (latencyMs < 750 ? 'fair' : 'poor')
|
|
575
|
+
if (typeof store.rememberPing === "function") store.rememberPing(ref, { latencyMs, latencyGrade })
|
|
576
|
+
if (typeof store.rememberDoctorVerdict === "function") store.rememberDoctorVerdict(ref, null)
|
|
558
577
|
history.add({ provider, ref, path: "/smoke", status: 200, ms: latencyMs })
|
|
559
578
|
writeJson(res, 200, {
|
|
560
579
|
ok: true,
|
|
@@ -562,6 +581,7 @@ export function registerStatusRoutes(ctx, state) {
|
|
|
562
581
|
index,
|
|
563
582
|
ref,
|
|
564
583
|
latencyMs,
|
|
584
|
+
latencyGrade,
|
|
565
585
|
label: pmL(blob.label),
|
|
566
586
|
email: pmE(blob.email),
|
|
567
587
|
quota: info.quota || null,
|
|
@@ -569,6 +589,80 @@ export function registerStatusRoutes(ctx, state) {
|
|
|
569
589
|
}),
|
|
570
590
|
}), 'dsh-subscriptions: /smoke')
|
|
571
591
|
|
|
592
|
+
// Issue #542: Latency RTT ping test endpoint
|
|
593
|
+
ctx.effect(() => ctx.webServer.register({
|
|
594
|
+
kind: 'exact',
|
|
595
|
+
path: '/dsh-subscriptions/ping',
|
|
596
|
+
handler: safeJsonHandler(async (req, res) => {
|
|
597
|
+
if (req.method !== 'POST') {
|
|
598
|
+
writeJson(res, 405, { ok: false, error: { code: 'method', message: 'POST only' } })
|
|
599
|
+
return
|
|
600
|
+
}
|
|
601
|
+
if (!isTrustedSettingsRequest(req, ctx)) {
|
|
602
|
+
writeJson(res, 403, { ok: false, error: { code: 'forbidden', message: 'same-origin only' } })
|
|
603
|
+
return
|
|
604
|
+
}
|
|
605
|
+
let payload = {}
|
|
606
|
+
try { payload = JSON.parse((await readBody(req, 64 * 1024)).toString('utf8') || '{}') } catch { /* ignore ping payload */ }
|
|
607
|
+
const provider = payload.provider
|
|
608
|
+
const index = payload.index || 1
|
|
609
|
+
if (!provider || !isProvider(provider)) {
|
|
610
|
+
writeJson(res, 400, { ok: false, error: { code: 'invalid_provider', message: 'Provider required' } })
|
|
611
|
+
return
|
|
612
|
+
}
|
|
613
|
+
const ref = oauthRef(provider, index)
|
|
614
|
+
const info = await store.describeRef(ref)
|
|
615
|
+
if (!info.configured) {
|
|
616
|
+
writeJson(res, 400, { ok: false, error: { code: 'not_configured', message: 'Account is not configured' } })
|
|
617
|
+
return
|
|
618
|
+
}
|
|
619
|
+
const t0 = Date.now()
|
|
620
|
+
let latencyMs = 0
|
|
621
|
+
let latencyGrade = 'good'
|
|
622
|
+
try {
|
|
623
|
+
const blob = await store.loadBlob(ref)
|
|
624
|
+
const vendor = getVendor(provider)
|
|
625
|
+
const cfg = vendorConfig(provider, live())
|
|
626
|
+
const doFetch = (typeof fetchForRef === 'function' && fetchForRef(ref)) || fetch
|
|
627
|
+
if (typeof vendor.check === 'function') {
|
|
628
|
+
await vendor.check(blob, cfg, doFetch)
|
|
629
|
+
} else if (typeof vendor.listModels === 'function') {
|
|
630
|
+
await vendor.listModels(blob, cfg, doFetch)
|
|
631
|
+
}
|
|
632
|
+
latencyMs = Math.max(1, Date.now() - t0)
|
|
633
|
+
latencyGrade = latencyMs < 250 ? 'good' : (latencyMs < 750 ? 'fair' : 'poor')
|
|
634
|
+
if (typeof store.rememberPing === 'function') store.rememberPing(ref, { latencyMs, latencyGrade })
|
|
635
|
+
if (typeof store.rememberDoctorVerdict === 'function') store.rememberDoctorVerdict(ref, null)
|
|
636
|
+
writeJson(res, 200, { ok: true, provider, index, ref, latencyMs, latencyGrade })
|
|
637
|
+
} catch (err) {
|
|
638
|
+
latencyMs = Math.max(1, Date.now() - t0)
|
|
639
|
+
latencyGrade = 'poor'
|
|
640
|
+
const doc = diagnoseVendorError(err, provider)
|
|
641
|
+
if (typeof store.rememberPing === 'function') store.rememberPing(ref, { latencyMs, latencyGrade })
|
|
642
|
+
if (typeof store.rememberDoctorVerdict === 'function') store.rememberDoctorVerdict(ref, doc)
|
|
643
|
+
writeJson(res, 502, { ok: false, provider, index, ref, latencyMs, latencyGrade, doctor: doc, error: { message: String(err && err.message || err) } })
|
|
644
|
+
}
|
|
645
|
+
}),
|
|
646
|
+
}), 'dsh-subscriptions: /ping')
|
|
647
|
+
|
|
648
|
+
// Issue #540: Dynamic vendor instructions API
|
|
649
|
+
ctx.effect(() => ctx.webServer.register({
|
|
650
|
+
kind: 'exact',
|
|
651
|
+
path: '/dsh-subscriptions/instructions',
|
|
652
|
+
handler: safeJsonHandler(async (req, res) => {
|
|
653
|
+
if (req.method !== 'GET') {
|
|
654
|
+
writeJson(res, 405, { ok: false, error: { code: 'method', message: 'GET only' } })
|
|
655
|
+
return
|
|
656
|
+
}
|
|
657
|
+
if (!isTrustedSettingsRequest(req, ctx)) {
|
|
658
|
+
writeJson(res, 403, { ok: false, error: { code: 'forbidden', message: 'same-origin only' } })
|
|
659
|
+
return
|
|
660
|
+
}
|
|
661
|
+
writeJson(res, 200, { ok: true, instructions: INSTRUCTIONS })
|
|
662
|
+
}),
|
|
663
|
+
}), 'dsh-subscriptions: /instructions')
|
|
664
|
+
|
|
665
|
+
|
|
572
666
|
// #351: Quota and session alerts API
|
|
573
667
|
ctx.effect(() => ctx.webServer.register({
|
|
574
668
|
kind: 'exact',
|
package/lib/sentinel.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
export const SOFT_FAILURE_PATTERNS = [
|
|
2
|
+
/currently (?:unable to process|unavailable|overloaded)/i,
|
|
3
|
+
/experiencing (?:exceptionally )?(?:high|heavy) (?:traffic|demand|load)/i,
|
|
4
|
+
/capacity (?:is )?temporarily (?:unavailable|limited|exceeded)/i,
|
|
5
|
+
/model is (?:currently )?(?:overloaded|busy)/i,
|
|
6
|
+
/due to high traffic/i,
|
|
7
|
+
/usage cap reached, switching to lower/i,
|
|
8
|
+
/you have reached your (?:current )?usage limit/i,
|
|
9
|
+
/server is currently under heavy load/i,
|
|
10
|
+
/i apologize, but (?:i am unable to assist|i cannot fulfill this request due to (?:high demand|capacity|server load))/i,
|
|
11
|
+
/our systems are experiencing heavy load/i,
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
const SUSPICIOUS_ROOTS = [
|
|
15
|
+
'i am',
|
|
16
|
+
'i apologize',
|
|
17
|
+
'we are',
|
|
18
|
+
'capacity',
|
|
19
|
+
'the model',
|
|
20
|
+
'due to',
|
|
21
|
+
'usage cap',
|
|
22
|
+
'you have reached',
|
|
23
|
+
'server is',
|
|
24
|
+
'our systems',
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
export function extractChunkText(chunk) {
|
|
28
|
+
if (!chunk) return ''
|
|
29
|
+
if (typeof chunk === 'string') return chunk
|
|
30
|
+
if (typeof chunk.delta === 'string') return chunk.delta
|
|
31
|
+
if (typeof chunk.content === 'string') return chunk.content
|
|
32
|
+
if (typeof chunk.text === 'string') return chunk.text
|
|
33
|
+
if (chunk.choices && Array.isArray(chunk.choices) && chunk.choices[0]) {
|
|
34
|
+
const c0 = chunk.choices[0]
|
|
35
|
+
if (c0.delta && typeof c0.delta.content === 'string') return c0.delta.content
|
|
36
|
+
if (typeof c0.text === 'string') return c0.text
|
|
37
|
+
}
|
|
38
|
+
if (chunk.candidates && Array.isArray(chunk.candidates) && chunk.candidates[0]) {
|
|
39
|
+
const cand = chunk.candidates[0]
|
|
40
|
+
if (cand.content && Array.isArray(cand.content.parts)) {
|
|
41
|
+
return cand.content.parts.map((p) => (p && typeof p.text === 'string' ? p.text : '')).join('')
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return ''
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function detectSoftFailure(text) {
|
|
48
|
+
if (!text || typeof text !== 'string') return null
|
|
49
|
+
const cleaned = text.trim()
|
|
50
|
+
if (!cleaned) return null
|
|
51
|
+
for (const pattern of SOFT_FAILURE_PATTERNS) {
|
|
52
|
+
if (pattern.test(cleaned)) {
|
|
53
|
+
return pattern.toString()
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return null
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function isCandidatePrefix(text) {
|
|
60
|
+
if (!text) return true
|
|
61
|
+
const low = text.toLowerCase().trim()
|
|
62
|
+
if (low.length < 3) return true
|
|
63
|
+
for (const root of SUSPICIOUS_ROOTS) {
|
|
64
|
+
if (root.startsWith(low) || low.startsWith(root)) {
|
|
65
|
+
return true
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return false
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function createSoftFailureError(pattern, text) {
|
|
72
|
+
const preview = String(text || '').slice(0, 120).replace(/\s+/g, ' ')
|
|
73
|
+
const err = new Error(`Soft-failure detected: ${preview}`)
|
|
74
|
+
return Object.assign(err, {
|
|
75
|
+
name: 'SoftFailureError',
|
|
76
|
+
code: 'SOFT_FAILURE',
|
|
77
|
+
status: 429,
|
|
78
|
+
isSoftFailure: true,
|
|
79
|
+
degraded: true,
|
|
80
|
+
pattern,
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function* withSoftFailureSentinel(stream, options = {}) {
|
|
85
|
+
const enabled = options.enabled !== false
|
|
86
|
+
if (!enabled || !stream) {
|
|
87
|
+
if (stream) {
|
|
88
|
+
for await (const chunk of stream) {
|
|
89
|
+
yield chunk
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const bufferLimitChars = Number(options.bufferLimitChars) || 250
|
|
96
|
+
const maxBufferChunks = Number(options.maxBufferChunks) || 4
|
|
97
|
+
|
|
98
|
+
const bufferedChunks = []
|
|
99
|
+
let accumulatedText = ''
|
|
100
|
+
let flushed = false
|
|
101
|
+
|
|
102
|
+
for await (const chunk of stream) {
|
|
103
|
+
if (flushed) {
|
|
104
|
+
yield chunk
|
|
105
|
+
continue
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
bufferedChunks.push(chunk)
|
|
109
|
+
accumulatedText += extractChunkText(chunk)
|
|
110
|
+
|
|
111
|
+
const matched = detectSoftFailure(accumulatedText)
|
|
112
|
+
if (matched) {
|
|
113
|
+
throw createSoftFailureError(matched, accumulatedText)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const candidate = isCandidatePrefix(accumulatedText)
|
|
117
|
+
if (!candidate || accumulatedText.length >= bufferLimitChars || bufferedChunks.length >= maxBufferChunks) {
|
|
118
|
+
flushed = true
|
|
119
|
+
for (const b of bufferedChunks) {
|
|
120
|
+
yield b
|
|
121
|
+
}
|
|
122
|
+
bufferedChunks.length = 0
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!flushed) {
|
|
127
|
+
const matched = detectSoftFailure(accumulatedText)
|
|
128
|
+
if (matched) {
|
|
129
|
+
throw createSoftFailureError(matched, accumulatedText)
|
|
130
|
+
}
|
|
131
|
+
for (const b of bufferedChunks) {
|
|
132
|
+
yield b
|
|
133
|
+
}
|
|
134
|
+
bufferedChunks.length = 0
|
|
135
|
+
}
|
|
136
|
+
}
|
package/lib/stream-rotate.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { defaultConcurrencyQueue } from './concurrency-queue.js'
|
|
2
2
|
import { recordSessionCacheUsage } from './session-pin.js'
|
|
3
3
|
import { pickAccountWithTrace, markCooldown, isSwitchableError, isRegionError, modelFamily } from './rotate.js'
|
|
4
|
-
import { putInQuarantine, REASON_RATE_LIMIT, REASON_HARD_LIMIT, REASON_REVOKED } from './quarantine.js'
|
|
4
|
+
import { putInQuarantine, REASON_RATE_LIMIT, REASON_HARD_LIMIT, REASON_REVOKED, REASON_DEGRADED } from './quarantine.js'
|
|
5
5
|
import { notifySessionExpired, notifyQuarantineEntered } from './alerts.js'
|
|
6
6
|
import { withRunawayGuard } from './runaway-guard.js'
|
|
7
7
|
import { defaultErrorBuffer } from './error-buffer.js'
|
|
8
|
+
import { withSoftFailureSentinel } from './sentinel.js'
|
|
9
|
+
import { resolveAccurateReset } from './reset-clock.js'
|
|
8
10
|
|
|
9
11
|
export async function* streamWithRotation({
|
|
10
12
|
accounts,
|
|
@@ -55,6 +57,7 @@ export async function* streamWithRotation({
|
|
|
55
57
|
autoPacing: options && (options.autoPacing !== undefined ? options.autoPacing : (options.config && options.config.autoPacing)),
|
|
56
58
|
drainBeforeReset: options && (options.drainBeforeReset !== undefined ? options.drainBeforeReset : (options.config && options.config.drainBeforeReset)),
|
|
57
59
|
drainSafetyPercent: options && (options.drainSafetyPercent !== undefined ? options.drainSafetyPercent : (options.config && options.config.drainSafetyPercent)),
|
|
60
|
+
rotationStrategy: options && (options.rotationStrategy !== undefined ? options.rotationStrategy : (options.config && options.config.rotationStrategy)),
|
|
58
61
|
})
|
|
59
62
|
const account = pickRes.account
|
|
60
63
|
|
|
@@ -147,7 +150,10 @@ export async function* streamWithRotation({
|
|
|
147
150
|
streamOnce(account, { ...options, _runawayState: runawayState }),
|
|
148
151
|
runawayOpts
|
|
149
152
|
)
|
|
150
|
-
|
|
153
|
+
const sentineledStream = withSoftFailureSentinel(guardedStream, {
|
|
154
|
+
enabled: (options?.enableSoftFailureSentinel !== false) && (options?.config?.enableSoftFailureSentinel !== false),
|
|
155
|
+
})
|
|
156
|
+
for await (const chunk of sentineledStream) {
|
|
151
157
|
firstChunkDelivered = true
|
|
152
158
|
const cachedTokens = Number(
|
|
153
159
|
(chunk && chunk.usage && (chunk.usage.cacheReadTokens || chunk.usage.cachedTokens)) ||
|
|
@@ -195,18 +201,24 @@ export async function* streamWithRotation({
|
|
|
195
201
|
currentAttempt.status = 'switched'
|
|
196
202
|
currentAttempt.switchError = err ? (err.code || err.status || err.message) : 'switchable'
|
|
197
203
|
|
|
198
|
-
|
|
199
|
-
const
|
|
204
|
+
const accurateUntil = resolveAccurateReset(err, account, options && options.provider, nowMs())
|
|
205
|
+
const isSoft = Boolean(err && (err.code === 'SOFT_FAILURE' || err.isSoftFailure))
|
|
206
|
+
|
|
207
|
+
// Move slot to cooldown and quarantine (#172, #538)
|
|
208
|
+
const effectiveCooldownMs = isSoft ? Math.min(cooldownMs, 5 * 60 * 1000) : cooldownMs
|
|
209
|
+
const cooled = markCooldown(account, nowMs(), effectiveCooldownMs, modelFamily(options && options.provider, options && options.model), accurateUntil)
|
|
200
210
|
account.cooldownUntil = cooled.cooldownUntil
|
|
201
211
|
if (cooled.cooldownFamilies) account.cooldownFamilies = cooled.cooldownFamilies
|
|
202
212
|
|
|
203
213
|
const status = Number(err && (err.status || err.statusCode) || 0)
|
|
204
214
|
const code = String(err && err.code || '')
|
|
205
|
-
const reason =
|
|
206
|
-
?
|
|
207
|
-
: (status ===
|
|
208
|
-
?
|
|
209
|
-
:
|
|
215
|
+
const reason = isSoft
|
|
216
|
+
? REASON_DEGRADED
|
|
217
|
+
: (status === 401 || code === 'AUTH' || code === 'TOKEN_REVOKED')
|
|
218
|
+
? REASON_REVOKED
|
|
219
|
+
: (status === 403 || code === 'HARD_LIMIT' || code === 'LICENSE_REQUIRED')
|
|
220
|
+
? REASON_HARD_LIMIT
|
|
221
|
+
: REASON_RATE_LIMIT
|
|
210
222
|
|
|
211
223
|
if (err && err.isWarmupProbeFailure && err.warmupAccount) {
|
|
212
224
|
account.quarantineUntil = err.warmupAccount.quarantineUntil
|
|
@@ -214,7 +226,7 @@ export async function* streamWithRotation({
|
|
|
214
226
|
account.quarantineAttempts = err.warmupAccount.quarantineAttempts
|
|
215
227
|
account.status = err.warmupAccount.status
|
|
216
228
|
} else {
|
|
217
|
-
const quarantined = putInQuarantine(account, reason, nowMs())
|
|
229
|
+
const quarantined = putInQuarantine(account, reason, nowMs(), accurateUntil)
|
|
218
230
|
account.quarantineUntil = quarantined.quarantineUntil
|
|
219
231
|
account.quarantineReason = quarantined.quarantineReason
|
|
220
232
|
account.quarantineAttempts = (account.quarantineAttempts || 0) + 1
|
|
@@ -222,6 +234,10 @@ export async function* streamWithRotation({
|
|
|
222
234
|
|
|
223
235
|
currentAttempt.quarantineReason = account.quarantineReason || reason
|
|
224
236
|
currentAttempt.quarantineUntil = account.quarantineUntil
|
|
237
|
+
if (accurateUntil) {
|
|
238
|
+
currentAttempt.accurateReset = true
|
|
239
|
+
currentAttempt.resetAt = accurateUntil
|
|
240
|
+
}
|
|
225
241
|
|
|
226
242
|
try {
|
|
227
243
|
notifyQuarantineEntered({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-subscriptions",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.72",
|
|
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",
|