@goodandready/dsh-subscriptions 0.7.31 → 0.7.32
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/account-reliability.js +68 -0
- package/lib/accounts-view.js +2 -0
- package/lib/accounts.js +28 -0
- package/lib/index.js +3 -3
- package/package.json +1 -1
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
function text(value) {
|
|
2
|
+
return value == null ? '' : String(value).trim()
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* #827: decide whether a stored credential is usable without exposing it.
|
|
7
|
+
* Empty access/refresh tokens and non-finite expiry are treated as corrupt.
|
|
8
|
+
*/
|
|
9
|
+
export function credentialState(blob) {
|
|
10
|
+
if (!blob || typeof blob !== 'object') return { usable: false, reason: 'invalid_blob' }
|
|
11
|
+
const accessToken = text(blob.accessToken)
|
|
12
|
+
const refreshToken = text(blob.refreshToken)
|
|
13
|
+
if (!accessToken && !refreshToken && !text(blob.apiKey)) {
|
|
14
|
+
return { usable: false, reason: 'empty_credentials' }
|
|
15
|
+
}
|
|
16
|
+
if (blob.expiresAt != null && blob.expiresAt !== '' && !Number.isFinite(Number(blob.expiresAt))) {
|
|
17
|
+
return { usable: false, reason: 'invalid_expiry' }
|
|
18
|
+
}
|
|
19
|
+
return { usable: true, reason: null }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* #831: stable provider identity used for duplicate detection. Unknown
|
|
24
|
+
* identities return null and must never be merged automatically.
|
|
25
|
+
*/
|
|
26
|
+
export function accountIdentityKey(provider, blob) {
|
|
27
|
+
if (!blob || typeof blob !== 'object') return null
|
|
28
|
+
const id = text(blob.email || blob.login || blob.accountId || blob.userId)
|
|
29
|
+
if (!id) return null
|
|
30
|
+
return `${text(provider).toLowerCase()}:${id.toLowerCase()}`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* #818: aliases are display-only. Resolve either a stable ref or a legacy
|
|
35
|
+
* label within one provider back to the stable credential ref.
|
|
36
|
+
*/
|
|
37
|
+
export function resolveAccountRef(slots, provider, value) {
|
|
38
|
+
const wanted = text(value)
|
|
39
|
+
if (!wanted) return null
|
|
40
|
+
const list = (Array.isArray(slots) ? slots : []).filter((slot) => slot && slot.provider === provider)
|
|
41
|
+
const byIndex = Number(wanted)
|
|
42
|
+
if (Number.isInteger(byIndex) && byIndex > 0) {
|
|
43
|
+
return list.find((slot) => Number(slot.index) === byIndex)?.ref || null
|
|
44
|
+
}
|
|
45
|
+
return list.find((slot) => slot.ref === wanted || text(slot.label) === wanted)?.ref || null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* #806: a delayed refresh may only write when its credential lineage is
|
|
50
|
+
* still current. A newer save/revoke bumps the version.
|
|
51
|
+
*/
|
|
52
|
+
export function refreshIsCurrent(currentVersion, expectedVersion) {
|
|
53
|
+
return Number(currentVersion) === Number(expectedVersion)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* #833: deterministic host-env vs plugin-proxy precedence.
|
|
58
|
+
* @param {{ hostProxy?: string, pluginProxy?: string, prefer?: 'host' | 'plugin' }} [options]
|
|
59
|
+
*/
|
|
60
|
+
export function proxyPrecedence(options = {}) {
|
|
61
|
+
const prefer = options.prefer || 'host'
|
|
62
|
+
const host = text(options.hostProxy)
|
|
63
|
+
const plugin = text(options.pluginProxy)
|
|
64
|
+
if (host && plugin && prefer === 'plugin') return { proxy: plugin, source: 'plugin', conflict: true }
|
|
65
|
+
if (host) return { proxy: host, source: 'host', conflict: Boolean(plugin) }
|
|
66
|
+
if (plugin) return { proxy: plugin, source: 'plugin', conflict: false }
|
|
67
|
+
return { proxy: '', source: 'none', conflict: false }
|
|
68
|
+
}
|
package/lib/accounts-view.js
CHANGED
|
@@ -169,6 +169,8 @@ export async function createAccountsView({ live, store, fetch, pmL, pmE, concurr
|
|
|
169
169
|
poolEnabled: slot.poolEnabled !== false,
|
|
170
170
|
isDefault: !!slot.isDefault,
|
|
171
171
|
...(Array.isArray(slot.poolModels) ? { poolModels: slot.poolModels } : {}),
|
|
172
|
+
credentialError: info.credentialError || null,
|
|
173
|
+
duplicateOf: info.duplicateOf || null,
|
|
172
174
|
capabilities,
|
|
173
175
|
billing,
|
|
174
176
|
renewsAt: billing.renewsAt,
|
package/lib/accounts.js
CHANGED
|
@@ -46,6 +46,7 @@ import { credentialRef } from '@deepseek-ai/dsh-credentials'
|
|
|
46
46
|
import { oauthRef, isProvider } from './refs.js'
|
|
47
47
|
import { parseBlob, serializeBlob } from './blob.js'
|
|
48
48
|
import { encryptWithPassphrase, decryptWithPassphrase } from './crypto.js'
|
|
49
|
+
import { accountIdentityKey, credentialState } from './account-reliability.js'
|
|
49
50
|
import { recordSwitch, recordExhaust, recordBroken, computeHealthScore, healthBadge } from './health.js'
|
|
50
51
|
import { executeWithRetry } from './backoff.js'
|
|
51
52
|
import { getVendor } from './vendors/index.js'
|
|
@@ -308,6 +309,26 @@ export function createAccountStore({ credentials, getConfig, fetchImpl, fetchFor
|
|
|
308
309
|
if (base.configured) {
|
|
309
310
|
try {
|
|
310
311
|
const blob = parseBlob(await resolveRaw(ref))
|
|
312
|
+
const state = credentialState(blob)
|
|
313
|
+
if (!state.usable) {
|
|
314
|
+
base.configured = false
|
|
315
|
+
base.credentialError = state.reason
|
|
316
|
+
base.label = blob.label || blob.email || ''
|
|
317
|
+
return {
|
|
318
|
+
...base,
|
|
319
|
+
cooldownUntil: 0,
|
|
320
|
+
quarantineUntil: 0,
|
|
321
|
+
cooldownFamilies: null,
|
|
322
|
+
cooldownModels: null,
|
|
323
|
+
quota: null,
|
|
324
|
+
isRevoked: false,
|
|
325
|
+
authWarning: '',
|
|
326
|
+
validationUrl: '',
|
|
327
|
+
validationMessage: '',
|
|
328
|
+
accountNotice: '',
|
|
329
|
+
paidTierName: '',
|
|
330
|
+
}
|
|
331
|
+
}
|
|
311
332
|
base.label = blob.label || blob.email || ''
|
|
312
333
|
base.email = blob.email || ''
|
|
313
334
|
base.plan = blob.plan || blob.paidTierName || ''
|
|
@@ -367,6 +388,7 @@ export function createAccountStore({ credentials, getConfig, fetchImpl, fetchFor
|
|
|
367
388
|
latencyGrade: pings.get(ref)?.latencyGrade ?? null,
|
|
368
389
|
lastDoctorVerdict: doctorVerdicts.get(ref) || null,
|
|
369
390
|
availableModels: dynamicModels.get(ref)?.models || null,
|
|
391
|
+
credentialError: base.credentialError || null,
|
|
370
392
|
}
|
|
371
393
|
}
|
|
372
394
|
|
|
@@ -374,8 +396,12 @@ export function createAccountStore({ credentials, getConfig, fetchImpl, fetchFor
|
|
|
374
396
|
const curConfig = getConfig()
|
|
375
397
|
const slots = normalizeSlots(curConfig.slots).filter((s) => s.provider === provider)
|
|
376
398
|
const out = []
|
|
399
|
+
const identities = new Map()
|
|
377
400
|
for (const slot of slots) {
|
|
378
401
|
const info = await describeRef(slot.ref, curConfig)
|
|
402
|
+
const identity = accountIdentityKey(provider, { email: info.email, accountId: info.accountId })
|
|
403
|
+
const duplicateOf = identity ? (identities.get(identity) || null) : null
|
|
404
|
+
if (identity && !duplicateOf) identities.set(identity, slot.ref)
|
|
379
405
|
const q = quarantines.get(slot.ref)
|
|
380
406
|
out.push({
|
|
381
407
|
ref: slot.ref,
|
|
@@ -400,6 +426,8 @@ export function createAccountStore({ credentials, getConfig, fetchImpl, fetchFor
|
|
|
400
426
|
poolEnabled: slot.poolEnabled !== false,
|
|
401
427
|
isDefault: !!slot.isDefault,
|
|
402
428
|
...(Array.isArray(slot.poolModels) ? { poolModels: slot.poolModels } : {}),
|
|
429
|
+
credentialError: info.credentialError || null,
|
|
430
|
+
duplicateOf,
|
|
403
431
|
})
|
|
404
432
|
}
|
|
405
433
|
return out
|
package/lib/index.js
CHANGED
|
@@ -18,6 +18,7 @@ import { createSubscriptionsService } from './subscriptions.js'
|
|
|
18
18
|
import { registerSubscriptionTools } from './native-tools.js'
|
|
19
19
|
import { detectConflicts, readProfilePluginNames, setActiveConflicts } from './coexistence.js'
|
|
20
20
|
import { createAccountStore, defaultAccountRef, normalizeSlots, vendorConfig } from './accounts.js'
|
|
21
|
+
import { resolveAccountRef } from './account-reliability.js'
|
|
21
22
|
import { OllamaAdapter, ollamaAlive, ollamaModels, ollamaBase } from './ollama.js'
|
|
22
23
|
import { createResetCreditService } from './reset-credits.js'
|
|
23
24
|
import { maskEmail, maskLabel } from './mask.js'
|
|
@@ -276,9 +277,8 @@ export function apply(ctx, rawConfig) {
|
|
|
276
277
|
|
|
277
278
|
// #85: host-only reset credit service for Codex accounts.
|
|
278
279
|
const resetCredits = createResetCreditService({ loadBlob: (ref) => store.loadBlob(ref) })
|
|
279
|
-
function refForSlot(provider,
|
|
280
|
-
|
|
281
|
-
return slot ? slot.ref : null
|
|
280
|
+
function refForSlot(provider, indexOrAlias) {
|
|
281
|
+
return resolveAccountRef(normalizeSlots(live().slots), provider, indexOrAlias)
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
function stripLegacySlots(slots) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-subscriptions",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.32",
|
|
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",
|