@goodandready/dsh-subscriptions 0.6.41 → 0.6.43

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 CHANGED
@@ -1,5 +1,5 @@
1
1
  import { needsWarmupProbe, enterProbing, resolveWarmupSuccess, resolveWarmupFailure, STATUS_QUARANTINE, STATUS_PROBING } from "./quarantine.js"
2
- import { fetchWithTimeout } from "./http.js"
2
+ import { fetchWithTimeout, isTimeoutError } from "./http.js"
3
3
  import { credentialRef } from '@deepseek-ai/dsh-credentials'
4
4
  import { oauthRef, isProvider } from './refs.js'
5
5
  import { parseBlob, serializeBlob } from './blob.js'
@@ -75,7 +75,7 @@ export function createAccountStore({ credentials, getConfig, fetchImpl, fetchFor
75
75
  const doFetch = fetchImpl || fetch
76
76
  // #88: per-account proxy fetch for token refresh / usage (falls back to doFetch).
77
77
  const rawFetchFor = (ref) => (typeof fetchForRef === 'function' && fetchForRef(ref)) || doFetch
78
- const fetchFor = (ref) => (url, init, opts) => fetchWithTimeout(rawFetchFor(ref), url, init, { timeoutMs: 25000, ...(opts || {}) })
78
+ const fetchFor = (ref, defaultTimeoutMs = 25000) => (url, init, opts) => fetchWithTimeout(rawFetchFor(ref), url, init, { timeoutMs: defaultTimeoutMs, ...(opts || {}) })
79
79
 
80
80
  async function resolveRaw(ref) {
81
81
  try {
@@ -89,7 +89,7 @@ export function createAccountStore({ credentials, getConfig, fetchImpl, fetchFor
89
89
  async function loadBlob(ref) {
90
90
  const raw = await resolveRaw(ref)
91
91
  if (!raw) {
92
- const err = new Error(`no token for ${ref}`)
92
+ const err = /** @type {Error & { code?: string }} */ (new Error(`no token for ${ref}`))
93
93
  err.code = 'AUTH'
94
94
  throw err
95
95
  }
@@ -103,7 +103,7 @@ export function createAccountStore({ credentials, getConfig, fetchImpl, fetchFor
103
103
  try {
104
104
  await credentials.set(credentialRef(ref), serializeBlob(blob))
105
105
  } catch (err) {
106
- const wrapErr = new Error(`failed to persist credentials for ${ref}: ${err && err.message || err}`)
106
+ const wrapErr = /** @type {Error & { code?: string, cause?: any }} */ (new Error(`failed to persist credentials for ${ref}: ${err && err.message || err}`))
107
107
  wrapErr.code = "CREDENTIAL_SAVE_FAILED"
108
108
  wrapErr.cause = err
109
109
  throw wrapErr
@@ -171,6 +171,7 @@ export function createAccountStore({ credentials, getConfig, fetchImpl, fetchFor
171
171
  healthBadge: healthBadge(computeHealthScore(health.get(ref) || null)),
172
172
  usagePercent: usage.has(ref) ? usage.get(ref) : null,
173
173
  refreshError: refreshFailures.get(ref)?.error || '',
174
+ isTimeout: !!refreshFailures.get(ref)?.isTimeout,
174
175
  validationUrl: base.validationUrl || '',
175
176
  validationMessage: base.validationMessage || '',
176
177
  accountNotice: base.accountNotice || '',
@@ -227,7 +228,7 @@ export function createAccountStore({ credentials, getConfig, fetchImpl, fetchFor
227
228
  const curConfig = getConfig()
228
229
  const cfg = vendorConfig(provider, curConfig)
229
230
  const next = await executeWithRetry(
230
- () => getVendor(provider).refresh(cfg, blob, fetchFor(ref)),
231
+ () => getVendor(provider).refresh(cfg, blob, fetchFor(ref, 30000)),
231
232
  {
232
233
  maxRetries: 2,
233
234
  initialDelayMs: 300,
@@ -250,7 +251,11 @@ export function createAccountStore({ credentials, getConfig, fetchImpl, fetchFor
250
251
  if (ref) refreshFailures.delete(ref)
251
252
  return merged
252
253
  } catch (e) {
253
- if (ref) refreshFailures.set(ref, { at: Date.now(), error: String(e && e.message || e) })
254
+ const isTimeout = isTimeoutError(e)
255
+ const errMsg = isTimeout
256
+ ? `Network timeout refreshing token: ${e.message || 'timed out'}`
257
+ : String(e && e.message || e)
258
+ if (ref) refreshFailures.set(ref, { at: Date.now(), error: errMsg, isTimeout })
254
259
  throw e
255
260
  } finally {
256
261
  refreshLocks.delete(lockKey)
@@ -287,7 +292,8 @@ export function createAccountStore({ credentials, getConfig, fetchImpl, fetchFor
287
292
  try {
288
293
  usageFetched.set(slot.ref, Date.now())
289
294
  const blob = await ensureFresh(provider, parseBlob(raw), slot.ref)
290
- const snap = await vendor.usage(blob, cfg, fetchFor(slot.ref))
295
+ const usageTimeout = Number(curConfig?.usageTimeoutMs) || 15000
296
+ const snap = await vendor.usage(blob, cfg, fetchFor(slot.ref, usageTimeout))
291
297
  if (!snap) return
292
298
  if (Number.isFinite(Number(snap.usedPercent))) {
293
299
  usage.set(slot.ref, Number(snap.usedPercent))
@@ -322,8 +328,12 @@ export function createAccountStore({ credentials, getConfig, fetchImpl, fetchFor
322
328
  }
323
329
  } catch { /* persistence best-effort */ }
324
330
  }
325
- } catch {
331
+ } catch (err) {
326
332
  usageFetched.set(slot.ref, Date.now())
333
+ const isTimeout = isTimeoutError(err)
334
+ if (isTimeout && !refreshFailures.has(slot.ref)) {
335
+ refreshFailures.set(slot.ref, { at: Date.now(), error: `Network timeout fetching usage: ${err.message || 'timed out'}`, isTimeout: true })
336
+ }
327
337
  } finally {
328
338
  slotUsageLocks.delete(slot.ref)
329
339
  }
package/lib/adapter.js CHANGED
@@ -53,14 +53,16 @@ export class SubscriptionAdapter extends LlmAdapter {
53
53
  let models = null
54
54
  if (targetAccount) {
55
55
  const cfg = this.deps.vendorConfig(provider)
56
- const fetchImpl = pickFetch(this.deps, targetAccount.ref)
56
+ const baseFetch = pickFetch(this.deps, targetAccount.ref)
57
+ const listModelsFetch = (url, init, opts) =>
58
+ fetchWithTimeout(baseFetch, url, init, { timeoutMs: 15000, ...(opts || {}) })
57
59
  try {
58
60
  const blob = await this.deps.ensureFresh(
59
61
  provider,
60
62
  await this.deps.loadBlob(targetAccount.ref),
61
63
  targetAccount.ref,
62
64
  )
63
- const live = await getVendor(provider).listModels(blob, cfg, fetchImpl)
65
+ const live = await getVendor(provider).listModels(blob, cfg, listModelsFetch)
64
66
  if (Array.isArray(live) && live.length) models = live
65
67
  } catch { /* use built-in catalog */ }
66
68
  }
@@ -200,7 +202,7 @@ export class SubscriptionAdapter extends LlmAdapter {
200
202
  // ponytail: capture x-ratelimit headers without touching vendors
201
203
  const baseFetch = pickFetch(deps, account.ref)
202
204
  const quotaFetch = async (url, init) => {
203
- const res = await fetchWithTimeout(baseFetch, url, init, { timeoutMs: 120000 })
205
+ const res = await fetchWithTimeout(baseFetch, url, init, { timeoutMs: 60000, stream: true })
204
206
  try {
205
207
  const snap = quotaSnapshot(provider, res.headers, null, Date.now())
206
208
  if (snap && typeof deps.rememberQuota === "function") {
@@ -221,7 +223,9 @@ export class SubscriptionAdapter extends LlmAdapter {
221
223
  } catch { /* ignore quota capture failure */ }
222
224
  if (!res.ok && res.clone) {
223
225
  try {
224
- const txt = await res.clone().text()
226
+ const textPromise = res.clone().text()
227
+ const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 5000))
228
+ const txt = await Promise.race([textPromise, timeoutPromise])
225
229
  let j=null; try { j = JSON.parse(txt) } catch { j = null }
226
230
  if (j) {
227
231
  const b = parseBody(provider, j, Date.now())
package/lib/backoff.js CHANGED
@@ -1,9 +1,21 @@
1
+ /**
2
+ * @template T
3
+ * @param {() => Promise<T>} fn
4
+ * @param {{
5
+ * maxRetries?: number,
6
+ * initialDelayMs?: number,
7
+ * maxDelayMs?: number,
8
+ * factor?: number,
9
+ * isRetryable?: (err: any) => boolean,
10
+ * }} [options]
11
+ * @returns {Promise<T>}
12
+ */
1
13
  export async function executeWithRetry(fn, {
2
14
  maxRetries = 3,
3
15
  initialDelayMs = 200,
4
16
  maxDelayMs = 3000,
5
17
  factor = 2,
6
- isRetryable = () => true
18
+ isRetryable = (_err) => true
7
19
  } = {}) {
8
20
  let attempt = 0
9
21
  let delay = initialDelayMs
@@ -204,6 +204,7 @@ export function streamEnvelope({ projectId, model, request, userAgent, sessionId
204
204
  const session = sessionId || randomUUID()
205
205
  const body = { ...(request || {}) }
206
206
  if (!body.session_id) body.session_id = session
207
+ /** @type {Record<string, any>} */
207
208
  const envelope = {
208
209
  ...(projectId ? { project: projectId } : {}),
209
210
  model,
@@ -98,7 +98,7 @@ export function validationFromHttpError(status, bodyText) {
98
98
  }
99
99
 
100
100
  export function validationRequiredError(details) {
101
- const err = new Error(`${details.message} Verify at ${details.validationUrl}`)
101
+ const err = /** @type {Error & { code?: string, validationUrl?: string, learnMoreUrl?: string }} */ (new Error(`${details.message} Verify at ${details.validationUrl}`))
102
102
  err.code = 'VALIDATION_REQUIRED'
103
103
  err.validationUrl = details.validationUrl
104
104
  if (details.learnMoreUrl) err.learnMoreUrl = details.learnMoreUrl
package/lib/http.js CHANGED
@@ -81,7 +81,7 @@ export function isTrustedSettingsRequest(request) {
81
81
  if (secFetchSite === 'cross-site') return false
82
82
 
83
83
  // 2. If origin header is present, it must match host
84
- const host = headers.host || headers['x-forwarded-host']
84
+ const host = String(Array.isArray(headers.host) ? headers.host[0] : (headers.host || headers['x-forwarded-host'] || ''))
85
85
  const origin = headers.origin
86
86
  if (origin) {
87
87
  try {
@@ -167,17 +167,36 @@ export function sanitizeRequestHeaders(url, init = {}, options = {}) {
167
167
  delete headers.Cookie
168
168
  }
169
169
  }
170
- } catch {}
170
+ } catch (err) {
171
+ void err
172
+ return headers
173
+ }
171
174
  return headers
172
175
  }
173
176
 
174
- export async function fetchWithTimeout(fetchImpl, url, init = {}, { timeoutMs = 30000, allowedOrigins } = {}) {
177
+ export function isTimeoutError(err) {
178
+ if (!err) return false
179
+ return err.name === 'TimeoutError'
180
+ || err.code === 'ETIMEDOUT'
181
+ || (err.name === 'AbortError' && String(err.message || '').toLowerCase().includes('timeout'))
182
+ || String(err.message || '').toLowerCase().includes('aborted due to timeout')
183
+ || String(err.message || '').toLowerCase().includes('timed out')
184
+ || String(err.message || '').toLowerCase().includes('operation was aborted')
185
+ }
186
+
187
+ /**
188
+ * @param {any} fetchImpl
189
+ * @param {string} url
190
+ * @param {any} [init]
191
+ * @param {{ timeoutMs?: number, allowedOrigins?: string[], stream?: boolean }} [options]
192
+ */
193
+ export async function fetchWithTimeout(fetchImpl, url, init = {}, { timeoutMs = 30000, allowedOrigins, stream = false } = {}) {
175
194
  const safeHeaders = sanitizeRequestHeaders(url, init, { allowedOrigins })
176
195
  init = { ...init, headers: safeHeaders }
177
196
  const impl = fetchImpl || fetch
178
197
  if (!timeoutMs || timeoutMs <= 0) return impl(url, init)
179
198
  const controller = new AbortController()
180
- const timer = setTimeout(() => {
199
+ let timer = setTimeout(() => {
181
200
  controller.abort(new DOMException('The operation was aborted due to timeout', 'TimeoutError'))
182
201
  }, timeoutMs)
183
202
 
@@ -186,9 +205,74 @@ export async function fetchWithTimeout(fetchImpl, url, init = {}, { timeoutMs =
186
205
  : controller.signal
187
206
 
188
207
  try {
189
- return await impl(url, { ...init, signal })
190
- } finally {
208
+ const res = await impl(url, { ...init, signal })
209
+ if (stream) {
210
+ clearTimeout(timer)
211
+ return res
212
+ }
213
+ const origJson = typeof res.json === 'function' ? res.json.bind(res) : null
214
+ const origText = typeof res.text === 'function' ? res.text.bind(res) : null
215
+ if (origJson) {
216
+ res.json = () => {
217
+ if (controller.signal.aborted) {
218
+ return Promise.reject(new DOMException('The operation was aborted due to timeout', 'TimeoutError'))
219
+ }
220
+ return new Promise((resolve, reject) => {
221
+ const onAbort = () => reject(new DOMException('The operation was aborted due to timeout', 'TimeoutError'))
222
+ controller.signal.addEventListener('abort', onAbort, { once: true })
223
+ origJson().then(
224
+ (val) => {
225
+ controller.signal.removeEventListener('abort', onAbort)
226
+ clearTimeout(timer)
227
+ resolve(val)
228
+ },
229
+ (err) => {
230
+ controller.signal.removeEventListener('abort', onAbort)
231
+ clearTimeout(timer)
232
+ if (controller.signal.aborted) {
233
+ reject(new DOMException('The operation was aborted due to timeout', 'TimeoutError'))
234
+ } else {
235
+ reject(err)
236
+ }
237
+ }
238
+ )
239
+ })
240
+ }
241
+ }
242
+ if (origText) {
243
+ res.text = () => {
244
+ if (controller.signal.aborted) {
245
+ return Promise.reject(new DOMException('The operation was aborted due to timeout', 'TimeoutError'))
246
+ }
247
+ return new Promise((resolve, reject) => {
248
+ const onAbort = () => reject(new DOMException('The operation was aborted due to timeout', 'TimeoutError'))
249
+ controller.signal.addEventListener('abort', onAbort, { once: true })
250
+ origText().then(
251
+ (val) => {
252
+ controller.signal.removeEventListener('abort', onAbort)
253
+ clearTimeout(timer)
254
+ resolve(val)
255
+ },
256
+ (err) => {
257
+ controller.signal.removeEventListener('abort', onAbort)
258
+ clearTimeout(timer)
259
+ if (controller.signal.aborted) {
260
+ reject(new DOMException('The operation was aborted due to timeout', 'TimeoutError'))
261
+ } else {
262
+ reject(err)
263
+ }
264
+ }
265
+ )
266
+ })
267
+ }
268
+ }
269
+ return res
270
+ } catch (err) {
191
271
  clearTimeout(timer)
272
+ if (controller.signal.aborted && !isTimeoutError(err)) {
273
+ throw new DOMException('The operation was aborted due to timeout', 'TimeoutError')
274
+ }
275
+ throw err
192
276
  }
193
277
  }
194
278
 
@@ -39,6 +39,7 @@ async function discoverLocalCliSessionsInner() {
39
39
  const tok = codex.tokens || codex
40
40
  detected.codex = {
41
41
  provider: 'codex',
42
+ source: 'file',
42
43
  path: homeFile('.codex', 'auth.json'),
43
44
  email: codex.email || codex.account || '',
44
45
  hasRefreshToken: !!(tok.refresh_token || tok.refreshToken),
@@ -54,6 +55,7 @@ async function discoverLocalCliSessionsInner() {
54
55
  if (grok && (grok.access_token || grok.token || (grok.tokens && grok.tokens.access_token))) {
55
56
  detected.grok = {
56
57
  provider: 'grok',
58
+ source: 'file',
57
59
  path: p,
58
60
  email: grok.email || grok.account || '',
59
61
  hasRefreshToken: !!(grok.refresh_token || (grok.tokens && grok.tokens.refresh_token)),
@@ -73,6 +75,7 @@ async function discoverLocalCliSessionsInner() {
73
75
  if (tok && (tok.access_token || tok.accessToken)) {
74
76
  detected.antigravity = {
75
77
  provider: 'antigravity',
78
+ source: 'file',
76
79
  path: p,
77
80
  email: agy.email || tok.account || '',
78
81
  hasRefreshToken: !!(tok.refresh_token || tok.refreshToken),
@@ -86,6 +89,7 @@ async function discoverLocalCliSessionsInner() {
86
89
  if (kimi && (kimi.access_token || kimi.token)) {
87
90
  detected.kimi = {
88
91
  provider: 'kimi',
92
+ source: 'file',
89
93
  path: homeFile('.kimi-code', 'credentials', 'kimi-code.json'),
90
94
  email: kimi.email || kimi.account || '',
91
95
  hasRefreshToken: !!(kimi.refresh_token || kimi.refreshToken),
@@ -103,6 +107,7 @@ async function discoverLocalCliSessionsInner() {
103
107
  if (glm && (glm.apiKey || glm.api_key || (glm.provider && glm.provider.apiKey))) {
104
108
  detected.glm = {
105
109
  provider: 'glm',
110
+ source: 'file',
106
111
  path: p,
107
112
  email: 'ZCode CLI Account',
108
113
  hasRefreshToken: false,
@@ -111,20 +116,31 @@ async function discoverLocalCliSessionsInner() {
111
116
  }
112
117
  }
113
118
 
114
- // 6. Cursor Token (env, CLI config or IDE state)
119
+ // 6. Cursor Token (CLI config, auth.json, or env)
115
120
  const cursorCliConfig = await readJson(homeFile('.cursor', 'cli-config.json'))
116
- if (process.env.CURSOR_ACCESS_TOKEN) {
121
+ const cursorAuthConfig = await readJson(homeFile('.cursor', 'auth.json'))
122
+ if (cursorCliConfig && (cursorCliConfig.authInfo || cursorCliConfig.serverConfigCache)) {
117
123
  detected.cursor = {
118
124
  provider: 'cursor',
119
- path: 'CURSOR_ACCESS_TOKEN env',
120
- email: 'Cursor IDE User',
125
+ source: 'file',
126
+ path: homeFile('.cursor', 'cli-config.json'),
127
+ email: (cursorCliConfig.authInfo && cursorCliConfig.authInfo.email) || 'Cursor CLI User',
121
128
  hasRefreshToken: false,
122
129
  }
123
- } else if (cursorCliConfig && (cursorCliConfig.authInfo || cursorCliConfig.serverConfigCache)) {
130
+ } else if (cursorAuthConfig && (cursorAuthConfig.accessToken || cursorAuthConfig.access_token || (cursorAuthConfig.authInfo && cursorAuthConfig.authInfo.authId))) {
124
131
  detected.cursor = {
125
132
  provider: 'cursor',
126
- path: homeFile('.cursor', 'cli-config.json'),
127
- email: (cursorCliConfig.authInfo && cursorCliConfig.authInfo.email) || 'Cursor CLI User',
133
+ source: 'file',
134
+ path: homeFile('.cursor', 'auth.json'),
135
+ email: (cursorAuthConfig.authInfo && cursorAuthConfig.authInfo.email) || cursorAuthConfig.email || 'Cursor User',
136
+ hasRefreshToken: false,
137
+ }
138
+ } else if (process.env.CURSOR_ACCESS_TOKEN) {
139
+ detected.cursor = {
140
+ provider: 'cursor',
141
+ source: 'env',
142
+ path: 'CURSOR_ACCESS_TOKEN',
143
+ email: 'Cursor IDE User',
128
144
  hasRefreshToken: false,
129
145
  }
130
146
  }
@@ -139,6 +155,7 @@ async function discoverLocalCliSessionsInner() {
139
155
  if (k && (k.accessToken || k.access_token || k.token)) {
140
156
  detected.kiro = {
141
157
  provider: 'kiro',
158
+ source: 'file',
142
159
  path: p,
143
160
  email: k.email || k.account || 'AWS Kiro User',
144
161
  hasRefreshToken: !!(k.refreshToken || k.refresh_token),
@@ -149,7 +166,8 @@ async function discoverLocalCliSessionsInner() {
149
166
  if (!detected.kiro && process.env.KIRO_API_KEY) {
150
167
  detected.kiro = {
151
168
  provider: 'kiro',
152
- path: 'KIRO_API_KEY env',
169
+ source: 'env',
170
+ path: 'KIRO_API_KEY',
153
171
  email: 'AWS Kiro API Key',
154
172
  hasRefreshToken: false,
155
173
  }
@@ -158,12 +176,15 @@ async function discoverLocalCliSessionsInner() {
158
176
  // 8. Claude CLI Direct Import
159
177
  const claudeCreds = await readClaudeCredentials(activeHome)
160
178
  if (claudeCreds) {
161
- detected['claude-cli'] = {
179
+ const claudeEntry = {
162
180
  provider: 'claude',
181
+ source: 'file',
163
182
  path: homeFile('.claude', 'credentials.json'),
164
- email: 'Claude Code CLI User',
183
+ email: claudeCreds.email || 'Claude Code CLI User',
165
184
  hasRefreshToken: !!claudeCreds.refreshToken,
166
185
  }
186
+ detected.claude = claudeEntry
187
+ detected['claude-cli'] = claudeEntry
167
188
  }
168
189
 
169
190
  // 9. GitHub Copilot token
@@ -179,6 +200,7 @@ async function discoverLocalCliSessionsInner() {
179
200
  if (tok) {
180
201
  detected.copilot = {
181
202
  provider: 'copilot',
203
+ source: 'file',
182
204
  path: p,
183
205
  email: gh.user || 'GitHub Copilot User',
184
206
  hasRefreshToken: false,
@@ -190,37 +212,37 @@ async function discoverLocalCliSessionsInner() {
190
212
  if (!detected.copilot && (process.env.GITHUB_COPILOT_TOKEN || process.env.GH_TOKEN)) {
191
213
  detected.copilot = {
192
214
  provider: 'copilot',
193
- path: 'GITHUB_COPILOT_TOKEN env',
215
+ source: 'env',
216
+ path: process.env.GITHUB_COPILOT_TOKEN ? 'GITHUB_COPILOT_TOKEN' : 'GH_TOKEN',
194
217
  email: 'GitHub Copilot User',
195
218
  hasRefreshToken: false,
196
219
  }
197
220
  }
198
221
 
199
-
200
-
201
222
  return detected
202
223
  }
203
224
 
204
225
  export async function loadLocalCliBlob(provider, options = {}) {
226
+ const canonical = (provider === 'claude-cli') ? 'claude' : provider
205
227
  const discovered = await discoverLocalCliSessions(options)
206
- const info = discovered[provider]
228
+ const info = discovered[canonical] || discovered[provider]
207
229
  if (!info) throw new Error(`no local CLI session found for ${provider}`)
208
230
 
209
- if (provider === 'claude-cli' || (provider === 'claude' && info.provider === 'claude')) {
231
+ if (canonical === 'claude') {
210
232
  const creds = await readClaudeCredentials(options && options.home)
211
233
  if (!creds) throw new Error('Claude CLI credentials not found')
212
234
  return {
213
235
  accessToken: creds.accessToken,
214
- refreshToken: creds.refreshToken,
236
+ refreshToken: creds.refreshToken || '',
215
237
  expiresAt: creds.expiresAt,
216
- email: 'Claude Code CLI User'
238
+ email: creds.email || 'Claude Code CLI User',
217
239
  }
218
240
  }
219
241
 
220
- if (provider === 'copilot') {
242
+ if (canonical === 'copilot') {
221
243
  let tok = process.env.GITHUB_COPILOT_TOKEN || process.env.GH_TOKEN || ''
222
244
  let email = 'GitHub Copilot User'
223
- if (info.path && info.path !== 'GITHUB_COPILOT_TOKEN env') {
245
+ if (info.source === 'file' && info.path) {
224
246
  const cp = await readJson(info.path)
225
247
  if (cp) {
226
248
  const gh = cp['github.com'] || cp
@@ -228,6 +250,7 @@ export async function loadLocalCliBlob(provider, options = {}) {
228
250
  email = gh.user || email
229
251
  }
230
252
  }
253
+ if (!tok) throw new Error('no Copilot token found')
231
254
  return {
232
255
  accessToken: tok,
233
256
  refreshToken: tok,
@@ -238,10 +261,54 @@ export async function loadLocalCliBlob(provider, options = {}) {
238
261
  }
239
262
  }
240
263
 
264
+ if (canonical === 'cursor') {
265
+ if (info.source === 'env' || !info.path || info.path.includes('env') || info.path === 'CURSOR_ACCESS_TOKEN') {
266
+ const token = process.env.CURSOR_ACCESS_TOKEN
267
+ if (!token) throw new Error('CURSOR_ACCESS_TOKEN environment variable not set')
268
+ return {
269
+ accessToken: token,
270
+ refreshToken: '',
271
+ expiresAt: Date.now() + 30 * 86400 * 1000,
272
+ email: 'Cursor IDE User',
273
+ }
274
+ }
275
+ const raw = await readJson(info.path)
276
+ if (!raw) throw new Error(`failed to read local CLI file: ${info.path}`)
277
+ const token = (raw && (raw.accessToken || raw.access_token || (raw.authInfo && raw.authInfo.authId))) || process.env.CURSOR_ACCESS_TOKEN || ''
278
+ return {
279
+ accessToken: token,
280
+ refreshToken: '',
281
+ expiresAt: Date.now() + 30 * 86400 * 1000,
282
+ email: (raw && raw.authInfo && raw.authInfo.email) || raw.email || 'Cursor User',
283
+ }
284
+ }
285
+
286
+ if (canonical === 'kiro') {
287
+ if (info.source === 'env' || !info.path || info.path.includes('env') || info.path === 'KIRO_API_KEY') {
288
+ const token = process.env.KIRO_API_KEY
289
+ if (!token) throw new Error('KIRO_API_KEY environment variable not set')
290
+ return {
291
+ accessToken: token,
292
+ refreshToken: '',
293
+ expiresAt: Date.now() + 30 * 86400 * 1000,
294
+ email: 'AWS Kiro API Key',
295
+ }
296
+ }
297
+ const raw = await readJson(info.path)
298
+ if (!raw) throw new Error(`failed to read local CLI file: ${info.path}`)
299
+ const token = (raw && (raw.accessToken || raw.access_token || raw.token)) || process.env.KIRO_API_KEY || ''
300
+ return {
301
+ accessToken: token,
302
+ refreshToken: (raw && (raw.refreshToken || raw.refresh_token)) || '',
303
+ expiresAt: (raw && raw.expiresAt) || (Date.now() + 30 * 86400 * 1000),
304
+ email: (raw && raw.email) || 'AWS Kiro User',
305
+ }
306
+ }
307
+
241
308
  const raw = await readJson(info.path)
242
309
  if (!raw) throw new Error(`failed to read local CLI file: ${info.path}`)
243
310
 
244
- if (provider === 'codex') {
311
+ if (canonical === 'codex') {
245
312
  const tok = raw.tokens || raw
246
313
  return {
247
314
  accessToken: tok.access_token || tok.accessToken || '',
@@ -252,7 +319,7 @@ export async function loadLocalCliBlob(provider, options = {}) {
252
319
  }
253
320
  }
254
321
 
255
- if (provider === 'grok') {
322
+ if (canonical === 'grok') {
256
323
  const tok = raw.tokens || raw
257
324
  return {
258
325
  accessToken: tok.access_token || tok.token || '',
@@ -262,7 +329,7 @@ export async function loadLocalCliBlob(provider, options = {}) {
262
329
  }
263
330
  }
264
331
 
265
- if (provider === 'antigravity') {
332
+ if (canonical === 'antigravity') {
266
333
  const tok = raw.token || raw
267
334
  return {
268
335
  accessToken: tok.access_token || tok.accessToken || '',
@@ -273,7 +340,7 @@ export async function loadLocalCliBlob(provider, options = {}) {
273
340
  }
274
341
  }
275
342
 
276
- if (provider === 'kimi') {
343
+ if (canonical === 'kimi') {
277
344
  return {
278
345
  accessToken: raw.access_token || raw.token || '',
279
346
  refreshToken: raw.refresh_token || '',
@@ -282,7 +349,7 @@ export async function loadLocalCliBlob(provider, options = {}) {
282
349
  }
283
350
  }
284
351
 
285
- if (provider === 'glm') {
352
+ if (canonical === 'glm') {
286
353
  const apiKey = raw.apiKey || raw.api_key || (raw.provider && raw.provider.apiKey) || ''
287
354
  return {
288
355
  accessToken: apiKey,
@@ -292,25 +359,5 @@ export async function loadLocalCliBlob(provider, options = {}) {
292
359
  }
293
360
  }
294
361
 
295
- if (provider === 'cursor') {
296
- const token = process.env.CURSOR_ACCESS_TOKEN || (raw && (raw.accessToken || raw.access_token || (raw.authInfo && raw.authInfo.authId))) || ''
297
- return {
298
- accessToken: token,
299
- refreshToken: '',
300
- expiresAt: Date.now() + 30 * 86400 * 1000,
301
- email: (raw && raw.authInfo && raw.authInfo.email) || 'Cursor User',
302
- }
303
- }
304
-
305
- if (provider === 'kiro') {
306
- const token = process.env.KIRO_API_KEY || (raw && (raw.accessToken || raw.access_token || raw.token)) || ''
307
- return {
308
- accessToken: token,
309
- refreshToken: (raw && (raw.refreshToken || raw.refresh_token)) || '',
310
- expiresAt: (raw && raw.expiresAt) || (Date.now() + 30 * 86400 * 1000),
311
- email: (raw && raw.email) || 'AWS Kiro User',
312
- }
313
- }
314
-
315
362
  throw new Error(`unsupported local CLI import for provider ${provider}`)
316
363
  }
package/lib/oauth.js CHANGED
@@ -1,3 +1,15 @@
1
+ /**
2
+ * @param {{
3
+ * authUrl: string,
4
+ * clientId: string,
5
+ * redirectUri: string,
6
+ * challenge: string,
7
+ * state: string,
8
+ * extra?: Record<string, any>,
9
+ * scope?: string,
10
+ * }} params
11
+ * @returns {string}
12
+ */
1
13
  export function buildAuthorizeUrl({
2
14
  authUrl,
3
15
  clientId,
@@ -143,7 +143,9 @@ export function registerAccountsRoutes(ctx, state) {
143
143
  } catch {
144
144
  body = null
145
145
  }
146
- if (!body || !body.provider || !isProvider(body.provider)) {
146
+ const rawProv = body && body.provider
147
+ const prov = (rawProv === 'claude-cli') ? 'claude' : rawProv
148
+ if (!prov || !isProvider(prov)) {
147
149
  writeJson(res, 400, { ok: false, error: { code: 'bad_request', message: 'unknown or missing provider' } })
148
150
  return
149
151
  }
@@ -153,9 +155,8 @@ export function registerAccountsRoutes(ctx, state) {
153
155
  return
154
156
  }
155
157
  try {
156
- const blob = await loadLocalCliBlob(body.provider)
157
- const prov = body.provider
158
- const idx = Number(body.index) || 1
158
+ const blob = await loadLocalCliBlob(prov)
159
+ const idx = idxNum
159
160
  const curSlots = Array.isArray(live().slots) ? live().slots.slice() : []
160
161
  const exists = curSlots.some((s) => s && s.provider === prov && Number(s.index) === idx)
161
162
  if (!exists && getSettingsApi()) {
@@ -2,7 +2,7 @@
2
2
  import { getRecentAlerts, clearAlerts } from '../alerts.js'
3
3
  import { normalizeSlots, vendorConfig } from '../accounts.js'
4
4
  import { Config, plainConfig, publicConfig, restoreMaskedSecrets } from '../config-schema.js'
5
- import { isTrustedSettingsRequest, queryOf, readBody, safeJsonHandler, writeJson } from '../http.js'
5
+ import { isTrustedSettingsRequest, queryOf, readBody, safeJsonHandler, writeJson, fetchWithTimeout, isTimeoutError } from '../http.js'
6
6
  import { quotaSnapshot } from '../ratelimit.js'
7
7
  import { PROVIDERS, displayName, droppedCredentialRefs, isProvider, oauthRef } from '../refs.js'
8
8
  import { getVendor } from '../vendors/index.js'
@@ -257,10 +257,8 @@ export function registerStatusRoutes(ctx, state) {
257
257
  }
258
258
  let capturedQuota = null
259
259
  const probeFetch = async (url, init = {}) => {
260
- const timeoutSignal = AbortSignal.timeout(15_000)
261
- const signal = init.signal ? AbortSignal.any([init.signal, timeoutSignal]) : timeoutSignal
262
260
  const doFetch = (typeof fetchForRef === "function" && fetchForRef(ref)) || fetch
263
- const res2 = await doFetch(url, { ...init, signal })
261
+ const res2 = await fetchWithTimeout(doFetch, url, init, { timeoutMs: 15_000 })
264
262
  try {
265
263
  const snap = quotaSnapshot(provider, res2.headers, null, Date.now())
266
264
  if (snap) { capturedQuota = snap; store.rememberQuota(ref, snap) }
@@ -272,12 +270,14 @@ export function registerStatusRoutes(ctx, state) {
272
270
  const latencyMs = Date.now() - t0;
273
271
  writeJson(res, 200, { ok: true, provider, index: index, ref, latencyMs, email: pmE(blob.email), label: pmL(blob.label), expiresAt: blob.expiresAt || null, quota: capturedQuota || info.quota || null, usagePercent: info.usagePercent ?? null })
274
272
  } catch (e) {
273
+ const isTimeout = isTimeoutError(e)
275
274
  writeJson(res, 200, {
276
275
  ok: false, provider, index: index, ref,
277
276
  latencyMs: Date.now() - t0,
278
277
  email: pmE(blob.email), label: pmL(blob.label), expiresAt: blob.expiresAt || null,
279
278
  quota: capturedQuota || info.quota || null,
280
- error: { code: e && e.code ? e.code : 'VENDOR', message: String(e && e.message || e).slice(0, 300) },
279
+ isTimeout,
280
+ error: { code: isTimeout ? 'TIMEOUT' : (e && e.code ? e.code : 'VENDOR'), message: String(e && e.message || e).slice(0, 300) },
281
281
  })
282
282
  }
283
283
  }),
@@ -384,10 +384,8 @@ export function registerStatusRoutes(ctx, state) {
384
384
  if (typeof vendor.check === 'function') {
385
385
  let capturedQuota = null
386
386
  const probeFetch = async (url, init = {}) => {
387
- const timeoutSignal = AbortSignal.timeout(15_000)
388
- const signal = init.signal ? AbortSignal.any([init.signal, timeoutSignal]) : timeoutSignal
389
387
  const doFetch = (typeof fetchForRef === "function" && fetchForRef(ref)) || fetch
390
- const res2 = await doFetch(url, { ...init, signal })
388
+ const res2 = await fetchWithTimeout(doFetch, url, init, { timeoutMs: 15_000 })
391
389
  try {
392
390
  const snap = quotaSnapshot(provider, res2.headers, null, Date.now())
393
391
  if (snap) { capturedQuota = snap; store.rememberQuota(ref, snap) }
@@ -68,6 +68,11 @@ export class RunawayDetector {
68
68
  }
69
69
  }
70
70
 
71
+ /**
72
+ * @param {AsyncIterable<any>} stream
73
+ * @param {Record<string, any>} [options]
74
+ * @returns {AsyncGenerator<any, void, unknown>}
75
+ */
71
76
  export async function* withRunawayGuard(stream, options = {}) {
72
77
  if (options && options.enabled === false) {
73
78
  yield* stream
@@ -2,10 +2,8 @@ const sessionPins = new Map()
2
2
  const DEFAULT_TTL_MS = 30 * 60 * 1000 // 30 mins
3
3
 
4
4
  /**
5
- * Bind a session id to an account ref for ttlMs (default 30 minutes).
6
- * @param {string|undefined} sessionId
7
- * @param {string|undefined} accountRef
8
- * @param {number} [ttlMs]
5
+ * Prune expired session pin entries.
6
+ * @param {number} [now]
9
7
  * @returns {void}
10
8
  */
11
9
  export function pruneSessionPins(now = Date.now()) {
@@ -16,6 +14,13 @@ export function pruneSessionPins(now = Date.now()) {
16
14
  }
17
15
  }
18
16
 
17
+ /**
18
+ * Bind a session id to an account ref for ttlMs (default 30 minutes).
19
+ * @param {string|undefined} sessionId
20
+ * @param {string|undefined} accountRef
21
+ * @param {number} [ttlMs]
22
+ * @returns {void}
23
+ */
19
24
  export function pinSession(sessionId, accountRef, ttlMs = DEFAULT_TTL_MS) {
20
25
  if (!sessionId || !accountRef) return
21
26
  if (sessionPins.size >= 50) {
package/lib/sse.js CHANGED
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @param {any} body
3
+ * @param {{ idleTimeoutMs?: number, signal?: AbortSignal }} [options]
4
+ * @returns {AsyncGenerator<string, void, unknown>}
5
+ */
1
6
  export async function* iterateSse(body, { idleTimeoutMs = 60000, signal } = {}) {
2
7
  const reader = body && typeof body.getReader === 'function' ? body.getReader() : null
3
8
  const decoder = new TextDecoder()
@@ -38,7 +43,7 @@ export async function* iterateSse(body, { idleTimeoutMs = 60000, signal } = {})
38
43
  const timeoutPromise = new Promise((_, reject) => {
39
44
  if (!idleTimeoutMs || idleTimeoutMs <= 0) return
40
45
  timer = setTimeout(() => {
41
- const err = new Error(`stream idle timeout: no data received for ${Math.round(idleTimeoutMs / 1000)}s`)
46
+ const err = /** @type {Error & { code?: string }} */ (new Error(`stream idle timeout: no data received for ${Math.round(idleTimeoutMs / 1000)}s`))
42
47
  err.code = 'TIMEOUT'
43
48
  reject(err)
44
49
  }, idleTimeoutMs)
@@ -41,7 +41,7 @@ export async function* streamWithRotation({
41
41
  return
42
42
  }
43
43
  if (lastError) throw lastError
44
- const err = new Error('no usable subscription account for this provider')
44
+ const err = /** @type {Error & { code?: string }} */ (new Error('no usable subscription account for this provider'))
45
45
  err.code = 'RATE_LIMIT'
46
46
  throw err
47
47
  }
@@ -69,7 +69,8 @@ export function createVendorFromProfile(profile) {
69
69
  code_verifier: pkce.verifier,
70
70
  }
71
71
  const req = tokenStyle === "json" ? jsonTokenRequest : formTokenRequest
72
- const json = await req(profile.tokenUrl, params, fetchImpl)
72
+ const tokenFetch = (u, i, o) => fetchWithTimeout(fetchImpl || fetch, u, i, { timeoutMs: 30000, ...(o || {}) })
73
+ const json = await req(profile.tokenUrl, params, tokenFetch)
73
74
  const blob = tokenBlobFromOAuth(json)
74
75
  return { ...blob, email: blob.email || emailFromToken(blob.accessToken), label: blob.label || name }
75
76
  },
@@ -80,7 +81,8 @@ export function createVendorFromProfile(profile) {
80
81
  refresh_token: blob.refreshToken,
81
82
  }
82
83
  const req = tokenStyle === "json" ? jsonTokenRequest : formTokenRequest
83
- const json = await req(profile.tokenUrl, params, fetchImpl)
84
+ const tokenFetch = (u, i, o) => fetchWithTimeout(fetchImpl || fetch, u, i, { timeoutMs: 30000, ...(o || {}) })
85
+ const json = await req(profile.tokenUrl, params, tokenFetch)
84
86
  return tokenBlobFromOAuth(json, { label: blob.label, email: blob.email })
85
87
  },
86
88
  async listModels(blob, cfg, fetchImpl) {
@@ -112,7 +114,7 @@ export function createVendorFromProfile(profile) {
112
114
  async* streamOnce({ blob, options, fetchImpl, headers, config, signal }) {
113
115
  const body = codexResponsesBody(options, "")
114
116
  if (!body.instructions) delete body.instructions
115
- const res = await (fetchImpl || fetch)(`${base}/responses`, {
117
+ const res = await fetchWithTimeout(fetchImpl || fetch, `${base}/responses`, {
116
118
  method: "POST",
117
119
  headers: {
118
120
  ...headers,
@@ -122,7 +124,7 @@ export function createVendorFromProfile(profile) {
122
124
  },
123
125
  body: JSON.stringify(body),
124
126
  signal,
125
- })
127
+ }, { timeoutMs: 60000, stream: true })
126
128
  if (!res.ok) throw httpError(res.status, await res.text())
127
129
  yield* codexResponsesStream(res.body)
128
130
  },
@@ -18,7 +18,8 @@ export async function readClaudeCredentials(home) {
18
18
  accessToken: oauth.accessToken || oauth.access_token,
19
19
  refreshToken: oauth.refreshToken || oauth.refresh_token || '',
20
20
  expiresAt: oauth.expiresAt || (oauth.expires_in ? Date.now() + oauth.expires_in * 1000 : Date.now() + 3600000),
21
- subscriptionType: oauth.subscriptionType || 'Claude Code'
21
+ subscriptionType: oauth.subscriptionType || 'Claude Code',
22
+ email: oauth.email || json.email || 'Claude Code CLI User',
22
23
  }
23
24
  }
24
25
  return null
@@ -1,3 +1,4 @@
1
+ import { isTimeoutError } from "../http.js"
1
2
  import { randomUUID, createHash } from 'node:crypto'
2
3
  import { buildAuthorizeUrl } from '../oauth.js'
3
4
  import { codexResponsesBody, modelCatalog } from '../messages.js'
@@ -203,14 +204,15 @@ export async function usage(blob, cfg, fetchImpl) {
203
204
  const snap = asUsageSnapshot(deepestUsedPercent(json))
204
205
  if (snap) snap.windows = usageWindows(json)
205
206
  return snap
206
- } catch {
207
+ } catch (err) {
208
+ if (isTimeoutError(err)) throw err
207
209
  return null
208
210
  }
209
211
  }
210
212
 
211
213
  export async function* streamOnce({ blob, options, fetchImpl, headers, config, signal }) {
212
214
  const base = (config.baseUrl || defaults().baseUrl).replace(/\/$/, '')
213
- const body = codexResponsesBody(options, INSTRUCTIONS, config)
215
+ const body = /** @type {Record<string, any>} */ (codexResponsesBody(options, INSTRUCTIONS, config))
214
216
  const sessionId = deriveSessionId(options)
215
217
 
216
218
  // Prompt Cache Affinity
@@ -296,6 +296,7 @@ export async function* streamOnce({ blob, options, fetchImpl, headers, config, s
296
296
 
297
297
  if (!copilotToken) throw new LlmError("GitHub Copilot not authenticated", "AUTH")
298
298
 
299
+ /** @type {Record<string, any>} */
299
300
  const body = {
300
301
  model: options.model || "claude-3.7-sonnet",
301
302
  messages: openaiMessages(options),
@@ -175,6 +175,7 @@ export async function* streamOnce({ blob, options, fetchImpl, headers, config, s
175
175
  const token = blob && (blob.accessToken || blob.access_token)
176
176
  if (!token) throw new LlmError('Cursor not authenticated', 'AUTH')
177
177
 
178
+ /** @type {Record<string, any>} */
178
179
  const body = {
179
180
  model: options.model || 'composer-2',
180
181
  messages: openaiMessages(options),
@@ -84,6 +84,7 @@ export async function* streamOnce({ blob, options, fetchImpl, headers, config, s
84
84
  const token = blob && (blob.accessToken || blob.access_token)
85
85
  if (!token) throw new LlmError('GLM not authenticated', 'AUTH')
86
86
 
87
+ /** @type {Record<string, any>} */
87
88
  const body = {
88
89
  model: options.model || 'glm-4-plus',
89
90
  messages: openaiMessages(options),
@@ -88,6 +88,7 @@ export async function* streamOnce({ blob, options, fetchImpl, headers, config, s
88
88
  const token = blob && (blob.accessToken || blob.access_token)
89
89
  if (!token) throw new LlmError('Kimi not authenticated', 'AUTH')
90
90
 
91
+ /** @type {Record<string, any>} */
91
92
  const body = {
92
93
  model: options.model || 'kimi-for-coding',
93
94
  messages: openaiMessages(options),
@@ -87,6 +87,7 @@ export async function* streamOnce({ blob, options, fetchImpl, headers, config, s
87
87
  const token = blob && (blob.accessToken || blob.access_token)
88
88
  if (!token) throw new LlmError('AWS Kiro not authenticated', 'AUTH')
89
89
 
90
+ /** @type {Record<string, any>} */
90
91
  const body = {
91
92
  model: options.model || 'claude-sonnet-5',
92
93
  messages: openaiMessages(options),
@@ -57,6 +57,7 @@ export async function* streamOnce({ blob, options, fetchImpl, headers, config, s
57
57
  const key = blob && (blob.accessToken || blob.apiKey)
58
58
  if (!key) throw new LlmError('Qwen API Key or Token is required', 'AUTH')
59
59
 
60
+ /** @type {Record<string, any>} */
60
61
  const body = {
61
62
  model: options.model || 'qwen-2.5-coder-32b-instruct',
62
63
  messages: openaiMessages(options),
package/lib/wire.js CHANGED
@@ -1,9 +1,16 @@
1
+ import { fetchWithTimeout } from "./http.js"
1
2
  import { iterateSse, jsonSse } from './sse.js'
2
3
  import { validationFromHttpError, validationRequiredError, googleRateLimitMessage, googleLicenseMessage } from './google-validation.js'
3
4
 
5
+ /**
6
+ * @param {number} status
7
+ * @param {string} [bodyText]
8
+ * @param {string} [code]
9
+ * @returns {Error & { status?: number, code?: string }}
10
+ */
4
11
  export function httpError(status, bodyText, code) {
5
12
  const snippet = String(bodyText || '').slice(0, 400)
6
- const err = new Error(snippet ? `vendor http ${status}: ${snippet}` : `vendor http ${status}`)
13
+ const err = /** @type {Error & { status?: number, code?: string }} */ (new Error(snippet ? `vendor http ${status}: ${snippet}` : `vendor http ${status}`))
7
14
  err.status = status
8
15
  if (code) {
9
16
  err.code = code
@@ -401,25 +408,19 @@ export async function* googleStream(body) {
401
408
  }
402
409
 
403
410
  export async function formTokenRequest(url, params, fetchImpl, headers, { timeoutMs = 25000 } = {}) {
404
- const impl = fetchImpl || fetch
405
- const timeoutSignal = AbortSignal.timeout(timeoutMs)
406
- const res = await impl(url, {
411
+ const res = await fetchWithTimeout(fetchImpl || fetch, url, {
407
412
  method: 'POST',
408
413
  headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json', ...(headers || {}) },
409
414
  body: new URLSearchParams(params),
410
- signal: timeoutSignal,
411
- })
415
+ }, { timeoutMs })
412
416
  return readJson(res)
413
417
  }
414
418
 
415
419
  export async function jsonTokenRequest(url, body, fetchImpl, headers, { timeoutMs = 25000 } = {}) {
416
- const impl = fetchImpl || fetch
417
- const timeoutSignal = AbortSignal.timeout(timeoutMs)
418
- const res = await impl(url, {
420
+ const res = await fetchWithTimeout(fetchImpl || fetch, url, {
419
421
  method: 'POST',
420
422
  headers: { 'Content-Type': 'application/json', Accept: 'application/json', ...(headers || {}) },
421
423
  body: JSON.stringify(body),
422
- signal: timeoutSignal,
423
- })
424
+ }, { timeoutMs })
424
425
  return readJson(res)
425
426
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-subscriptions",
3
- "version": "0.6.41",
3
+ "version": "0.6.43",
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,10 +54,10 @@
54
54
  },
55
55
  "peerDependencies": {
56
56
  "@deepseek-ai/cordis": "^4.0.1",
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",
57
+ "@deepseek-ai/dsh-credentials": "^0.1.7-rc.2 || ^0.2.0-rc.1 || ^0.2.0-rc.2",
58
+ "@deepseek-ai/dsh-host-webserver": "^0.1.7-rc.2 || ^0.2.0-rc.1 || ^0.2.0-rc.2",
59
+ "@deepseek-ai/dsh-llm": "^0.1.7-rc.2 || ^0.2.0-rc.1 || ^0.2.0-rc.2",
60
+ "@deepseek-ai/dsh-settings": "^0.1.7-rc.2 || ^0.2.0-rc.1 || ^0.2.0-rc.2",
61
61
  "@deepseek-ai/schemastery": "^3.18.4"
62
62
  },
63
63
  "publishConfig": {
@@ -68,9 +68,14 @@
68
68
  "undici": "^8.10.1"
69
69
  },
70
70
  "devDependencies": {
71
- "@deepseek-ai/dsh-llm": "0.1.0-rc.8",
71
+ "@deepseek-ai/cordis": "^4.0.4",
72
+ "@deepseek-ai/dsh-credentials": "^0.2.0-rc.2",
73
+ "@deepseek-ai/dsh-host-webserver": "^0.2.0-rc.2",
74
+ "@deepseek-ai/dsh-llm": "^0.2.0-rc.2",
75
+ "@deepseek-ai/dsh-settings": "^0.2.0-rc.2",
76
+ "@deepseek-ai/schemastery": "^3.18.4",
72
77
  "@types/node": "^22.20.2",
73
- "typescript": "^7.0.2",
74
- "@deepseek-ai/schemastery": "^3.18.4"
78
+ "eslint": "^10.4.0",
79
+ "typescript": "^7.0.2"
75
80
  }
76
81
  }