@goodandready/dsh-subscriptions 0.6.22 → 0.6.23
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-view.js +84 -0
- package/lib/accounts.js +5 -2
- package/lib/adapter-manager.js +300 -0
- package/lib/adapter.js +7 -7
- package/lib/alerts.js +2 -2
- package/lib/backoff.js +1 -1
- package/lib/client.js +89 -75
- package/lib/code-assist.js +1 -1
- package/lib/config-schema.js +46 -6
- package/lib/diagnostics.js +60 -0
- package/lib/history.js +21 -1
- package/lib/http.js +58 -11
- package/lib/index.js +143 -543
- package/lib/mask.js +1 -1
- package/lib/ollama.js +4 -4
- package/lib/routes/accounts.js +5 -1
- package/lib/routes/proxy.js +1 -1
- package/lib/routes/status.js +42 -7
- package/lib/routes.js +0 -1
- package/lib/session-pin.js +0 -8
- package/lib/subscriptions.js +5 -6
- package/lib/vault.js +2 -2
- package/lib/vendor-factory.js +0 -1
- package/lib/vendors/antigravity.js +1 -1
- package/lib/vendors/codex.js +1 -1
- package/lib/vendors/grok.js +1 -1
- package/lib/vendors/index.js +0 -1
- package/lib/vendors/qwen.js +1 -1
- package/lib/vendors/spark.js +1 -1
- package/package.json +3 -1
package/lib/mask.js
CHANGED
|
@@ -30,7 +30,7 @@ export function cleanErrorMessage(raw) {
|
|
|
30
30
|
if (parsed.error && parsed.error.message) text = parsed.error.message
|
|
31
31
|
else if (parsed.message) text = parsed.message
|
|
32
32
|
else if (parsed.code) text = parsed.code
|
|
33
|
-
} catch
|
|
33
|
+
} catch { /* ignore JSON parse failure */ }
|
|
34
34
|
}
|
|
35
35
|
const firstLine = text.split('\n')[0].trim()
|
|
36
36
|
if (firstLine.length > 160) {
|
package/lib/ollama.js
CHANGED
|
@@ -37,19 +37,19 @@ export class OllamaAdapter extends LlmAdapter {
|
|
|
37
37
|
return { id: provider, name: 'Ollama (local)' }
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
providerRetryPolicy(
|
|
40
|
+
providerRetryPolicy() {
|
|
41
41
|
return undefined
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
async listModels(
|
|
44
|
+
async listModels() {
|
|
45
45
|
return ollamaModels(this.deps.baseUrl(), this.deps.fetchImpl || fetch)
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
imageRequestPricing(
|
|
48
|
+
imageRequestPricing() {
|
|
49
49
|
return undefined
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
async resolveModel(provider, model
|
|
52
|
+
async resolveModel(provider, model) {
|
|
53
53
|
return { provider, id: model, name: model }
|
|
54
54
|
}
|
|
55
55
|
|
package/lib/routes/accounts.js
CHANGED
|
@@ -111,6 +111,10 @@ export function registerAccountsRoutes(ctx, state) {
|
|
|
111
111
|
writeJson(res, 405, { ok: false, error: { code: 'method', message: 'GET only' } })
|
|
112
112
|
return
|
|
113
113
|
}
|
|
114
|
+
if (!isTrustedSettingsRequest(req)) {
|
|
115
|
+
writeJson(res, 403, { ok: false, error: { code: 'forbidden', message: 'same-origin only' } })
|
|
116
|
+
return
|
|
117
|
+
}
|
|
114
118
|
try {
|
|
115
119
|
const detected = await discoverLocalCliSessions()
|
|
116
120
|
writeJson(res, 200, { ok: true, detected })
|
|
@@ -250,7 +254,7 @@ export function registerAccountsRoutes(ctx, state) {
|
|
|
250
254
|
let bundle
|
|
251
255
|
try {
|
|
252
256
|
bundle = JSON.parse(decryptWithPassphrase(encrypted, passphrase))
|
|
253
|
-
} catch
|
|
257
|
+
} catch {
|
|
254
258
|
writeJson(res, 400, { ok: false, error: { code: 'decrypt', message: 'wrong passphrase or corrupted bundle' } })
|
|
255
259
|
return
|
|
256
260
|
}
|
package/lib/routes/proxy.js
CHANGED
|
@@ -21,7 +21,7 @@ export function registerProxyRoutes(ctx, state) {
|
|
|
21
21
|
}
|
|
22
22
|
const raw = await readBody(req, 128 * 1024).catch(() => Buffer.alloc(0))
|
|
23
23
|
let body = {}
|
|
24
|
-
try { body = JSON.parse(raw.toString('utf8') || '{}') } catch
|
|
24
|
+
try { body = JSON.parse(raw.toString('utf8') || '{}') } catch { body = {} }
|
|
25
25
|
const events = Array.isArray(body && body.events) ? body.events : []
|
|
26
26
|
const analysis = analyzeSessionEvents(events)
|
|
27
27
|
writeJson(res, 200, { ok: true, analysis })
|
package/lib/routes/status.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getRecentAlerts } from '../alerts.js'
|
|
1
|
+
import { getRecentAlerts, clearAlerts } from '../alerts.js'
|
|
2
2
|
import { normalizeSlots, vendorConfig } from '../accounts.js'
|
|
3
3
|
import { Config, publicConfig } from '../config-schema.js'
|
|
4
4
|
import { isTrustedSettingsRequest, queryOf, readBody, safeJsonHandler, writeJson } from '../http.js'
|
|
@@ -22,9 +22,12 @@ export function registerStatusRoutes(ctx, state) {
|
|
|
22
22
|
fetchForRef,
|
|
23
23
|
} = state
|
|
24
24
|
|
|
25
|
+
let currentRevision = 1
|
|
26
|
+
|
|
25
27
|
async function configResponse() {
|
|
26
28
|
return {
|
|
27
29
|
ok: true,
|
|
30
|
+
revision: currentRevision,
|
|
28
31
|
config: publicConfig(live()),
|
|
29
32
|
accounts: await accountsView(),
|
|
30
33
|
providers: PROVIDERS.map((id) => ({ id, name: displayName(id) })),
|
|
@@ -36,6 +39,10 @@ export function registerStatusRoutes(ctx, state) {
|
|
|
36
39
|
path: '/dsh-subscriptions/config',
|
|
37
40
|
handler: safeJsonHandler(async (req, res) => {
|
|
38
41
|
if (req.method === 'GET') {
|
|
42
|
+
if (!isTrustedSettingsRequest(req)) {
|
|
43
|
+
writeJson(res, 403, { ok: false, error: { code: 'forbidden', message: 'same-origin only' } })
|
|
44
|
+
return
|
|
45
|
+
}
|
|
39
46
|
writeJson(res, 200, await configResponse())
|
|
40
47
|
return
|
|
41
48
|
}
|
|
@@ -56,12 +63,27 @@ export function registerStatusRoutes(ctx, state) {
|
|
|
56
63
|
writeJson(res, 400, { ok: false, error: { code: 'json', message: 'invalid json' } })
|
|
57
64
|
return
|
|
58
65
|
}
|
|
66
|
+
// #379: revision conflict detection (CAS)
|
|
67
|
+
const baseRev = payload?.revision != null ? Number(payload.revision) : (req.headers?.['if-match'] ? Number(req.headers['if-match']) : null)
|
|
68
|
+
if (baseRev != null && !Number.isNaN(baseRev) && baseRev !== currentRevision) {
|
|
69
|
+
writeJson(res, 409, {
|
|
70
|
+
ok: false,
|
|
71
|
+
error: {
|
|
72
|
+
code: 'conflict',
|
|
73
|
+
message: 'configuration has been modified concurrently',
|
|
74
|
+
currentRevision,
|
|
75
|
+
},
|
|
76
|
+
})
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
|
|
59
80
|
if (payload && typeof payload.config === 'object') payload = payload.config
|
|
60
81
|
try {
|
|
61
82
|
if (Array.isArray(payload.slots)) payload.slots = stripLegacySlots(payload.slots)
|
|
62
83
|
const parsed = Config(payload)
|
|
63
84
|
const dropped = droppedCredentialRefs(live().slots, parsed.slots)
|
|
64
85
|
await getSettingsApi().replace(parsed)
|
|
86
|
+
currentRevision++
|
|
65
87
|
syncCustomVendors()
|
|
66
88
|
for (const ref of dropped) await store.clearRef(ref)
|
|
67
89
|
await syncAdapter()
|
|
@@ -77,8 +99,17 @@ export function registerStatusRoutes(ctx, state) {
|
|
|
77
99
|
kind: 'exact',
|
|
78
100
|
path: '/dsh-subscriptions/status',
|
|
79
101
|
handler: safeJsonHandler(async (req, res) => {
|
|
102
|
+
if (req.method === 'DELETE') {
|
|
103
|
+
if (!isTrustedSettingsRequest(req)) {
|
|
104
|
+
writeJson(res, 403, { ok: false, error: { code: 'forbidden', message: 'same-origin only' } })
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
clearAlerts()
|
|
108
|
+
writeJson(res, 200, { ok: true })
|
|
109
|
+
return
|
|
110
|
+
}
|
|
80
111
|
if (req.method !== 'GET') {
|
|
81
|
-
writeJson(res, 405, { ok: false, error: { code: 'method', message: 'GET only' } })
|
|
112
|
+
writeJson(res, 405, { ok: false, error: { code: 'method', message: 'GET or DELETE only' } })
|
|
82
113
|
return
|
|
83
114
|
}
|
|
84
115
|
if (!isTrustedSettingsRequest(req)) {
|
|
@@ -101,7 +132,7 @@ export function registerStatusRoutes(ctx, state) {
|
|
|
101
132
|
expires[slot.provider] = Math.max(expires[slot.provider] || 0, slot.expiresAt)
|
|
102
133
|
labels[slot.provider] = pmL(slot.label || info.label || slot.provider)
|
|
103
134
|
}
|
|
104
|
-
} catch
|
|
135
|
+
} catch { /* ignore status inspection error */ }
|
|
105
136
|
}
|
|
106
137
|
// #69/#72: active subscription = last successful request (newest first).
|
|
107
138
|
let active = null
|
|
@@ -124,7 +155,7 @@ export function registerStatusRoutes(ctx, state) {
|
|
|
124
155
|
.filter((w) => w && typeof w.usedPercent === 'number')
|
|
125
156
|
.map((w) => ({ id: w.id || w.en || w.ru, label: w.en || w.ru || w.id, usedPercent: w.usedPercent }))
|
|
126
157
|
}
|
|
127
|
-
} catch
|
|
158
|
+
} catch { /* ignore status inspection error */ }
|
|
128
159
|
active = {
|
|
129
160
|
provider: lastRow.provider,
|
|
130
161
|
index,
|
|
@@ -160,6 +191,10 @@ export function registerStatusRoutes(ctx, state) {
|
|
|
160
191
|
writeJson(res, 405, { ok: false, error: { code: 'method', message: 'GET only' } })
|
|
161
192
|
return
|
|
162
193
|
}
|
|
194
|
+
if (!isTrustedSettingsRequest(req)) {
|
|
195
|
+
writeJson(res, 403, { ok: false, error: { code: 'forbidden', message: 'same-origin only' } })
|
|
196
|
+
return
|
|
197
|
+
}
|
|
163
198
|
writeJson(res, 200, { ok: true, report: await diagnosticsReport() })
|
|
164
199
|
}),
|
|
165
200
|
}), 'dsh-subscriptions: /diagnostics')
|
|
@@ -224,7 +259,7 @@ export function registerStatusRoutes(ctx, state) {
|
|
|
224
259
|
try {
|
|
225
260
|
const snap = quotaSnapshot(provider, res2.headers, null, Date.now())
|
|
226
261
|
if (snap) { capturedQuota = snap; store.rememberQuota(ref, snap) }
|
|
227
|
-
} catch
|
|
262
|
+
} catch { /* ignore status inspection error */ }
|
|
228
263
|
return res2
|
|
229
264
|
}
|
|
230
265
|
try {
|
|
@@ -302,7 +337,7 @@ export function registerStatusRoutes(ctx, state) {
|
|
|
302
337
|
return
|
|
303
338
|
}
|
|
304
339
|
let payload = {}
|
|
305
|
-
try { payload = JSON.parse((await readBody(req, 64 * 1024)).toString('utf8') || '{}') } catch
|
|
340
|
+
try { payload = JSON.parse((await readBody(req, 64 * 1024)).toString('utf8') || '{}') } catch { /* ignore status inspection error */ }
|
|
306
341
|
|
|
307
342
|
let provider = payload.provider
|
|
308
343
|
let index = payload.index || 1
|
|
@@ -351,7 +386,7 @@ export function registerStatusRoutes(ctx, state) {
|
|
|
351
386
|
try {
|
|
352
387
|
const snap = quotaSnapshot(provider, res2.headers, null, Date.now())
|
|
353
388
|
if (snap) { capturedQuota = snap; store.rememberQuota(ref, snap) }
|
|
354
|
-
} catch
|
|
389
|
+
} catch { /* ignore status inspection error */ }
|
|
355
390
|
return res2
|
|
356
391
|
}
|
|
357
392
|
try {
|
package/lib/routes.js
CHANGED
package/lib/session-pin.js
CHANGED
|
@@ -41,11 +41,3 @@ export function getPinnedAccountRef(sessionId) {
|
|
|
41
41
|
}
|
|
42
42
|
return entry.accountRef
|
|
43
43
|
}
|
|
44
|
-
|
|
45
|
-
export function unpinSession(sessionId) {
|
|
46
|
-
if (sessionId) sessionPins.delete(sessionId)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function clearSessionPins() {
|
|
50
|
-
sessionPins.clear()
|
|
51
|
-
}
|
package/lib/subscriptions.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { isProvider } from "./refs.js"
|
|
2
2
|
import { pickAccount, markCooldown, isSwitchableError, modelFamily } from "./rotate.js"
|
|
3
3
|
import { quotaSnapshot } from "./ratelimit.js"
|
|
4
|
-
import { getVendor } from "./vendors/index.js"
|
|
5
4
|
import { pickFetch } from "./proxy.js"
|
|
6
5
|
|
|
7
6
|
// ponytail: allowlist per provider — add path to extend without touching request logic
|
|
@@ -20,7 +19,7 @@ export function isAllowed(provider, path) {
|
|
|
20
19
|
p = p.split("?")[0].split("#")[0]
|
|
21
20
|
try {
|
|
22
21
|
if (p.startsWith("http://") || p.startsWith("https://")) p = new URL(p).pathname
|
|
23
|
-
} catch
|
|
22
|
+
} catch { /* ignore subscriptions error */ }
|
|
24
23
|
if (!p.startsWith("/")) p = "/" + p
|
|
25
24
|
for (const allowed of list) {
|
|
26
25
|
const a = allowed.split("?")[0]
|
|
@@ -140,17 +139,17 @@ export function createSubscriptionsService(deps) {
|
|
|
140
139
|
try {
|
|
141
140
|
const snap = quotaSnapshot(provider, res.headers, null, Date.now())
|
|
142
141
|
if (snap && typeof deps.rememberQuota === "function") deps.rememberQuota(account.ref, snap)
|
|
143
|
-
} catch
|
|
142
|
+
} catch { /* ignore subscriptions error */ }
|
|
144
143
|
if (!res.ok && res.clone) {
|
|
145
144
|
try {
|
|
146
145
|
const txt = await res.clone().text()
|
|
147
146
|
let j = null
|
|
148
|
-
try { j = JSON.parse(txt) } catch
|
|
147
|
+
try { j = JSON.parse(txt) } catch { /* ignore subscriptions error */ }
|
|
149
148
|
if (j) {
|
|
150
149
|
const snap2 = quotaSnapshot(provider, res.headers, j, Date.now())
|
|
151
150
|
if (snap2 && typeof deps.rememberQuota === "function") deps.rememberQuota(account.ref, snap2)
|
|
152
151
|
}
|
|
153
|
-
} catch
|
|
152
|
+
} catch { /* ignore subscriptions error */ }
|
|
154
153
|
}
|
|
155
154
|
if (!res.ok) {
|
|
156
155
|
const txt = await res.text().catch(() => "")
|
|
@@ -174,7 +173,7 @@ export function createSubscriptionsService(deps) {
|
|
|
174
173
|
ms: Date.now() - t0,
|
|
175
174
|
kind: "request",
|
|
176
175
|
})
|
|
177
|
-
} catch
|
|
176
|
+
} catch { /* ignore subscriptions error */ }
|
|
178
177
|
}
|
|
179
178
|
return res
|
|
180
179
|
} catch (err) {
|
package/lib/vault.js
CHANGED
|
@@ -50,14 +50,14 @@ export function importVault(encryptedPayload, passphrase) {
|
|
|
50
50
|
let jsonStr
|
|
51
51
|
try {
|
|
52
52
|
jsonStr = decryptWithPassphrase(raw, pass)
|
|
53
|
-
} catch
|
|
53
|
+
} catch {
|
|
54
54
|
throw new Error('failed to decrypt vault: invalid passphrase or corrupted payload')
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
let data
|
|
58
58
|
try {
|
|
59
59
|
data = JSON.parse(jsonStr)
|
|
60
|
-
} catch
|
|
60
|
+
} catch {
|
|
61
61
|
throw new Error('malformed vault content: invalid JSON')
|
|
62
62
|
}
|
|
63
63
|
|
package/lib/vendor-factory.js
CHANGED
|
@@ -12,7 +12,6 @@ import { buildAuthorizeUrl } from "./oauth.js"
|
|
|
12
12
|
import { codexResponsesBody, modelCatalog } from "./messages.js"
|
|
13
13
|
import { formTokenRequest, jsonTokenRequest, codexResponsesStream, readJson, tokenBlobFromOAuth, httpError } from "./wire.js"
|
|
14
14
|
import { emailFromToken } from "./jwt.js"
|
|
15
|
-
import { asUsageSnapshot } from "./usage.js"
|
|
16
15
|
|
|
17
16
|
function defaultModels(models) {
|
|
18
17
|
return modelCatalog("custom", (models || []).map((row) => (typeof row === "string" ? { id: row, name: row } : row)))
|
|
@@ -152,7 +152,7 @@ export async function usage(blob, _cfg, fetchImpl) {
|
|
|
152
152
|
return retrieveQuotaPercent(fetchImpl || fetch, blob.accessToken, headersFor(blob.projectId || ''))
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
export async function* streamOnce({ blob, options, fetchImpl, headers,
|
|
155
|
+
export async function* streamOnce({ blob, options, fetchImpl, headers, signal, saveBlob }) {
|
|
156
156
|
// Abort must cover the project pre-flight as well as the stream.
|
|
157
157
|
const preflight = (u, i) => fetchImpl(u, { ...(i || {}), signal })
|
|
158
158
|
const ready = await withProject(blob, preflight, saveBlob)
|
package/lib/vendors/codex.js
CHANGED
|
@@ -174,7 +174,7 @@ export async function listModels(blob, cfg, fetchImpl) {
|
|
|
174
174
|
}
|
|
175
175
|
if (!rows.length) throw new Error('empty codex catalog')
|
|
176
176
|
rows.sort((a, b) => a.priority - b.priority)
|
|
177
|
-
return modelCatalog(id, rows.map(({
|
|
177
|
+
return modelCatalog(id, rows.map((row) => { const copy = { ...row }; delete copy.priority; return copy }))
|
|
178
178
|
} catch { /* catalog fallback */ }
|
|
179
179
|
return catalog
|
|
180
180
|
}
|
package/lib/vendors/grok.js
CHANGED
|
@@ -220,7 +220,7 @@ export async function usage(blob, cfg, fetchImpl) {
|
|
|
220
220
|
const decoded = decodeGrokCreditsFrame(Buffer.from(buf))
|
|
221
221
|
if (decoded != null) pct = decoded
|
|
222
222
|
}
|
|
223
|
-
} catch
|
|
223
|
+
} catch { /* ignore grok usage capture error */ }
|
|
224
224
|
}
|
|
225
225
|
const snap = asUsageSnapshot(pct)
|
|
226
226
|
if (snap) snap.windows = usageWindows(json)
|
package/lib/vendors/index.js
CHANGED
package/lib/vendors/qwen.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { LlmError } from '@deepseek-ai/dsh-llm'
|
|
2
2
|
import { openaiMessages, openaiTools } from '../messages.js'
|
|
3
|
-
import { openaiChatStream,
|
|
3
|
+
import { openaiChatStream, httpError } from '../wire.js'
|
|
4
4
|
import { asUsageSnapshot } from '../usage.js'
|
|
5
5
|
|
|
6
6
|
export const id = 'qwen'
|
package/lib/vendors/spark.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { LlmError } from '@deepseek-ai/dsh-llm'
|
|
2
2
|
import { openaiMessages } from '../messages.js'
|
|
3
|
-
import { openaiChatStream,
|
|
3
|
+
import { openaiChatStream, httpError } from '../wire.js'
|
|
4
4
|
import { asUsageSnapshot } from '../usage.js'
|
|
5
5
|
|
|
6
6
|
export const id = 'spark'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-subscriptions",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.23",
|
|
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",
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
"lib/",
|
|
16
16
|
"cordis.patch.yml",
|
|
17
17
|
"README.md",
|
|
18
|
+
"README.ru.md",
|
|
19
|
+
"README.zh.md",
|
|
18
20
|
"LICENSE"
|
|
19
21
|
],
|
|
20
22
|
"keywords": [
|