@goodandready/dsh-subscriptions 0.6.33 → 0.6.35
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/adapter-manager.js +2 -2
- package/lib/config-schema.js +45 -36
- package/lib/index.js +85 -49
- package/lib/routes/accounts.js +3 -3
- package/lib/routes/status.js +2 -2
- package/lib/subscriptions.js +3 -0
- package/package.json +6 -6
package/lib/adapter-manager.js
CHANGED
|
@@ -4,7 +4,7 @@ import { normalizeSlots, vendorConfig } from './accounts.js'
|
|
|
4
4
|
import { PROVIDERS, isProvider, oauthRef } from './refs.js'
|
|
5
5
|
import { credentialRef } from '@deepseek-ai/dsh-credentials'
|
|
6
6
|
import { getVendor } from './vendors/index.js'
|
|
7
|
-
import { Config } from './config-schema.js'
|
|
7
|
+
import { Config, plainConfig } from './config-schema.js'
|
|
8
8
|
|
|
9
9
|
export function createAdapterManager({
|
|
10
10
|
ctx,
|
|
@@ -187,7 +187,7 @@ export function createAdapterManager({
|
|
|
187
187
|
}
|
|
188
188
|
const currentSlots = Array.isArray(live().slots) ? live().slots : []
|
|
189
189
|
const newSlots = [...currentSlots, ...additions]
|
|
190
|
-
const parsed = Config({ ...live(), slots: stripLegacySlots(newSlots) })
|
|
190
|
+
const parsed = plainConfig(Config({ ...live(), slots: stripLegacySlots(newSlots) }))
|
|
191
191
|
await api.replace(parsed)
|
|
192
192
|
syncCustomVendors()
|
|
193
193
|
await syncAdapter()
|
package/lib/config-schema.js
CHANGED
|
@@ -16,68 +16,77 @@ export const Slot = z.object({
|
|
|
16
16
|
|
|
17
17
|
export const defaultSlots = PROVIDERS.map((provider) => ({ provider, index: 1, label: '' }))
|
|
18
18
|
|
|
19
|
+
// Unwrap Volatile boxes before any caller reads a value. A volatile field holds a box
|
|
20
|
+
// rather than its value, and the Loader mutates those boxes in place.
|
|
21
|
+
export function plainConfig(value) {
|
|
22
|
+
if (value === null || typeof value !== 'object') return value
|
|
23
|
+
if (Array.isArray(value)) return value.map(plainConfig)
|
|
24
|
+
if (typeof value.get === 'function') return plainConfig(value.get())
|
|
25
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, plainConfig(v)]))
|
|
26
|
+
}
|
|
27
|
+
|
|
19
28
|
export const Config = z.object({
|
|
20
|
-
cooldownMs: z.number().default(30 * 60 * 1000)
|
|
29
|
+
cooldownMs: z.number().volatile().default(30 * 60 * 1000)
|
|
21
30
|
.description('After RATE_LIMIT/QUOTA/429, skip that account for this many milliseconds.'),
|
|
22
|
-
switchAtRemaining: z.number().default(0.01)
|
|
31
|
+
switchAtRemaining: z.number().volatile().default(0.01)
|
|
23
32
|
.description('If remaining <= this (absolute or <1 fraction), treat as exhausted before request. 0 disables.'),
|
|
24
|
-
refreshAheadMs: z.number().default(5 * 60 * 1000)
|
|
33
|
+
refreshAheadMs: z.number().volatile().default(5 * 60 * 1000)
|
|
25
34
|
.description('Background refresh when expiry within this many ms.'),
|
|
26
|
-
refreshRetryMs: z.number().default(10 * 60 * 1000)
|
|
35
|
+
refreshRetryMs: z.number().volatile().default(10 * 60 * 1000)
|
|
27
36
|
.description('Do not retry background refresh more often than this after failure.'),
|
|
28
|
-
probeIntervalMin: z.number().default(15)
|
|
37
|
+
probeIntervalMin: z.number().volatile().default(15)
|
|
29
38
|
.description('Background health-check interval in minutes. 0 disables.'),
|
|
30
|
-
notifyLimits: z.boolean().default(true)
|
|
39
|
+
notifyLimits: z.boolean().volatile().default(true)
|
|
31
40
|
.description('Emit log notices when usage crosses 70/90/100% of a window.'),
|
|
32
|
-
expiryNotifyDays: z.number().default(7)
|
|
41
|
+
expiryNotifyDays: z.number().volatile().default(7)
|
|
33
42
|
.description('#67 Warn in the header chip this many days before a subscription expiry (expiresAt). 0 disables.'),
|
|
34
|
-
privacyMask: z.boolean().default(false)
|
|
43
|
+
privacyMask: z.boolean().volatile().default(false)
|
|
35
44
|
.description('#98 Hide personal data in the UI: emails show as j***n@example.com.'),
|
|
36
|
-
slots: z.array(Slot).default(defaultSlots)
|
|
45
|
+
slots: z.array(Slot).volatile().default(defaultSlots)
|
|
37
46
|
.description('Account slots. Secrets are not stored here; only the credential ref names.'),
|
|
38
|
-
useWebCallback: z.boolean().default(false)
|
|
47
|
+
useWebCallback: z.boolean().volatile().default(false)
|
|
39
48
|
.description('When on, redirect_uri is this Web UI origin + /dsh-subscriptions/oauth/callback. When off, the vendor CLI registered redirect is used and you paste the redirected URL.'),
|
|
40
|
-
autoLoopback: z.boolean().default(true)
|
|
49
|
+
autoLoopback: z.boolean().volatile().default(true)
|
|
41
50
|
.description('#89 When on and the vendor redirect_uri is a loopback address (codex :1455, grok :56121), a temporary local server catches the OAuth callback automatically - no paste needed. Paste fallback stays available.'),
|
|
42
|
-
ollamaBaseUrl: z.string().default('http://127.0.0.1:11434')
|
|
51
|
+
ollamaBaseUrl: z.string().volatile().default('http://127.0.0.1:11434')
|
|
43
52
|
.description('#91 Local Ollama base URL. Served as the ollama provider in the native model picker when reachable.'),
|
|
44
|
-
ollamaFallback: z.boolean().default(true)
|
|
53
|
+
ollamaFallback: z.boolean().volatile().default(true)
|
|
45
54
|
.description('#91 When every account of a provider is exhausted, continue the chat on local Ollama instead of failing.'),
|
|
46
|
-
ollamaFallbackModel: z.string().default('')
|
|
55
|
+
ollamaFallbackModel: z.string().volatile().default('')
|
|
47
56
|
.description('#91 Ollama model used for the fallback (for example qwen2.5-coder). Empty = first model from /api/tags.'),
|
|
48
|
-
hideDeprecatedModels: z.boolean().default(false)
|
|
57
|
+
hideDeprecatedModels: z.boolean().volatile().default(false)
|
|
49
58
|
.description('#94 Hide test/preview/beta/legacy model ids from the native model picker.'),
|
|
50
|
-
codexClientId: z.string().default(''),
|
|
51
|
-
codexVerbosity: z.string().default('')
|
|
59
|
+
codexClientId: z.string().volatile().default(''),
|
|
60
|
+
codexVerbosity: z.string().volatile().default('')
|
|
52
61
|
.description('#93 Response verbosity for Codex reasoning models: low, medium or high. Empty = protocol default.'),
|
|
53
|
-
codexFastMode: z.boolean().default(false)
|
|
62
|
+
codexFastMode: z.boolean().volatile().default(false)
|
|
54
63
|
.description('#92 Fast Mode for Codex: sends service_tier priority (1.5x speed billing tier) with every request.'),
|
|
55
|
-
composerQuota: z.string().default('off')
|
|
64
|
+
composerQuota: z.string().volatile().default('off')
|
|
56
65
|
.description('#84 Composer quota indicator mode: off, percent, bar or forecast (predictive runway from a sliding window).'),
|
|
57
|
-
codexRedirectUri: z.string().default(''),
|
|
58
|
-
codexBaseUrl: z.string().default(''),
|
|
59
|
-
codexClientVersion: z.string().default('')
|
|
66
|
+
codexRedirectUri: z.string().volatile().default(''),
|
|
67
|
+
codexBaseUrl: z.string().volatile().default(''),
|
|
68
|
+
codexClientVersion: z.string().volatile().default('')
|
|
60
69
|
.description('Codex CLI identity version for /models catalog gating (e.g. 0.157.1). Empty uses the built-in default.'),
|
|
61
|
-
claudeClientId: z.string().default(''),
|
|
62
|
-
claudeRedirectUri: z.string().default(''),
|
|
63
|
-
grokClientId: z.string().default(''),
|
|
64
|
-
grokRedirectUri: z.string().default(''),
|
|
65
|
-
grokBaseUrl: z.string().default(''),
|
|
66
|
-
grokClientVersion: z.string().default('')
|
|
70
|
+
claudeClientId: z.string().volatile().default(''),
|
|
71
|
+
claudeRedirectUri: z.string().volatile().default(''),
|
|
72
|
+
grokClientId: z.string().volatile().default(''),
|
|
73
|
+
grokRedirectUri: z.string().volatile().default(''),
|
|
74
|
+
grokBaseUrl: z.string().volatile().default(''),
|
|
75
|
+
grokClientVersion: z.string().volatile().default('')
|
|
67
76
|
.description('Grok CLI identity version header. Empty uses the built-in default.'),
|
|
68
|
-
antigravityClientId: z.string().default(''),
|
|
69
|
-
antigravityRedirectUri: z.string().default(''),
|
|
70
|
-
cascadingFallback: z.boolean().default(false)
|
|
77
|
+
antigravityClientId: z.string().volatile().default(''),
|
|
78
|
+
antigravityRedirectUri: z.string().volatile().default(''),
|
|
79
|
+
cascadingFallback: z.boolean().volatile().default(false)
|
|
71
80
|
.description('#349 Opt-in cross-vendor fallback when all accounts of a provider are exhausted or rate limited.'),
|
|
72
|
-
cascadingChain: z.any().default({})
|
|
81
|
+
cascadingChain: z.any().volatile().default({})
|
|
73
82
|
.description('#349 Custom cross-vendor fallback chains (e.g. { claude: ["copilot", "cursor"] }).'),
|
|
74
|
-
webhookUrl: z.string().default('')
|
|
83
|
+
webhookUrl: z.string().volatile().default('')
|
|
75
84
|
.description('#351 External webhook URL to receive quota and session alerts (JSON POST).'),
|
|
76
|
-
alertThresholds: z.array(z.number()).default([80, 90, 95])
|
|
85
|
+
alertThresholds: z.array(z.number()).volatile().default([80, 90, 95])
|
|
77
86
|
.description('#351 Quota usage percentage thresholds for firing alerts.'),
|
|
78
|
-
autoPacing: z.boolean().default(true)
|
|
87
|
+
autoPacing: z.boolean().volatile().default(true)
|
|
79
88
|
.description('#352 Predictive burn-rate load balancing between accounts.'),
|
|
80
|
-
customVendors: z.array(z.any()).default([])
|
|
89
|
+
customVendors: z.array(z.any()).volatile().default([])
|
|
81
90
|
.description('Declarative OpenAI-Responses-compatible providers. See README.'),
|
|
82
91
|
})
|
|
83
92
|
|
package/lib/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { plainConfig } from './config-schema.js'
|
|
2
|
+
export { plainConfig } from './config-schema.js'
|
|
1
3
|
import { readFileSync } from 'node:fs'
|
|
2
4
|
import { bestEffort } from './utils.js'
|
|
3
5
|
export { Config, publicConfig, Slot, defaultSlots } from './config-schema.js'
|
|
@@ -66,9 +68,14 @@ const OK_HTML = `<!doctype html>
|
|
|
66
68
|
|
|
67
69
|
export function apply(ctx, rawConfig) {
|
|
68
70
|
const logger = typeof ctx.logger === 'function' ? ctx.logger('subscriptions') : (ctx.logger || undefined)
|
|
69
|
-
let settingsApi
|
|
70
71
|
|
|
71
|
-
|
|
72
|
+
// rawConfig still holds Volatile boxes, so unwrap before validating.
|
|
73
|
+
const resolveEntry = () => {
|
|
74
|
+
// rawConfig still holds Volatile boxes, so unwrap before validating; Config then
|
|
75
|
+
// re-creates boxes for the volatile fields, so unwrap the result as well.
|
|
76
|
+
return plainConfig(Config(plainConfig(rawConfig || {})))
|
|
77
|
+
}
|
|
78
|
+
let cachedConfig = resolveEntry()
|
|
72
79
|
const live = () => cachedConfig
|
|
73
80
|
|
|
74
81
|
function syncCustomVendors() {
|
|
@@ -95,65 +102,93 @@ export function apply(ctx, rawConfig) {
|
|
|
95
102
|
|
|
96
103
|
const syncSnapshot = (next) => {
|
|
97
104
|
try {
|
|
98
|
-
const fresh = next !== undefined ? next : (
|
|
99
|
-
cachedConfig =
|
|
105
|
+
const fresh = next !== undefined ? next : resolveEntry()
|
|
106
|
+
cachedConfig = fresh
|
|
100
107
|
syncCustomVendors()
|
|
101
108
|
syncAdapter().catch(() => {})
|
|
102
109
|
syncOllama().catch(() => {})
|
|
103
110
|
} catch { /* keep existing snapshot on error */ }
|
|
104
111
|
}
|
|
105
112
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
get: () => (typeof scope.get === 'function' ? scope.get() : cachedConfig),
|
|
112
|
-
async replace(next) {
|
|
113
|
-
syncSnapshot(next)
|
|
114
|
-
if (typeof scope.replace === 'function') {
|
|
115
|
-
return await scope.replace(next)
|
|
116
|
-
}
|
|
117
|
-
},
|
|
118
|
-
}
|
|
113
|
+
// settings.register was removed before DSH 0.1.7-rc.2, so the scope this plugin used
|
|
114
|
+
// to build from it never existed: getSettingsApi() returned null and writes only
|
|
115
|
+
// touched memory. Back it with the real settings service instead, whose
|
|
116
|
+
// describe()/update()/replace() are the contract both releases share.
|
|
117
|
+
let settingsSvc = null
|
|
119
118
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
119
|
+
const describeRow = () => {
|
|
120
|
+
if (typeof settingsSvc?.describe !== 'function') return null
|
|
121
|
+
try {
|
|
122
|
+
return settingsSvc.describe().find((r) => r && r.ns === NS) || null
|
|
123
|
+
} catch (err) {
|
|
124
|
+
if (logger && logger.warn) logger.warn('[dsh-subscriptions] describe failed: ' + String(err && err.message || err))
|
|
125
|
+
return null
|
|
126
|
+
}
|
|
127
|
+
}
|
|
123
128
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
129
|
+
const settingsApi = {
|
|
130
|
+
get: () => live(),
|
|
131
|
+
replace: async (next) => {
|
|
132
|
+
const parsed = plainConfig(next)
|
|
133
|
+
cachedConfig = parsed
|
|
134
|
+
syncCustomVendors()
|
|
135
|
+
if (typeof settingsSvc?.update === 'function') {
|
|
136
|
+
await settingsSvc.update(NS, structuredClone(parsed), describeRow()?.revision)
|
|
137
|
+
} else if (typeof settingsSvc?.replace === 'function') {
|
|
138
|
+
await settingsSvc.replace(NS, structuredClone(parsed), describeRow()?.revision)
|
|
131
139
|
}
|
|
140
|
+
return parsed
|
|
141
|
+
},
|
|
142
|
+
}
|
|
143
|
+
// Consumers (routes, adapter manager) call getSettingsApi().replace(config). The host
|
|
144
|
+
// service offers describe/update/replace with a revision, so adapt it here: the adapter
|
|
145
|
+
// keeps the cached snapshot current and persists through the real service.
|
|
146
|
+
const getSettingsApi = () => (settingsSvc ? settingsApi : null)
|
|
132
147
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (!ns || ns === NS) {
|
|
141
|
-
syncSnapshot()
|
|
142
|
-
}
|
|
148
|
+
ctx.inject(['settings'], (sctx) => {
|
|
149
|
+
try {
|
|
150
|
+
settingsSvc = sctx.settings || (typeof sctx.get === 'function' ? sctx.get('settings') : null) || null
|
|
151
|
+
if (!settingsSvc) return
|
|
152
|
+
const served = describeRow()
|
|
153
|
+
if (served && served.value) {
|
|
154
|
+
try { cachedConfig = plainConfig(Config(plainConfig(served.value))) } catch { /* keep current */ }
|
|
143
155
|
}
|
|
144
|
-
if (typeof sctx.
|
|
145
|
-
sctx.
|
|
146
|
-
} else if (typeof ctx.on === 'function') {
|
|
147
|
-
ctx.on('settings/document-updated', onDocUpdated)
|
|
156
|
+
if (typeof sctx.effect === 'function') {
|
|
157
|
+
sctx.effect(() => () => { settingsSvc = null })
|
|
148
158
|
}
|
|
159
|
+
} catch (err) {
|
|
160
|
+
// The profile may be mounted without the settings service; the static config stays
|
|
161
|
+
// authoritative and the volatile-update handler below still applies later changes.
|
|
162
|
+
if (logger && logger.warn) logger.warn('[dsh-subscriptions] settings service unavailable: ' + String(err && err.message || err))
|
|
163
|
+
}
|
|
164
|
+
})
|
|
149
165
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
166
|
+
// The host announces configuration changes with settings/document-updated; the Loader
|
|
167
|
+
// also re-announces the volatile boxes in place. Refresh the cached snapshot on both.
|
|
168
|
+
const onSettingsChanged = () => {
|
|
169
|
+
try {
|
|
170
|
+
const row = describeRow()
|
|
171
|
+
if (row && row.value) cachedConfig = plainConfig(Config(plainConfig(row.value)))
|
|
172
|
+
else syncSnapshot()
|
|
173
|
+
reconcileSlots().catch(() => {})
|
|
174
|
+
} catch { /* keep existing snapshot on error */ }
|
|
156
175
|
}
|
|
176
|
+
if (typeof ctx.on === 'function') {
|
|
177
|
+
ctx.effect(() => {
|
|
178
|
+
ctx.on('settings/document-updated', onSettingsChanged)
|
|
179
|
+
ctx.on('loader/volatile-update', onSettingsChanged)
|
|
180
|
+
return () => {
|
|
181
|
+
ctx.off?.('settings/document-updated', onSettingsChanged)
|
|
182
|
+
ctx.off?.('loader/volatile-update', onSettingsChanged)
|
|
183
|
+
}
|
|
184
|
+
}, 'dsh-subscriptions: settings change listener')
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
Promise.resolve().then(() => {
|
|
188
|
+
reconcileSlots().catch((err) => {
|
|
189
|
+
if (logger && logger.warn) logger.warn('[dsh-subscriptions] reconcileSlots initial error: ' + (err && err.message || err))
|
|
190
|
+
})
|
|
191
|
+
})
|
|
157
192
|
|
|
158
193
|
const fetchForRef = (ref) => {
|
|
159
194
|
const parsed = parseOauthRef(ref)
|
|
@@ -250,7 +285,7 @@ export function apply(ctx, rawConfig) {
|
|
|
250
285
|
fetch,
|
|
251
286
|
fetchForRef,
|
|
252
287
|
recordHistory,
|
|
253
|
-
getSettingsApi
|
|
288
|
+
getSettingsApi,
|
|
254
289
|
syncCustomVendors,
|
|
255
290
|
syncOllama,
|
|
256
291
|
stripLegacySlots,
|
|
@@ -259,6 +294,7 @@ export function apply(ctx, rawConfig) {
|
|
|
259
294
|
adapterManager.registerAdapterLifecycle()
|
|
260
295
|
|
|
261
296
|
const subscriptions = createSubscriptionsService({
|
|
297
|
+
live,
|
|
262
298
|
listAccounts: (provider) => store.listAccounts(provider),
|
|
263
299
|
loadBlob: (ref) => store.loadBlob(ref),
|
|
264
300
|
ensureFresh: (provider, blob, ref) => store.ensureFresh(provider, blob, ref),
|
|
@@ -437,7 +473,7 @@ export function apply(ctx, rawConfig) {
|
|
|
437
473
|
NS,
|
|
438
474
|
live,
|
|
439
475
|
accountsView,
|
|
440
|
-
getSettingsApi
|
|
476
|
+
getSettingsApi,
|
|
441
477
|
syncCustomVendors,
|
|
442
478
|
syncAdapter,
|
|
443
479
|
stripLegacySlots,
|
package/lib/routes/accounts.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { exportVault, importVault } from '../vault.js'
|
|
2
2
|
import { normalizeSlots } from '../accounts.js'
|
|
3
|
-
import { Config, publicConfig } from '../config-schema.js'
|
|
3
|
+
import { Config, plainConfig, publicConfig } from '../config-schema.js'
|
|
4
4
|
import { decryptWithPassphrase, encryptWithPassphrase } from '../crypto.js'
|
|
5
5
|
import { isTrustedSettingsRequest, queryOf, readBody, safeJsonHandler, writeJson } from '../http.js'
|
|
6
6
|
import { discoverLocalCliSessions, loadLocalCliBlob } from '../import-auth.js'
|
|
@@ -159,7 +159,7 @@ export function registerAccountsRoutes(ctx, state) {
|
|
|
159
159
|
index: idx,
|
|
160
160
|
label: blob.email || '',
|
|
161
161
|
}])
|
|
162
|
-
const parsed = Config({ ...live(), slots: stripLegacySlots(nextSlots) })
|
|
162
|
+
const parsed = plainConfig(Config({ ...live(), slots: stripLegacySlots(nextSlots) }))
|
|
163
163
|
await getSettingsApi().replace(parsed)
|
|
164
164
|
syncCustomVendors()
|
|
165
165
|
}
|
|
@@ -424,7 +424,7 @@ export function registerAccountsRoutes(ctx, state) {
|
|
|
424
424
|
curSlots.push(s)
|
|
425
425
|
}
|
|
426
426
|
}
|
|
427
|
-
parsed = Config({ ...live(), slots: stripLegacySlots(curSlots) })
|
|
427
|
+
parsed = plainConfig(Config({ ...live(), slots: stripLegacySlots(curSlots) }))
|
|
428
428
|
}
|
|
429
429
|
let blobCount = 0
|
|
430
430
|
for (const [ref, blob] of Object.entries(result.blobs)) {
|
package/lib/routes/status.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getRecentAlerts, clearAlerts } from '../alerts.js'
|
|
2
2
|
import { normalizeSlots, vendorConfig } from '../accounts.js'
|
|
3
|
-
import { Config, publicConfig } from '../config-schema.js'
|
|
3
|
+
import { Config, plainConfig, publicConfig } from '../config-schema.js'
|
|
4
4
|
import { isTrustedSettingsRequest, queryOf, readBody, safeJsonHandler, writeJson } from '../http.js'
|
|
5
5
|
import { quotaSnapshot } from '../ratelimit.js'
|
|
6
6
|
import { PROVIDERS, displayName, droppedCredentialRefs, isProvider, oauthRef } from '../refs.js'
|
|
@@ -80,7 +80,7 @@ export function registerStatusRoutes(ctx, state) {
|
|
|
80
80
|
if (payload && typeof payload.config === 'object') payload = payload.config
|
|
81
81
|
try {
|
|
82
82
|
if (Array.isArray(payload.slots)) payload.slots = stripLegacySlots(payload.slots)
|
|
83
|
-
const parsed = Config(payload)
|
|
83
|
+
const parsed = plainConfig(Config(payload))
|
|
84
84
|
const dropped = droppedCredentialRefs(live().slots, parsed.slots)
|
|
85
85
|
await getSettingsApi().replace(parsed)
|
|
86
86
|
currentRevision++
|
package/lib/subscriptions.js
CHANGED
|
@@ -58,6 +58,9 @@ function headersFor(provider, blob, cfg) {
|
|
|
58
58
|
|
|
59
59
|
export function createSubscriptionsService(deps) {
|
|
60
60
|
return {
|
|
61
|
+
// The resolved config snapshot. Cached: identical between loader/volatile-update
|
|
62
|
+
// events, which is the guarantee issue #367 calls for.
|
|
63
|
+
live: () => deps.live(),
|
|
61
64
|
async available(provider) {
|
|
62
65
|
if (provider) {
|
|
63
66
|
if (!isProvider(provider)) return false
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-subscriptions",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.35",
|
|
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",
|
|
@@ -54,11 +54,11 @@
|
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
57
|
-
"@deepseek-ai/dsh-credentials": "^0.1.0-rc.
|
|
58
|
-
"@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.
|
|
59
|
-
"@deepseek-ai/dsh-llm": "^0.1.0-rc.
|
|
60
|
-
"@deepseek-ai/dsh-settings": "^0.1.0-rc.
|
|
61
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
57
|
+
"@deepseek-ai/dsh-credentials": "^0.1.7-rc.2 || ^0.2.0-rc.1",
|
|
58
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.7-rc.2 || ^0.2.0-rc.1",
|
|
59
|
+
"@deepseek-ai/dsh-llm": "^0.1.7-rc.2 || ^0.2.0-rc.1",
|
|
60
|
+
"@deepseek-ai/dsh-settings": "^0.1.7-rc.2 || ^0.2.0-rc.1",
|
|
61
|
+
"@deepseek-ai/schemastery": "^3.18.4"
|
|
62
62
|
},
|
|
63
63
|
"publishConfig": {
|
|
64
64
|
"access": "public"
|