@goodandready/dsh-subscriptions 0.4.15 → 0.4.17
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/README.md +175 -209
- package/README.ru.md +196 -0
- package/README.zh.md +123 -0
- package/lib/accounts.js +19 -5
- package/lib/adapter.js +28 -7
- package/lib/client.js +153 -3
- package/lib/index.js +352 -12
- package/lib/loopback.js +66 -0
- package/lib/mask.js +21 -0
- package/lib/messages.js +8 -1
- package/lib/ollama.js +70 -0
- package/lib/proxy.js +79 -0
- package/lib/rotate.js +28 -3
- package/lib/stream-rotate.js +4 -3
- package/lib/subscriptions.js +8 -5
- package/lib/vendors/codex.js +46 -1
- package/lib/vendors/grok.js +2 -0
- package/package.json +9 -2
package/lib/ollama.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { LlmAdapter, LlmError, attributionHeaders } from '@deepseek-ai/dsh-llm'
|
|
2
|
+
import { openaiMessages, openaiTools } from './messages.js'
|
|
3
|
+
import { openaiChatStream } from './wire.js'
|
|
4
|
+
|
|
5
|
+
// #91: local Ollama gateway - a $0 emergency provider and seamless quota
|
|
6
|
+
// fallback. Speaks the OpenAI-compatible subset Ollama serves at /v1.
|
|
7
|
+
|
|
8
|
+
export function ollamaBase(cfg) {
|
|
9
|
+
return String(cfg.ollamaBaseUrl || 'http://127.0.0.1:11434').replace(/\/$/, '')
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function ollamaAlive(baseUrl, fetchImpl, timeoutMs = 2000) {
|
|
13
|
+
try {
|
|
14
|
+
const res = await fetchImpl(baseUrl + '/api/tags', { signal: AbortSignal.timeout(timeoutMs) })
|
|
15
|
+
return !!res && res.ok
|
|
16
|
+
} catch { return false }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function ollamaModels(baseUrl, fetchImpl) {
|
|
20
|
+
const res = await fetchImpl(baseUrl + '/api/tags', { signal: AbortSignal.timeout(3000) })
|
|
21
|
+
if (!res || !res.ok) throw new LlmError('ollama /api/tags http ' + (res && res.status), 'VENDOR', { status: res && res.status })
|
|
22
|
+
const j = await res.json()
|
|
23
|
+
return (Array.isArray(j.models) ? j.models : []).map((m) => ({
|
|
24
|
+
id: m.name,
|
|
25
|
+
name: m.name,
|
|
26
|
+
...(m.details && m.details.parameter_size ? { description: m.details.parameter_size } : {}),
|
|
27
|
+
}))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class OllamaAdapter extends LlmAdapter {
|
|
31
|
+
constructor(deps) {
|
|
32
|
+
super()
|
|
33
|
+
this.deps = deps
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
providerInfo(provider) {
|
|
37
|
+
return { id: provider, name: 'Ollama (local)' }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
providerRetryPolicy(_provider) {
|
|
41
|
+
return undefined
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async listModels(provider) {
|
|
45
|
+
return ollamaModels(this.deps.baseUrl(), this.deps.fetchImpl || fetch)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async resolveModel(provider, model, _signal) {
|
|
49
|
+
return { provider, id: model, name: model }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async *stream(options) {
|
|
53
|
+
const base = this.deps.baseUrl()
|
|
54
|
+
const model = options.model || this.deps.fallbackModel() || ''
|
|
55
|
+
const body = { model, messages: openaiMessages(options), stream: true }
|
|
56
|
+
const tools = openaiTools(options)
|
|
57
|
+
if (tools) body.tools = tools
|
|
58
|
+
const res = await (this.deps.fetchImpl || fetch)(base + '/v1/chat/completions', {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
headers: { 'Content-Type': 'application/json', ...attributionHeaders() },
|
|
61
|
+
body: JSON.stringify(body),
|
|
62
|
+
signal: options.signal,
|
|
63
|
+
})
|
|
64
|
+
if (!res || !res.ok) {
|
|
65
|
+
const txt = res && res.text ? await res.text().catch(() => '') : ''
|
|
66
|
+
throw new LlmError('ollama http ' + (res && res.status) + (txt ? ': ' + txt.slice(0, 200) : ''), 'VENDOR', { status: res && res.status })
|
|
67
|
+
}
|
|
68
|
+
yield* openaiChatStream(res.body)
|
|
69
|
+
}
|
|
70
|
+
}
|
package/lib/proxy.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// #88: per-account proxy support.
|
|
2
|
+
// http/https proxies -> undici ProxyAgent; socks5 -> undici Agent with a socks connector.
|
|
3
|
+
import { ProxyAgent, Agent, fetch as undiciFetch } from 'undici'
|
|
4
|
+
import tls from 'node:tls'
|
|
5
|
+
import { SocksClient } from 'socks'
|
|
6
|
+
|
|
7
|
+
const cache = new Map()
|
|
8
|
+
|
|
9
|
+
export function parseProxyUrl(raw) {
|
|
10
|
+
const s = String(raw || '').trim()
|
|
11
|
+
if (!s) return null
|
|
12
|
+
let u
|
|
13
|
+
try { u = new URL(s) } catch { return null }
|
|
14
|
+
const scheme = u.protocol.replace(':', '')
|
|
15
|
+
if (scheme !== 'http' && scheme !== 'https' && scheme !== 'socks5') return null
|
|
16
|
+
if (!u.hostname) return null
|
|
17
|
+
const port = Number(u.port) || (scheme === 'socks5' ? 1080 : (scheme === 'https' ? 443 : 80))
|
|
18
|
+
const auth = u.username
|
|
19
|
+
? { username: decodeURIComponent(u.username), password: decodeURIComponent(u.password || '') }
|
|
20
|
+
: null
|
|
21
|
+
return { href: s, scheme, host: u.hostname, port, auth }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function socksDispatcher(p) {
|
|
25
|
+
return new Agent({
|
|
26
|
+
connect: (opts, callback) => {
|
|
27
|
+
SocksClient.createConnection({
|
|
28
|
+
proxy: {
|
|
29
|
+
host: p.host,
|
|
30
|
+
port: p.port,
|
|
31
|
+
type: 5,
|
|
32
|
+
...(p.auth ? { userId: p.auth.username, password: p.auth.password } : {}),
|
|
33
|
+
},
|
|
34
|
+
command: 'connect',
|
|
35
|
+
destination: { host: opts.hostname || opts.host, port: Number(opts.port) || 443 },
|
|
36
|
+
}).then(({ socket }) => {
|
|
37
|
+
if (String(opts.protocol) !== 'https:') {
|
|
38
|
+
callback(null, socket)
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
const tlsSocket = tls.connect({
|
|
42
|
+
socket,
|
|
43
|
+
servername: opts.servername || opts.hostname || opts.host,
|
|
44
|
+
})
|
|
45
|
+
tlsSocket.once('secureConnect', () => callback(null, tlsSocket))
|
|
46
|
+
tlsSocket.once('error', (e) => callback(e))
|
|
47
|
+
}).catch(callback)
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function httpDispatcher(p) {
|
|
53
|
+
return new ProxyAgent({
|
|
54
|
+
uri: p.href,
|
|
55
|
+
...(p.auth ? { token: 'Basic ' + Buffer.from(p.auth.username + ':' + p.auth.password).toString('base64') } : {}),
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Returns a fetch bound to the proxy dispatcher, or null for empty/invalid proxy. Dispatchers are cached per URL. */
|
|
60
|
+
export function proxyFetch(raw) {
|
|
61
|
+
const p = parseProxyUrl(raw)
|
|
62
|
+
if (!p) return null
|
|
63
|
+
let f = cache.get(p.href)
|
|
64
|
+
if (!f) {
|
|
65
|
+
const d = p.scheme === 'socks5' ? socksDispatcher(p) : httpDispatcher(p)
|
|
66
|
+
f = (url, init = {}) => undiciFetch(url, { ...init, dispatcher: d })
|
|
67
|
+
cache.set(p.href, f)
|
|
68
|
+
}
|
|
69
|
+
return f
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Pick the fetch for an account ref: per-slot proxy -> deps.fetchImpl -> global fetch. */
|
|
73
|
+
export function pickFetch(deps, ref) {
|
|
74
|
+
if (typeof deps?.fetchForRef === 'function') {
|
|
75
|
+
const f = deps.fetchForRef(ref)
|
|
76
|
+
if (f) return f
|
|
77
|
+
}
|
|
78
|
+
return deps?.fetchImpl || fetch
|
|
79
|
+
}
|
package/lib/rotate.js
CHANGED
|
@@ -12,6 +12,25 @@ export function isSwitchableError(err) {
|
|
|
12
12
|
return status === 429
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
// #87: family classification. Cooldowns are scoped to the model family that
|
|
16
|
+
// hit the limit, so a reasoning 429 does not block standard models.
|
|
17
|
+
export function modelFamily(provider, model) {
|
|
18
|
+
const id = String(model || '')
|
|
19
|
+
if (provider === 'claude') return /thinking/i.test(id) ? 'reasoning' : 'standard'
|
|
20
|
+
if (provider === 'grok') return /reasoning/i.test(id) ? 'reasoning' : 'standard'
|
|
21
|
+
if (provider === 'codex') return 'reasoning'
|
|
22
|
+
return 'standard'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Legacy cooldowns (no family list) block everything; scoped ones block only
|
|
26
|
+
// their own family. An unknown family never blocks a differently-scoped cooldown.
|
|
27
|
+
export function cooldownBlocks(acc, family) {
|
|
28
|
+
const fams = acc && acc.cooldownFamilies
|
|
29
|
+
if (!Array.isArray(fams) || !fams.length) return true
|
|
30
|
+
if (!family) return true
|
|
31
|
+
return fams.includes(family)
|
|
32
|
+
}
|
|
33
|
+
|
|
15
34
|
export function pickAccount(accounts, nowMs, opts) {
|
|
16
35
|
const list = Array.isArray(accounts) ? accounts : []
|
|
17
36
|
const now = Number(nowMs) || 0
|
|
@@ -47,7 +66,7 @@ export function pickAccount(accounts, nowMs, opts) {
|
|
|
47
66
|
const fallback = []
|
|
48
67
|
for (const acc of list) {
|
|
49
68
|
if (!acc || !acc.hasToken) continue
|
|
50
|
-
const isCooldown = acc.cooldownUntil && Number(acc.cooldownUntil) > now
|
|
69
|
+
const isCooldown = acc.cooldownUntil && Number(acc.cooldownUntil) > now && cooldownBlocks(acc, opts && opts.family)
|
|
51
70
|
const exhausted = isQuotaExhausted(acc) || isUsageExhausted(acc)
|
|
52
71
|
if (isCooldown || exhausted) {
|
|
53
72
|
tiers[2].push(acc)
|
|
@@ -68,8 +87,14 @@ export function pickAccount(accounts, nowMs, opts) {
|
|
|
68
87
|
return null
|
|
69
88
|
}
|
|
70
89
|
|
|
71
|
-
export function markCooldown(account, nowMs, cooldownMs) {
|
|
90
|
+
export function markCooldown(account, nowMs, cooldownMs, family) {
|
|
72
91
|
const wait = Number(cooldownMs)
|
|
73
92
|
const ms = Number.isFinite(wait) && wait > 0 ? wait : 30 * 60 * 1000
|
|
74
|
-
|
|
93
|
+
const prev = Array.isArray(account.cooldownFamilies) ? account.cooldownFamilies.slice() : []
|
|
94
|
+
const fams = family ? (prev.includes(family) ? prev : prev.concat(family)) : prev
|
|
95
|
+
return {
|
|
96
|
+
...account,
|
|
97
|
+
cooldownUntil: (Number(nowMs) || 0) + ms,
|
|
98
|
+
...(fams.length ? { cooldownFamilies: fams } : {}),
|
|
99
|
+
}
|
|
75
100
|
}
|
package/lib/stream-rotate.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { pickAccount, markCooldown, isSwitchableError } from './rotate.js'
|
|
1
|
+
import { pickAccount, markCooldown, isSwitchableError, modelFamily } from './rotate.js'
|
|
2
2
|
|
|
3
3
|
export async function* streamWithRotation({
|
|
4
4
|
accounts,
|
|
@@ -13,7 +13,7 @@ export async function* streamWithRotation({
|
|
|
13
13
|
let lastError = null
|
|
14
14
|
const tried = new Set()
|
|
15
15
|
while (true) {
|
|
16
|
-
const account = pickAccount(pool, nowMs(), { switchAtRemaining })
|
|
16
|
+
const account = pickAccount(pool, nowMs(), { switchAtRemaining, family: modelFamily(options && options.provider, options && options.model) })
|
|
17
17
|
if (!account) {
|
|
18
18
|
if (lastError) throw lastError
|
|
19
19
|
const err = new Error('no usable subscription account for this provider')
|
|
@@ -33,8 +33,9 @@ export async function* streamWithRotation({
|
|
|
33
33
|
} catch (err) {
|
|
34
34
|
lastError = err
|
|
35
35
|
if (!isSwitchableError(err)) throw err
|
|
36
|
-
const cooled = markCooldown(account, nowMs(), cooldownMs)
|
|
36
|
+
const cooled = markCooldown(account, nowMs(), cooldownMs, modelFamily(options && options.provider, options && options.model))
|
|
37
37
|
account.cooldownUntil = cooled.cooldownUntil
|
|
38
|
+
if (cooled.cooldownFamilies) account.cooldownFamilies = cooled.cooldownFamilies
|
|
38
39
|
if (onCooldown) onCooldown(account)
|
|
39
40
|
}
|
|
40
41
|
}
|
package/lib/subscriptions.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { isProvider } from "./refs.js"
|
|
2
|
-
import { pickAccount, markCooldown, isSwitchableError } from "./rotate.js"
|
|
2
|
+
import { pickAccount, markCooldown, isSwitchableError, modelFamily } from "./rotate.js"
|
|
3
3
|
import { quotaSnapshot } from "./ratelimit.js"
|
|
4
4
|
import { getVendor } from "./vendors/index.js"
|
|
5
|
+
import { pickFetch } from "./proxy.js"
|
|
5
6
|
|
|
6
7
|
// ponytail: allowlist per provider — add path to extend without touching request logic
|
|
7
8
|
export const ALLOWLIST = {
|
|
@@ -91,7 +92,7 @@ export function createSubscriptionsService(deps) {
|
|
|
91
92
|
const tried = new Set()
|
|
92
93
|
let lastError = null
|
|
93
94
|
while (true) {
|
|
94
|
-
const account = pickAccount(pool, Date.now(), { switchAtRemaining: thr })
|
|
95
|
+
const account = pickAccount(pool, Date.now(), { switchAtRemaining: thr, family: modelFamily(provider, body && body.model) })
|
|
95
96
|
if (!account) {
|
|
96
97
|
if (lastError) throw lastError
|
|
97
98
|
const err = new Error("no usable subscription account for this provider")
|
|
@@ -121,7 +122,8 @@ export function createSubscriptionsService(deps) {
|
|
|
121
122
|
}
|
|
122
123
|
const url = p
|
|
123
124
|
const extraHeaders = headersFor(provider, blob, cfg)
|
|
124
|
-
const fetchImpl = deps.
|
|
125
|
+
const fetchImpl = pickFetch(deps, account.ref)
|
|
126
|
+
const t0 = Date.now()
|
|
125
127
|
const res = await fetchImpl(url, {
|
|
126
128
|
method,
|
|
127
129
|
headers: {
|
|
@@ -169,6 +171,7 @@ export function createSubscriptionsService(deps) {
|
|
|
169
171
|
path,
|
|
170
172
|
method,
|
|
171
173
|
status: res.status || 200,
|
|
174
|
+
ms: Date.now() - t0,
|
|
172
175
|
kind: "request",
|
|
173
176
|
})
|
|
174
177
|
} catch {}
|
|
@@ -177,9 +180,9 @@ export function createSubscriptionsService(deps) {
|
|
|
177
180
|
} catch (err) {
|
|
178
181
|
lastError = err
|
|
179
182
|
if (!isSwitchableError(err)) throw err
|
|
180
|
-
const cooled = markCooldown(account, Date.now(), cooldownMs)
|
|
183
|
+
const cooled = markCooldown(account, Date.now(), cooldownMs, modelFamily(provider, body && body.model))
|
|
181
184
|
account.cooldownUntil = cooled.cooldownUntil
|
|
182
|
-
if (typeof deps.rememberCooldown === "function") deps.rememberCooldown(account.ref, account.cooldownUntil)
|
|
185
|
+
if (typeof deps.rememberCooldown === "function") deps.rememberCooldown(account.ref, account.cooldownUntil, cooled.cooldownFamilies || null)
|
|
183
186
|
// try next account
|
|
184
187
|
}
|
|
185
188
|
}
|
package/lib/vendors/codex.js
CHANGED
|
@@ -10,6 +10,12 @@ export const id = 'codex'
|
|
|
10
10
|
const AUTH = 'https://auth.openai.com/oauth/authorize'
|
|
11
11
|
const TOKEN = 'https://auth.openai.com/oauth/token'
|
|
12
12
|
const USAGE = 'https://chatgpt.com/backend-api/wham/usage'
|
|
13
|
+
// #90: device-code login (headless). OpenAI mints an authorization_code +
|
|
14
|
+
// code_verifier server-side; we finish with the normal PKCE exchange.
|
|
15
|
+
const DEVICE_USERCODE_URL = 'https://auth.openai.com/api/accounts/deviceauth/usercode'
|
|
16
|
+
const DEVICE_TOKEN_URL = 'https://auth.openai.com/api/accounts/deviceauth/token'
|
|
17
|
+
export const DEVICE_AUTH_URL = 'https://auth.openai.com/codex/device'
|
|
18
|
+
const DEVICE_REDIRECT_URI = 'https://auth.openai.com/deviceauth/callback'
|
|
13
19
|
const SCOPE = 'openid profile email offline_access api.connectors.read api.connectors.invoke'
|
|
14
20
|
const INSTRUCTIONS = 'You are a coding assistant using a ChatGPT Codex subscription.'
|
|
15
21
|
|
|
@@ -82,6 +88,45 @@ export async function exchangeCode(cfg, pkce, code, fetchImpl) {
|
|
|
82
88
|
return decorate(tokenBlobFromOAuth(json), json)
|
|
83
89
|
}
|
|
84
90
|
|
|
91
|
+
export async function deviceStart(cfg, fetchImpl) {
|
|
92
|
+
const impl = fetchImpl || fetch
|
|
93
|
+
const res = await impl(DEVICE_USERCODE_URL, {
|
|
94
|
+
method: 'POST',
|
|
95
|
+
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
|
96
|
+
body: JSON.stringify({ client_id: cfg.clientId }),
|
|
97
|
+
})
|
|
98
|
+
if (!res.ok) throw httpError(res.status, await res.text().catch(() => ''), 'DEVICE')
|
|
99
|
+
const json = await readJson(res)
|
|
100
|
+
if (!json || typeof json.user_code !== 'string' || typeof json.device_auth_id !== 'string') {
|
|
101
|
+
throw httpError(502, 'device auth response is incomplete', 'DEVICE')
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
userCode: json.user_code,
|
|
105
|
+
deviceAuthId: json.device_auth_id,
|
|
106
|
+
intervalMs: Math.max(parseInt(json.interval, 10) || 5, 1) * 1000,
|
|
107
|
+
authUrl: DEVICE_AUTH_URL,
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export async function devicePoll(cfg, session, fetchImpl) {
|
|
112
|
+
const impl = fetchImpl || fetch
|
|
113
|
+
const res = await impl(DEVICE_TOKEN_URL, {
|
|
114
|
+
method: 'POST',
|
|
115
|
+
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
|
116
|
+
body: JSON.stringify({ device_auth_id: session.deviceAuthId, user_code: session.userCode }),
|
|
117
|
+
})
|
|
118
|
+
// 403/404 = not confirmed yet (matches reference implementations)
|
|
119
|
+
if (res.status === 403 || res.status === 404) return { status: 'pending' }
|
|
120
|
+
if (!res.ok) throw httpError(res.status, await res.text().catch(() => ''), 'DEVICE')
|
|
121
|
+
const json = await readJson(res)
|
|
122
|
+
if (!json || typeof json.authorization_code !== 'string' || typeof json.code_verifier !== 'string') {
|
|
123
|
+
throw httpError(502, 'device auth token response is incomplete', 'DEVICE')
|
|
124
|
+
}
|
|
125
|
+
const deviceCfg = { ...cfg, redirectUri: DEVICE_REDIRECT_URI }
|
|
126
|
+
const blob = await exchangeCode(deviceCfg, { verifier: json.code_verifier }, json.authorization_code, impl)
|
|
127
|
+
return { status: 'authorized', blob }
|
|
128
|
+
}
|
|
129
|
+
|
|
85
130
|
export async function refresh(cfg, blob, fetchImpl) {
|
|
86
131
|
const json = await formTokenRequest(TOKEN, {
|
|
87
132
|
grant_type: 'refresh_token',
|
|
@@ -160,7 +205,7 @@ export async function usage(blob, cfg, fetchImpl) {
|
|
|
160
205
|
|
|
161
206
|
export async function* streamOnce({ blob, options, fetchImpl, headers, config, signal }) {
|
|
162
207
|
const base = (config.baseUrl || defaults().baseUrl).replace(/\/$/, '')
|
|
163
|
-
const body = codexResponsesBody(options, INSTRUCTIONS)
|
|
208
|
+
const body = codexResponsesBody(options, INSTRUCTIONS, config)
|
|
164
209
|
const res = await fetchImpl(`${base}/responses`, {
|
|
165
210
|
method: 'POST',
|
|
166
211
|
headers: {
|
package/lib/vendors/grok.js
CHANGED
|
@@ -213,6 +213,8 @@ export async function* streamOnce({ blob, options, fetchImpl, headers, config, s
|
|
|
213
213
|
const base = (config.baseUrl || defaults().baseUrl).replace(/\/$/, '')
|
|
214
214
|
let body = codexResponsesBody(options, '')
|
|
215
215
|
if (!body.instructions) delete body.instructions
|
|
216
|
+
// grok manages reasoning itself (cli catalog aware); drop the codex-level field
|
|
217
|
+
delete body.reasoning
|
|
216
218
|
const catalog = await cliCatalogCached(blob, config, fetchImpl).catch(() => new Map())
|
|
217
219
|
const reasoning = grokReasoningBody(options.model, options.reasoningEffort, catalog)
|
|
218
220
|
if (reasoning) body.reasoning = reasoning
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-subscriptions",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.17",
|
|
4
4
|
"description": "Use ChatGPT Codex, Claude, Grok, and Antigravity subscriptions as DeepSeek Harness LLM providers via OAuth.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -51,13 +51,20 @@
|
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
54
|
-
"@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
|
|
55
54
|
"@deepseek-ai/dsh-credentials": "^0.1.0-rc.6",
|
|
56
55
|
"@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.6",
|
|
56
|
+
"@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
|
|
57
57
|
"@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
|
|
58
58
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
59
59
|
},
|
|
60
60
|
"publishConfig": {
|
|
61
61
|
"access": "public"
|
|
62
|
+
},
|
|
63
|
+
"dependencies": {
|
|
64
|
+
"socks": "^2.8.9",
|
|
65
|
+
"undici": "^8.10.1"
|
|
66
|
+
},
|
|
67
|
+
"devDependencies": {
|
|
68
|
+
"@deepseek-ai/dsh-llm": "0.1.0-rc.8"
|
|
62
69
|
}
|
|
63
70
|
}
|