@goodandready/dsh-subscriptions 0.6.71 → 0.6.82
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 +13 -0
- package/README.ru.md +13 -0
- package/README.zh.md +13 -0
- package/lib/accounts-view.js +101 -5
- package/lib/accounts.js +150 -11
- package/lib/adapter.js +3 -0
- package/lib/alerts.js +35 -0
- package/lib/analytics.js +31 -2
- package/lib/billing.js +99 -0
- package/lib/blob.js +18 -0
- package/lib/capabilities.js +208 -0
- package/lib/client.js +944 -828
- package/lib/config-schema.js +46 -4
- package/lib/diagnostic-wizard.js +2 -5
- package/lib/diagnostics.js +2 -2
- package/lib/doctor.js +163 -0
- package/lib/error-buffer.js +2 -2
- package/lib/forecast.js +56 -0
- package/lib/headless-auth.js +379 -0
- package/lib/history.js +36 -0
- package/lib/http.js +26 -4
- package/lib/index.js +98 -11
- package/lib/instructions.js +138 -0
- package/lib/jwt.js +84 -17
- package/lib/loopback.js +3 -2
- package/lib/mask.js +16 -0
- package/lib/oauth-pending.js +12 -0
- package/lib/pricing.js +89 -0
- package/lib/refs.js +2 -0
- package/lib/reset-clock.js +55 -0
- package/lib/rotate.js +18 -4
- package/lib/routes/accounts.js +203 -0
- package/lib/routes/oauth.js +44 -2
- package/lib/routes/proxy.js +14 -0
- package/lib/routes/status.js +99 -5
- package/lib/scope-inspector.js +51 -0
- package/lib/stream-rotate.js +31 -10
- package/lib/updater.js +21 -0
- package/lib/vault-sync.js +412 -0
- package/lib/vault.js +1 -1
- package/lib/vendor-factory.js +6 -2
- package/lib/vendors/antigravity.js +11 -2
- package/lib/vendors/claude.js +56 -8
- package/lib/vendors/codex.js +34 -6
- package/lib/vendors/copilot-cli.js +8 -1
- package/lib/vendors/ernie.js +9 -2
- package/lib/vendors/grok.js +66 -6
- package/lib/vendors/jetbrains.js +133 -13
- package/lib/vendors/perplexity.js +148 -17
- package/package.json +1 -1
|
@@ -1,9 +1,20 @@
|
|
|
1
|
+
// #563: Perplexity Pro Subscription OAuth Vendor
|
|
2
|
+
// Supports OAuth PKCE authentication, token refresh, and Pro subscription models.
|
|
3
|
+
|
|
1
4
|
import { LlmError } from '@deepseek-ai/dsh-llm'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
5
|
+
import { buildAuthorizeUrl } from '../oauth.js'
|
|
6
|
+
import { openaiMessages, modelCatalog } from '../messages.js'
|
|
7
|
+
import { jsonTokenRequest, tokenBlobFromOAuth, openaiChatStream, httpError, readJson } from '../wire.js'
|
|
8
|
+
import { emailFromToken, planFromToken, avatarFromToken, scopesFromToken } from '../jwt.js'
|
|
4
9
|
|
|
5
10
|
export const id = 'perplexity'
|
|
11
|
+
|
|
12
|
+
const AUTH = 'https://www.perplexity.ai/oauth/authorize'
|
|
13
|
+
const TOKEN = 'https://api.perplexity.ai/oauth/token'
|
|
6
14
|
const PERPLEXITY_API_BASE = 'https://api.perplexity.ai'
|
|
15
|
+
const PROFILE = 'https://api.perplexity.ai/api/v1/user/profile'
|
|
16
|
+
const USAGE = 'https://api.perplexity.ai/api/v1/user/usage'
|
|
17
|
+
const SCOPE = 'openid profile email pro:search offline_access'
|
|
7
18
|
|
|
8
19
|
const PERPLEXITY_MODELS = [
|
|
9
20
|
{
|
|
@@ -11,15 +22,43 @@ const PERPLEXITY_MODELS = [
|
|
|
11
22
|
name: 'Sonar Reasoning Pro',
|
|
12
23
|
contextWindow: 128000,
|
|
13
24
|
maxTokens: 8192,
|
|
14
|
-
|
|
25
|
+
reasoning: {
|
|
26
|
+
efforts: [
|
|
27
|
+
{ id: 'low', name: 'low' },
|
|
28
|
+
{ id: 'medium', name: 'medium' },
|
|
29
|
+
{ id: 'high', name: 'high' },
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
inputModalities: ['text'],
|
|
15
33
|
},
|
|
16
34
|
{
|
|
17
35
|
id: 'sonar-pro',
|
|
18
36
|
name: 'Sonar Pro',
|
|
19
37
|
contextWindow: 200000,
|
|
20
38
|
maxTokens: 8192,
|
|
21
|
-
inputModalities: ['text']
|
|
22
|
-
}
|
|
39
|
+
inputModalities: ['text'],
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'sonar-reasoning',
|
|
43
|
+
name: 'Sonar Reasoning',
|
|
44
|
+
contextWindow: 128000,
|
|
45
|
+
maxTokens: 8192,
|
|
46
|
+
inputModalities: ['text'],
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'sonar',
|
|
50
|
+
name: 'Sonar',
|
|
51
|
+
contextWindow: 128000,
|
|
52
|
+
maxTokens: 8192,
|
|
53
|
+
inputModalities: ['text'],
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: 'r1-1776',
|
|
57
|
+
name: 'DeepSeek R1 (Perplexity)',
|
|
58
|
+
contextWindow: 128000,
|
|
59
|
+
maxTokens: 8192,
|
|
60
|
+
inputModalities: ['text'],
|
|
61
|
+
},
|
|
23
62
|
]
|
|
24
63
|
|
|
25
64
|
export function providerInfo() {
|
|
@@ -28,24 +67,116 @@ export function providerInfo() {
|
|
|
28
67
|
|
|
29
68
|
export function defaults() {
|
|
30
69
|
return {
|
|
70
|
+
clientId: 'perplexity-cli-oauth',
|
|
71
|
+
redirectUri: 'http://localhost:1455/auth/callback',
|
|
31
72
|
apiBase: PERPLEXITY_API_BASE,
|
|
32
|
-
models: PERPLEXITY_MODELS.map((m) => m.id)
|
|
73
|
+
models: PERPLEXITY_MODELS.map((m) => m.id),
|
|
33
74
|
}
|
|
34
75
|
}
|
|
35
76
|
|
|
36
|
-
export function authorizeUrl() {
|
|
37
|
-
|
|
77
|
+
export function authorizeUrl(cfg = {}, pkce = {}) {
|
|
78
|
+
const c = cfg || {}
|
|
79
|
+
const p = pkce || {}
|
|
80
|
+
return buildAuthorizeUrl({
|
|
81
|
+
authUrl: AUTH,
|
|
82
|
+
clientId: c.clientId || defaults().clientId,
|
|
83
|
+
redirectUri: c.redirectUri || defaults().redirectUri,
|
|
84
|
+
challenge: p.challenge || 'challenge',
|
|
85
|
+
state: p.state || 'state',
|
|
86
|
+
scope: SCOPE,
|
|
87
|
+
})
|
|
38
88
|
}
|
|
39
89
|
|
|
40
|
-
|
|
41
|
-
|
|
90
|
+
async function decorate(blob, fetchImpl) {
|
|
91
|
+
let email = blob.email || emailFromToken(blob.accessToken) || emailFromToken(blob.idToken)
|
|
92
|
+
let plan = blob.plan || planFromToken(blob.accessToken)
|
|
93
|
+
let avatarUrl = blob.avatarUrl || avatarFromToken(blob.accessToken)
|
|
94
|
+
let scopes = blob.scopes || scopesFromToken(blob.accessToken)
|
|
95
|
+
|
|
96
|
+
if (fetchImpl && blob.accessToken && (!email || !plan)) {
|
|
97
|
+
try {
|
|
98
|
+
const res = await fetchImpl(PROFILE, {
|
|
99
|
+
headers: {
|
|
100
|
+
Authorization: `Bearer ${blob.accessToken}`,
|
|
101
|
+
Accept: 'application/json',
|
|
102
|
+
},
|
|
103
|
+
})
|
|
104
|
+
if (res.ok) {
|
|
105
|
+
const json = await readJson(res)
|
|
106
|
+
email = email || json.email || ''
|
|
107
|
+
plan = plan || json.plan || (json.has_pro ? 'Perplexity Pro' : 'Free')
|
|
108
|
+
avatarUrl = avatarUrl || json.picture || json.avatar_url || ''
|
|
109
|
+
if (json.scope && !scopes.length) scopes = String(json.scope).split(/[\s,]+/).filter(Boolean)
|
|
110
|
+
}
|
|
111
|
+
} catch { /* profile is optional */ }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
...blob,
|
|
116
|
+
email: email || '',
|
|
117
|
+
plan: plan || 'Perplexity Pro',
|
|
118
|
+
avatarUrl: avatarUrl || '',
|
|
119
|
+
scopes,
|
|
120
|
+
label: blob.label || email || 'Perplexity Pro',
|
|
121
|
+
}
|
|
42
122
|
}
|
|
43
123
|
|
|
44
|
-
export async function
|
|
45
|
-
|
|
46
|
-
|
|
124
|
+
export async function exchangeCode(cfg, pkce, code, fetchImpl) {
|
|
125
|
+
const json = await jsonTokenRequest(TOKEN, {
|
|
126
|
+
grant_type: 'authorization_code',
|
|
127
|
+
client_id: cfg.clientId || defaults().clientId,
|
|
128
|
+
code,
|
|
129
|
+
redirect_uri: cfg.redirectUri || defaults().redirectUri,
|
|
130
|
+
code_verifier: pkce.verifier,
|
|
131
|
+
state: pkce.state,
|
|
132
|
+
}, fetchImpl)
|
|
133
|
+
return decorate(tokenBlobFromOAuth(json), fetchImpl)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function refresh(cfg, blob, fetchImpl) {
|
|
137
|
+
const json = await jsonTokenRequest(TOKEN, {
|
|
138
|
+
grant_type: 'refresh_token',
|
|
139
|
+
client_id: cfg.clientId || defaults().clientId,
|
|
140
|
+
refresh_token: blob.refreshToken,
|
|
141
|
+
}, fetchImpl)
|
|
142
|
+
return decorate(tokenBlobFromOAuth(json, { label: blob.label, email: blob.email }), fetchImpl)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export async function listModels(_blob, cfg) {
|
|
146
|
+
const customModels = (cfg && Array.isArray(cfg.models) && cfg.models.length) ? cfg.models : null
|
|
147
|
+
if (customModels) {
|
|
148
|
+
const enriched = customModels.map((m) => {
|
|
149
|
+
const mid = typeof m === 'string' ? m : m.id
|
|
150
|
+
const found = PERPLEXITY_MODELS.find((p) => p.id === mid)
|
|
151
|
+
return found ? { ...found, ...(typeof m === 'object' ? m : {}) } : (typeof m === 'string' ? { id: m, name: m } : m)
|
|
152
|
+
})
|
|
153
|
+
return modelCatalog(id, enriched)
|
|
47
154
|
}
|
|
48
|
-
return
|
|
155
|
+
return modelCatalog(id, PERPLEXITY_MODELS)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export async function usage(blob, _cfg, fetchImpl) {
|
|
159
|
+
if (!blob || (!blob.accessToken && !blob.apiKey)) return null
|
|
160
|
+
const key = blob.accessToken || blob.apiKey
|
|
161
|
+
const impl = fetchImpl || fetch
|
|
162
|
+
try {
|
|
163
|
+
const res = await impl(USAGE, {
|
|
164
|
+
headers: {
|
|
165
|
+
Authorization: `Bearer ${key}`,
|
|
166
|
+
Accept: 'application/json',
|
|
167
|
+
},
|
|
168
|
+
})
|
|
169
|
+
if (res.ok) {
|
|
170
|
+
const d = await readJson(res)
|
|
171
|
+
return {
|
|
172
|
+
usedPercent: typeof d.used_percent === 'number' ? d.used_percent : null,
|
|
173
|
+
plan: d.plan || blob.plan || 'Perplexity Pro',
|
|
174
|
+
timestamp: Date.now(),
|
|
175
|
+
quota: d.quota ? { used: d.quota.used, limit: d.quota.limit, resetAt: d.quota.reset_at } : undefined,
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
} catch { /* usage endpoint is optional */ }
|
|
179
|
+
return { usedPercent: null, plan: blob.plan || 'Perplexity Pro', timestamp: Date.now() }
|
|
49
180
|
}
|
|
50
181
|
|
|
51
182
|
export async function* streamOnce({ blob, options, fetchImpl, headers, config, signal }) {
|
|
@@ -57,7 +188,7 @@ export async function* streamOnce({ blob, options, fetchImpl, headers, config, s
|
|
|
57
188
|
const body = {
|
|
58
189
|
model: options.model || 'sonar-pro',
|
|
59
190
|
messages: openaiMessages(options),
|
|
60
|
-
stream: true
|
|
191
|
+
stream: true,
|
|
61
192
|
}
|
|
62
193
|
|
|
63
194
|
const res = await impl(url, {
|
|
@@ -65,10 +196,10 @@ export async function* streamOnce({ blob, options, fetchImpl, headers, config, s
|
|
|
65
196
|
headers: {
|
|
66
197
|
...headers,
|
|
67
198
|
'Authorization': `Bearer ${key}`,
|
|
68
|
-
'Content-Type': 'application/json'
|
|
199
|
+
'Content-Type': 'application/json',
|
|
69
200
|
},
|
|
70
201
|
body: JSON.stringify(body),
|
|
71
|
-
signal
|
|
202
|
+
signal,
|
|
72
203
|
})
|
|
73
204
|
|
|
74
205
|
if (!res.ok) throw httpError(res.status, await res.text())
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-subscriptions",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.82",
|
|
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",
|