@goodandready/dsh-subscriptions 0.6.50 → 0.6.52
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 +26 -10
- package/lib/adapter-manager.js +1 -1
- package/lib/adapter.js +13 -2
- package/lib/blob.js +35 -2
- package/lib/client.js +46 -22
- package/lib/forecast.js +15 -1
- package/lib/import-auth.js +31 -3
- package/lib/index.js +1 -1
- package/lib/jwt.js +33 -0
- package/lib/stream-rotate.js +11 -3
- package/lib/vendors/antigravity.js +15 -3
- package/package.json +1 -1
package/lib/accounts.js
CHANGED
|
@@ -13,10 +13,9 @@ function loadQuarantinesFromFile(map) {
|
|
|
13
13
|
const p = resolveQuarantineFilePath()
|
|
14
14
|
const content = readFileSync(p, 'utf8')
|
|
15
15
|
const json = JSON.parse(content)
|
|
16
|
-
const now = Date.now()
|
|
17
16
|
if (json && typeof json === 'object') {
|
|
18
17
|
for (const [k, v] of Object.entries(json)) {
|
|
19
|
-
if (v &&
|
|
18
|
+
if (v && typeof v === 'object' && v.until) {
|
|
20
19
|
map.set(k, v)
|
|
21
20
|
}
|
|
22
21
|
}
|
|
@@ -29,9 +28,8 @@ function saveQuarantinesToFile(map) {
|
|
|
29
28
|
const p = resolveQuarantineFilePath()
|
|
30
29
|
mkdirSync(join(p, '..'), { recursive: true })
|
|
31
30
|
const obj = {}
|
|
32
|
-
const now = Date.now()
|
|
33
31
|
for (const [k, v] of map.entries()) {
|
|
34
|
-
if (v &&
|
|
32
|
+
if (v && typeof v === 'object' && v.until) {
|
|
35
33
|
obj[k] = v
|
|
36
34
|
}
|
|
37
35
|
}
|
|
@@ -452,12 +450,25 @@ export function createAccountStore({ credentials, getConfig, fetchImpl, fetchFor
|
|
|
452
450
|
},
|
|
453
451
|
rememberQuota(ref, snap) {
|
|
454
452
|
if (snap) {
|
|
453
|
+
const prevSnap = quotas.get(ref)
|
|
455
454
|
quotas.set(ref, snap)
|
|
456
455
|
if (snap.usedPercent != null) {
|
|
457
456
|
const now = snap.measuredAt || Date.now()
|
|
458
|
-
|
|
457
|
+
let samples = (quotaSamples.get(ref) || []).slice()
|
|
459
458
|
const remainingPercent = snap.remainingPercent ?? (100 - Number(snap.usedPercent))
|
|
460
|
-
|
|
459
|
+
const lastSample = samples[samples.length - 1]
|
|
460
|
+
|
|
461
|
+
const prevReset = prevSnap?.resetAt ?? lastSample?.resetAt ?? null
|
|
462
|
+
const currReset = snap.resetAt ?? null
|
|
463
|
+
const resetChanged = (prevReset !== null && currReset !== null && Math.abs(Number(prevReset) - Number(currReset)) > 300) ||
|
|
464
|
+
(prevSnap !== undefined && (prevReset === null) !== (currReset === null))
|
|
465
|
+
const increased = lastSample !== undefined && remainingPercent > lastSample.pct + 0.5
|
|
466
|
+
|
|
467
|
+
if (resetChanged || increased) {
|
|
468
|
+
samples = []
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
samples.push({ at: now, pct: remainingPercent, resetAt: currReset })
|
|
461
472
|
while (samples.length > 20) samples.shift()
|
|
462
473
|
quotaSamples.set(ref, samples)
|
|
463
474
|
snap.samples = [...samples]
|
|
@@ -483,12 +494,14 @@ export function createAccountStore({ credentials, getConfig, fetchImpl, fetchFor
|
|
|
483
494
|
saveQuarantinesToFile(quarantines)
|
|
484
495
|
if (health.has(ref)) health.set(ref, null)
|
|
485
496
|
},
|
|
486
|
-
rememberQuarantine(ref, reason, until, attempts
|
|
497
|
+
rememberQuarantine(ref, reason, until, attempts) {
|
|
487
498
|
if (!ref) return
|
|
499
|
+
const existing = quarantines.get(ref)
|
|
500
|
+
const finalAttempts = attempts !== undefined ? Number(attempts) : (existing?.attempts ? existing.attempts + 1 : 1)
|
|
488
501
|
quarantines.set(ref, {
|
|
489
|
-
reason: reason || 'RATE_LIMIT',
|
|
490
|
-
until: Number(until) || (Date.now() + 3600000),
|
|
491
|
-
attempts: Number(
|
|
502
|
+
reason: reason || existing?.reason || 'RATE_LIMIT',
|
|
503
|
+
until: Number(until) || existing?.until || (Date.now() + 3600000),
|
|
504
|
+
attempts: Number(finalAttempts) || 1,
|
|
492
505
|
status: STATUS_QUARANTINE,
|
|
493
506
|
})
|
|
494
507
|
saveQuarantinesToFile(quarantines)
|
|
@@ -510,12 +523,14 @@ export function createAccountStore({ credentials, getConfig, fetchImpl, fetchFor
|
|
|
510
523
|
return { success: false, reason: 'NOT_EXPIRED' }
|
|
511
524
|
}
|
|
512
525
|
quarantines.set(ref, enterProbing(acc))
|
|
526
|
+
saveQuarantinesToFile(quarantines)
|
|
513
527
|
try {
|
|
514
528
|
if (typeof probeFn === 'function') {
|
|
515
529
|
await probeFn(ref)
|
|
516
530
|
}
|
|
517
531
|
resolveWarmupSuccess(acc)
|
|
518
532
|
quarantines.delete(ref)
|
|
533
|
+
saveQuarantinesToFile(quarantines)
|
|
519
534
|
return { success: true }
|
|
520
535
|
} catch (err) {
|
|
521
536
|
const updated = resolveWarmupFailure(acc, err?.message || q.reason)
|
|
@@ -525,6 +540,7 @@ export function createAccountStore({ credentials, getConfig, fetchImpl, fetchFor
|
|
|
525
540
|
attempts: updated.quarantineAttempts,
|
|
526
541
|
status: updated.status,
|
|
527
542
|
})
|
|
543
|
+
saveQuarantinesToFile(quarantines)
|
|
528
544
|
return { success: false, account: updated, error: err }
|
|
529
545
|
}
|
|
530
546
|
},
|
package/lib/adapter-manager.js
CHANGED
|
@@ -44,7 +44,7 @@ export function createAdapterManager({
|
|
|
44
44
|
autoPacing: () => live().autoPacing,
|
|
45
45
|
switchAtRemaining: () => live().switchAtRemaining,
|
|
46
46
|
rememberCooldown: (ref, until, families) => store.rememberCooldown(ref, until, families),
|
|
47
|
-
rememberQuarantine: (ref, reason, until) => store.rememberQuarantine(ref, reason, until),
|
|
47
|
+
rememberQuarantine: (ref, reason, until, attempts) => store.rememberQuarantine(ref, reason, until, attempts),
|
|
48
48
|
getQuarantine: (ref) => store.getQuarantine(ref),
|
|
49
49
|
probeWarmup: (ref, fn) => store.probeWarmup(ref, fn),
|
|
50
50
|
rememberQuota: (ref, snap) => store.rememberQuota(ref, snap),
|
package/lib/adapter.js
CHANGED
|
@@ -133,7 +133,12 @@ export class SubscriptionAdapter extends LlmAdapter {
|
|
|
133
133
|
onCooldown: (account) => {
|
|
134
134
|
if (typeof deps.rememberCooldown === 'function') deps.rememberCooldown(account.ref, account.cooldownUntil, account.cooldownFamilies || null)
|
|
135
135
|
if (typeof deps.rememberQuarantine === 'function') {
|
|
136
|
-
deps.
|
|
136
|
+
const currentQ = typeof deps.getQuarantine === 'function' ? deps.getQuarantine(account.ref) : null
|
|
137
|
+
if (currentQ && currentQ.attempts && currentQ.attempts > 1) {
|
|
138
|
+
deps.rememberQuarantine(account.ref, currentQ.reason, currentQ.until, currentQ.attempts)
|
|
139
|
+
} else {
|
|
140
|
+
deps.rememberQuarantine(account.ref, account.quarantineReason || 'RATE_LIMIT', account.quarantineUntil || account.cooldownUntil, account.quarantineAttempts || 1)
|
|
141
|
+
}
|
|
137
142
|
}
|
|
138
143
|
if (typeof deps.recordSwitch === 'function') deps.recordSwitch(account.ref)
|
|
139
144
|
},
|
|
@@ -267,15 +272,21 @@ export class SubscriptionAdapter extends LlmAdapter {
|
|
|
267
272
|
if (typeof deps.probeWarmup === "function") {
|
|
268
273
|
const qUntil = Number(account.quarantineUntil || 0)
|
|
269
274
|
if (qUntil > 0 && qUntil <= Date.now()) {
|
|
275
|
+
const probeFetch = typeof deps.fetchForRef === 'function'
|
|
276
|
+
? deps.fetchForRef(account.ref)
|
|
277
|
+
: (typeof deps.fetchFor === 'function' ? deps.fetchFor(account.ref) : (deps.fetchImpl || fetch))
|
|
278
|
+
const probeCfg = typeof deps.vendorConfig === 'function' ? (deps.vendorConfig(provider) || {}) : {}
|
|
270
279
|
const probeResult = await deps.probeWarmup(account.ref, async () => {
|
|
271
280
|
if (vendor && typeof vendor.check === "function") {
|
|
272
281
|
const probeBlob = await deps.ensureFresh(provider, await deps.loadBlob(account.ref), account.ref)
|
|
273
|
-
await vendor.check(probeBlob,
|
|
282
|
+
await vendor.check(probeBlob, probeCfg, probeFetch)
|
|
274
283
|
}
|
|
275
284
|
})
|
|
276
285
|
if (probeResult && !probeResult.success) {
|
|
277
286
|
const err = probeResult.error || new Error("warmup probe failed")
|
|
278
287
|
err.code = "QUOTA"
|
|
288
|
+
err.isWarmupProbeFailure = true
|
|
289
|
+
err.warmupAccount = probeResult.account
|
|
279
290
|
throw err
|
|
280
291
|
}
|
|
281
292
|
}
|
package/lib/blob.js
CHANGED
|
@@ -2,6 +2,33 @@ function asString(value) {
|
|
|
2
2
|
return value == null ? "" : String(value)
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
+
export function normalizeExpiresAt(raw) {
|
|
6
|
+
if (raw == null || raw === "") return 0
|
|
7
|
+
if (typeof raw === "number") {
|
|
8
|
+
if (!Number.isFinite(raw) || raw <= 0) return 0
|
|
9
|
+
return raw < 1e11 ? Math.floor(raw * 1000) : Math.floor(raw)
|
|
10
|
+
}
|
|
11
|
+
if (typeof raw === "string") {
|
|
12
|
+
const trimmed = raw.trim()
|
|
13
|
+
if (!trimmed) return 0
|
|
14
|
+
if (/^\d+(\.\d+)?$/.test(trimmed)) {
|
|
15
|
+
const num = Number(trimmed)
|
|
16
|
+
if (!Number.isFinite(num) || num <= 0) return 0
|
|
17
|
+
return num < 1e11 ? Math.floor(num * 1000) : Math.floor(num)
|
|
18
|
+
}
|
|
19
|
+
const parsed = Date.parse(trimmed)
|
|
20
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
21
|
+
return parsed
|
|
22
|
+
}
|
|
23
|
+
return 0
|
|
24
|
+
}
|
|
25
|
+
if (raw instanceof Date) {
|
|
26
|
+
const t = raw.getTime()
|
|
27
|
+
return Number.isFinite(t) && t > 0 ? t : 0
|
|
28
|
+
}
|
|
29
|
+
return 0
|
|
30
|
+
}
|
|
31
|
+
|
|
5
32
|
export function serializeBlob(obj) {
|
|
6
33
|
if (!obj || typeof obj !== "object") throw new Error("invalid oauth blob")
|
|
7
34
|
const accessToken = asString(obj.accessToken)
|
|
@@ -13,11 +40,14 @@ export function serializeBlob(obj) {
|
|
|
13
40
|
...obj,
|
|
14
41
|
accessToken,
|
|
15
42
|
refreshToken,
|
|
16
|
-
expiresAt:
|
|
43
|
+
expiresAt: normalizeExpiresAt(obj.expiresAt),
|
|
17
44
|
label: asString(obj.label),
|
|
18
45
|
email: asString(obj.email),
|
|
19
46
|
accountId: asString(obj.accountId),
|
|
20
47
|
projectId: asString(obj.projectId),
|
|
48
|
+
...(obj.clientId ? { clientId: asString(obj.clientId) } : {}),
|
|
49
|
+
...(obj.clientSecret ? { clientSecret: asString(obj.clientSecret) } : {}),
|
|
50
|
+
...(obj.idToken ? { idToken: asString(obj.idToken) } : {}),
|
|
21
51
|
...(Array.isArray(obj.usage) ? { usage: obj.usage } : {}),
|
|
22
52
|
...(obj.usageAt ? { usageAt: Number(obj.usageAt) } : {}),
|
|
23
53
|
})
|
|
@@ -30,11 +60,14 @@ export function parseBlob(text) {
|
|
|
30
60
|
...obj,
|
|
31
61
|
accessToken: asString(obj.accessToken),
|
|
32
62
|
refreshToken: asString(obj.refreshToken),
|
|
33
|
-
expiresAt:
|
|
63
|
+
expiresAt: normalizeExpiresAt(obj.expiresAt),
|
|
34
64
|
label: asString(obj.label),
|
|
35
65
|
email: asString(obj.email),
|
|
36
66
|
accountId: asString(obj.accountId),
|
|
37
67
|
projectId: asString(obj.projectId),
|
|
68
|
+
...(obj.clientId ? { clientId: asString(obj.clientId) } : {}),
|
|
69
|
+
...(obj.clientSecret ? { clientSecret: asString(obj.clientSecret) } : {}),
|
|
70
|
+
...(obj.idToken ? { idToken: asString(obj.idToken) } : {}),
|
|
38
71
|
...(Array.isArray(obj.usage) ? { usage: obj.usage } : {}),
|
|
39
72
|
...(obj.usageAt ? { usageAt: Number(obj.usageAt) } : {}),
|
|
40
73
|
}
|
package/lib/client.js
CHANGED
|
@@ -2362,44 +2362,68 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
2362
2362
|
return { label: (p.charAt(0).toUpperCase() || 'P'), cls: 'dsub-brandCodex', icon: '●' }
|
|
2363
2363
|
}
|
|
2364
2364
|
|
|
2365
|
-
|
|
2365
|
+
const PLUGIN_LABELS = new Set(['plugins', 'плагины', '插件', 'plugin'])
|
|
2366
|
+
function matchesPluginLabel(el) {
|
|
2367
|
+
if (!el) return false
|
|
2368
|
+
const txt = (el.textContent || '').trim().toLowerCase()
|
|
2369
|
+
const aria = (el.getAttribute('aria-label') || '').trim().toLowerCase()
|
|
2370
|
+
const title = (el.getAttribute('title') || '').trim().toLowerCase()
|
|
2371
|
+
const dataTab = (el.getAttribute('data-tab') || '').trim().toLowerCase()
|
|
2372
|
+
return PLUGIN_LABELS.has(txt) || PLUGIN_LABELS.has(aria) || PLUGIN_LABELS.has(title) || PLUGIN_LABELS.has(dataTab)
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
function findAndClickCard(doc) {
|
|
2376
|
+
for (const key of [PKG, 'dsh-subscriptions', '@goodandready/dsh-subscriptions']) {
|
|
2377
|
+
const card = doc.querySelector(`[data-plugin-package="${key}"]`) || doc.querySelector(`[data-plugin-item="${ROW_ID}"]`)
|
|
2378
|
+
if (card) {
|
|
2379
|
+
const open = [...card.querySelectorAll('button')].filter((b) => b.getAttribute('role') !== 'switch')
|
|
2380
|
+
const target = open.length >= 1 ? open[0] : card
|
|
2381
|
+
if (typeof target.click === 'function') {
|
|
2382
|
+
target.click()
|
|
2383
|
+
return true
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
return false
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2366
2390
|
function openPluginSettings(doc = document, schedule = (run, ms) => window.setTimeout(run, ms)) {
|
|
2367
2391
|
try {
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2392
|
+
if (findAndClickCard(doc)) return
|
|
2393
|
+
|
|
2394
|
+
const buttons = [...doc.querySelectorAll('button, a, div[role="button"], div[role="tab"]')]
|
|
2395
|
+
const panel = buttons.find(matchesPluginLabel)
|
|
2371
2396
|
if (panel) {
|
|
2372
2397
|
panel.click()
|
|
2373
2398
|
const poll = (left) => {
|
|
2374
|
-
|
|
2375
|
-
for (const key of [PKG, 'dsh-subscriptions', '@goodandready/dsh-subscriptions']) {
|
|
2376
|
-
const card = doc.querySelector(`[data-plugin-package="${key}"]`) || doc.querySelector(`[data-plugin-item="${ROW_ID}"]`)
|
|
2377
|
-
if (card) {
|
|
2378
|
-
const open = [...card.querySelectorAll('button')].filter((b) => b.getAttribute('role') !== 'switch')
|
|
2379
|
-
const target = open.length >= 1 ? open[0] : card
|
|
2380
|
-
if (typeof target.click === 'function') {
|
|
2381
|
-
target.click()
|
|
2382
|
-
return
|
|
2383
|
-
}
|
|
2384
|
-
}
|
|
2385
|
-
}
|
|
2386
|
-
} catch {}
|
|
2399
|
+
if (findAndClickCard(doc)) return
|
|
2387
2400
|
if (left > 0) schedule(() => poll(left - 1), 100)
|
|
2388
2401
|
}
|
|
2389
|
-
schedule(() => poll(40),
|
|
2402
|
+
schedule(() => poll(40), 100)
|
|
2390
2403
|
return
|
|
2391
2404
|
}
|
|
2405
|
+
|
|
2392
2406
|
const triggers = buttons.filter((b) => b.getAttribute('aria-haspopup') === 'dialog' && b.hasAttribute('aria-expanded'))
|
|
2393
2407
|
if (triggers.length > 0) {
|
|
2394
2408
|
const trigger = triggers.find((b) => b.getAttribute('aria-expanded') !== 'true') || triggers[0]
|
|
2395
2409
|
if (trigger && typeof trigger.click === 'function') {
|
|
2396
2410
|
trigger.click()
|
|
2397
|
-
|
|
2411
|
+
const pollSection = (left) => {
|
|
2398
2412
|
try {
|
|
2399
|
-
const section = [...doc.querySelectorAll('button')].find(
|
|
2400
|
-
if (section && typeof section.click === 'function')
|
|
2413
|
+
const section = [...doc.querySelectorAll('button, a, div[role="tab"], [role="button"]')].find(matchesPluginLabel)
|
|
2414
|
+
if (section && typeof section.click === 'function') {
|
|
2415
|
+
section.click()
|
|
2416
|
+
const poll = (cLeft) => {
|
|
2417
|
+
if (findAndClickCard(doc)) return
|
|
2418
|
+
if (cLeft > 0) schedule(() => poll(cLeft - 1), 100)
|
|
2419
|
+
}
|
|
2420
|
+
schedule(() => poll(40), 100)
|
|
2421
|
+
return
|
|
2422
|
+
}
|
|
2401
2423
|
} catch {}
|
|
2402
|
-
|
|
2424
|
+
if (left > 0) schedule(() => pollSection(left - 1), 80)
|
|
2425
|
+
}
|
|
2426
|
+
schedule(() => pollSection(20), 80)
|
|
2403
2427
|
}
|
|
2404
2428
|
}
|
|
2405
2429
|
} catch {}
|
package/lib/forecast.js
CHANGED
|
@@ -73,8 +73,22 @@ export function estimateForecast(state, key, remainingPercent, resetsAt, now) {
|
|
|
73
73
|
// #352: Burn-rate calculation & Pacing risk estimator
|
|
74
74
|
export function calculateBurnRatePerHour(samples, now = Date.now()) {
|
|
75
75
|
if (!Array.isArray(samples) || samples.length < 2) return 0
|
|
76
|
-
|
|
76
|
+
let recent = samples.filter((s) => s.at >= now - 6 * HOUR_MS)
|
|
77
77
|
if (recent.length < 2) return 0
|
|
78
|
+
|
|
79
|
+
let windowStartIdx = 0
|
|
80
|
+
for (let i = 1; i < recent.length; i++) {
|
|
81
|
+
const prev = recent[i - 1]
|
|
82
|
+
const curr = recent[i]
|
|
83
|
+
const resetChanged = prev.resetAt != null && curr.resetAt != null && Math.abs(Number(prev.resetAt) - Number(curr.resetAt)) > 300
|
|
84
|
+
const increased = curr.pct > prev.pct + 0.5
|
|
85
|
+
if (resetChanged || increased) {
|
|
86
|
+
windowStartIdx = i
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
recent = recent.slice(windowStartIdx)
|
|
90
|
+
if (recent.length < 2) return 0
|
|
91
|
+
|
|
78
92
|
const first = recent[0]
|
|
79
93
|
const last = recent[recent.length - 1]
|
|
80
94
|
const hours = (last.at - first.at) / HOUR_MS
|
package/lib/import-auth.js
CHANGED
|
@@ -2,6 +2,8 @@ import { readFile } from 'node:fs/promises'
|
|
|
2
2
|
import { homedir } from 'node:os'
|
|
3
3
|
import { join } from 'node:path'
|
|
4
4
|
import { readClaudeCredentials } from './vendors/claude-cli.js'
|
|
5
|
+
import { normalizeExpiresAt } from './blob.js'
|
|
6
|
+
import { isGoogleClientId, extractGoogleClientIdFromJwt, emailFromToken } from './jwt.js'
|
|
5
7
|
|
|
6
8
|
// Test seam: a fixture root overrides the real home for the duration of a
|
|
7
9
|
// public call. Undefined (the default) keeps production behaviour.
|
|
@@ -67,17 +69,20 @@ async function discoverLocalCliSessionsInner() {
|
|
|
67
69
|
// 3. Antigravity / Gemini CLI
|
|
68
70
|
const agyPaths = [
|
|
69
71
|
homeFile('.gemini', 'antigravity-cli', 'antigravity-oauth-token'),
|
|
72
|
+
homeFile('.gemini', 'oauth_creds.json'),
|
|
70
73
|
homeFile('.cli-proxy-api', 'antigravity.json'),
|
|
71
74
|
]
|
|
72
75
|
for (const p of agyPaths) {
|
|
73
76
|
const agy = await readJson(p)
|
|
74
77
|
const tok = agy && agy.token ? agy.token : agy
|
|
75
78
|
if (tok && (tok.access_token || tok.accessToken)) {
|
|
79
|
+
const idToken = agy?.id_token || agy?.idToken || tok?.id_token || tok?.idToken || ''
|
|
80
|
+
const email = agy?.email || tok?.account || (idToken ? emailFromToken(idToken) : '') || ''
|
|
76
81
|
detected.antigravity = {
|
|
77
82
|
provider: 'antigravity',
|
|
78
83
|
source: 'file',
|
|
79
84
|
path: p,
|
|
80
|
-
email:
|
|
85
|
+
email: email || 'Antigravity User',
|
|
81
86
|
hasRefreshToken: !!(tok.refresh_token || tok.refreshToken),
|
|
82
87
|
}
|
|
83
88
|
break
|
|
@@ -331,12 +336,35 @@ export async function loadLocalCliBlob(provider, options = {}) {
|
|
|
331
336
|
|
|
332
337
|
if (canonical === 'antigravity') {
|
|
333
338
|
const tok = raw.token || raw
|
|
339
|
+
const idToken = raw.id_token || raw.idToken || tok.id_token || tok.idToken || ''
|
|
340
|
+
|
|
341
|
+
const directClientId = raw.client_id || raw.clientId || tok.client_id || tok.clientId ||
|
|
342
|
+
raw.installed?.client_id || raw.web?.client_id || ''
|
|
343
|
+
let clientId = ''
|
|
344
|
+
if (typeof directClientId === 'string' && isGoogleClientId(directClientId)) {
|
|
345
|
+
clientId = directClientId.trim()
|
|
346
|
+
} else if (idToken) {
|
|
347
|
+
clientId = extractGoogleClientIdFromJwt(idToken)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const directSecret = raw.client_secret || raw.clientSecret || tok.client_secret || tok.clientSecret ||
|
|
351
|
+
raw.installed?.client_secret || raw.web?.client_secret || ''
|
|
352
|
+
const clientSecret = typeof directSecret === 'string' ? directSecret.trim() : ''
|
|
353
|
+
|
|
354
|
+
const rawExpiry = tok.expiry ?? tok.expires_at ?? tok.expiresAt ?? tok.expiry_date ?? raw.expiry_date ?? raw.expiry ?? (tok.expires_in ? Date.now() + Number(tok.expires_in) * 1000 : 0)
|
|
355
|
+
const expiresAt = normalizeExpiresAt(rawExpiry)
|
|
356
|
+
|
|
357
|
+
const email = raw.email || tok.account || (idToken ? emailFromToken(idToken) : '') || ''
|
|
358
|
+
|
|
334
359
|
return {
|
|
335
360
|
accessToken: tok.access_token || tok.accessToken || '',
|
|
336
361
|
refreshToken: tok.refresh_token || tok.refreshToken || '',
|
|
337
|
-
expiresAt
|
|
338
|
-
email
|
|
362
|
+
expiresAt,
|
|
363
|
+
email,
|
|
339
364
|
projectId: raw.project_id || raw.projectId || tok.project_id || '',
|
|
365
|
+
...(clientId ? { clientId } : {}),
|
|
366
|
+
...(clientSecret ? { clientSecret } : {}),
|
|
367
|
+
...(idToken ? { idToken } : {}),
|
|
340
368
|
}
|
|
341
369
|
}
|
|
342
370
|
|
package/lib/index.js
CHANGED
|
@@ -316,7 +316,7 @@ export function apply(ctx, rawConfig) {
|
|
|
316
316
|
cooldownMs: () => live().cooldownMs,
|
|
317
317
|
switchAtRemaining: () => live().switchAtRemaining,
|
|
318
318
|
rememberCooldown: (ref, until, families) => store.rememberCooldown(ref, until, families),
|
|
319
|
-
rememberQuarantine: (ref, reason, until) => store.rememberQuarantine(ref, reason, until),
|
|
319
|
+
rememberQuarantine: (ref, reason, until, attempts) => store.rememberQuarantine(ref, reason, until, attempts),
|
|
320
320
|
recordSuccess: (ref) => store.recordSuccess(ref),
|
|
321
321
|
getHealth: (ref) => store.getHealth(ref),
|
|
322
322
|
recordSwitch: (ref) => store.recordSwitch(ref),
|
package/lib/jwt.js
CHANGED
|
@@ -26,3 +26,36 @@ export function emailFromToken(token) {
|
|
|
26
26
|
if (!payload) return ''
|
|
27
27
|
return String(payload.email || payload.preferred_username || '')
|
|
28
28
|
}
|
|
29
|
+
|
|
30
|
+
export function isGoogleClientId(str) {
|
|
31
|
+
if (typeof str !== 'string') return false
|
|
32
|
+
const trimmed = str.trim()
|
|
33
|
+
return /^[0-9]+-[a-zA-Z0-9_-]+\.apps\.googleusercontent\.com$/.test(trimmed)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function extractGoogleClientIdFromJwt(token) {
|
|
37
|
+
const payload = decodeJwtPayload(token)
|
|
38
|
+
if (!payload || typeof payload !== 'object') return ''
|
|
39
|
+
|
|
40
|
+
const azp = typeof payload.azp === 'string' ? payload.azp.trim() : ''
|
|
41
|
+
const rawAud = payload.aud
|
|
42
|
+
|
|
43
|
+
const audiences = Array.isArray(rawAud)
|
|
44
|
+
? rawAud.filter(a => typeof a === 'string').map(a => a.trim())
|
|
45
|
+
: (typeof rawAud === 'string' && rawAud.trim() ? [rawAud.trim()] : [])
|
|
46
|
+
|
|
47
|
+
if (azp) {
|
|
48
|
+
if (!isGoogleClientId(azp)) return ''
|
|
49
|
+
if (audiences.length > 0 && !audiences.includes(azp)) {
|
|
50
|
+
return ''
|
|
51
|
+
}
|
|
52
|
+
return azp
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const validAuds = audiences.filter(isGoogleClientId)
|
|
56
|
+
if (validAuds.length === 1 && (audiences.length === 1 || audiences.every(a => a === validAuds[0]))) {
|
|
57
|
+
return validAuds[0]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return ''
|
|
61
|
+
}
|
package/lib/stream-rotate.js
CHANGED
|
@@ -100,9 +100,17 @@ export async function* streamWithRotation({
|
|
|
100
100
|
? REASON_HARD_LIMIT
|
|
101
101
|
: REASON_RATE_LIMIT
|
|
102
102
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
if (err && err.isWarmupProbeFailure && err.warmupAccount) {
|
|
104
|
+
account.quarantineUntil = err.warmupAccount.quarantineUntil
|
|
105
|
+
account.quarantineReason = err.warmupAccount.quarantineReason
|
|
106
|
+
account.quarantineAttempts = err.warmupAccount.quarantineAttempts
|
|
107
|
+
account.status = err.warmupAccount.status
|
|
108
|
+
} else {
|
|
109
|
+
const quarantined = putInQuarantine(account, reason, nowMs())
|
|
110
|
+
account.quarantineUntil = quarantined.quarantineUntil
|
|
111
|
+
account.quarantineReason = quarantined.quarantineReason
|
|
112
|
+
account.quarantineAttempts = (account.quarantineAttempts || 0) + 1
|
|
113
|
+
}
|
|
106
114
|
|
|
107
115
|
if (reason === REASON_REVOKED) {
|
|
108
116
|
try {
|
|
@@ -109,15 +109,25 @@ export async function exchangeCode(cfg, pkce, code, fetchImpl) {
|
|
|
109
109
|
redirect_uri: cfg.redirectUri,
|
|
110
110
|
code_verifier: pkce.verifier,
|
|
111
111
|
}, fetchImpl)
|
|
112
|
-
const blob = tokenBlobFromOAuth(json
|
|
112
|
+
const blob = tokenBlobFromOAuth(json, {
|
|
113
|
+
clientId: cfg.clientId,
|
|
114
|
+
...(cfg.clientSecret ? { clientSecret: cfg.clientSecret } : {}),
|
|
115
|
+
})
|
|
113
116
|
const labeled = { ...blob, email: blob.email || emailFromToken(blob.accessToken), label: blob.label || 'Antigravity' }
|
|
114
117
|
return withProject(labeled, fetchImpl)
|
|
115
118
|
}
|
|
116
119
|
|
|
117
120
|
export async function refresh(cfg, blob, fetchImpl) {
|
|
121
|
+
const clientId = (cfg?.clientId && String(cfg.clientId).trim()) || (blob?.clientId && String(blob.clientId).trim()) || ''
|
|
122
|
+
const clientSecret = (cfg?.clientSecret && String(cfg.clientSecret).trim()) || (blob?.clientSecret && String(blob.clientSecret).trim()) || ''
|
|
123
|
+
|
|
124
|
+
if (!clientId) {
|
|
125
|
+
throw new Error('Google OAuth refresh requires a Client ID. Configure antigravityClientId in plugin settings or re-import credentials with valid OAuth client metadata.')
|
|
126
|
+
}
|
|
127
|
+
|
|
118
128
|
const json = await formTokenRequest(TOKEN, {
|
|
119
|
-
client_id:
|
|
120
|
-
client_secret:
|
|
129
|
+
client_id: clientId,
|
|
130
|
+
...(clientSecret ? { client_secret: clientSecret } : {}),
|
|
121
131
|
grant_type: 'refresh_token',
|
|
122
132
|
refresh_token: blob.refreshToken,
|
|
123
133
|
}, fetchImpl)
|
|
@@ -128,6 +138,8 @@ export async function refresh(cfg, blob, fetchImpl) {
|
|
|
128
138
|
paidTierId: blob.paidTierId,
|
|
129
139
|
paidTierName: blob.paidTierName,
|
|
130
140
|
sessionId: blob.sessionId,
|
|
141
|
+
clientId,
|
|
142
|
+
...(clientSecret ? { clientSecret } : {}),
|
|
131
143
|
})
|
|
132
144
|
}
|
|
133
145
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-subscriptions",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.52",
|
|
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",
|