@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.
Files changed (50) hide show
  1. package/README.md +13 -0
  2. package/README.ru.md +13 -0
  3. package/README.zh.md +13 -0
  4. package/lib/accounts-view.js +101 -5
  5. package/lib/accounts.js +150 -11
  6. package/lib/adapter.js +3 -0
  7. package/lib/alerts.js +35 -0
  8. package/lib/analytics.js +31 -2
  9. package/lib/billing.js +99 -0
  10. package/lib/blob.js +18 -0
  11. package/lib/capabilities.js +208 -0
  12. package/lib/client.js +944 -828
  13. package/lib/config-schema.js +46 -4
  14. package/lib/diagnostic-wizard.js +2 -5
  15. package/lib/diagnostics.js +2 -2
  16. package/lib/doctor.js +163 -0
  17. package/lib/error-buffer.js +2 -2
  18. package/lib/forecast.js +56 -0
  19. package/lib/headless-auth.js +379 -0
  20. package/lib/history.js +36 -0
  21. package/lib/http.js +26 -4
  22. package/lib/index.js +98 -11
  23. package/lib/instructions.js +138 -0
  24. package/lib/jwt.js +84 -17
  25. package/lib/loopback.js +3 -2
  26. package/lib/mask.js +16 -0
  27. package/lib/oauth-pending.js +12 -0
  28. package/lib/pricing.js +89 -0
  29. package/lib/refs.js +2 -0
  30. package/lib/reset-clock.js +55 -0
  31. package/lib/rotate.js +18 -4
  32. package/lib/routes/accounts.js +203 -0
  33. package/lib/routes/oauth.js +44 -2
  34. package/lib/routes/proxy.js +14 -0
  35. package/lib/routes/status.js +99 -5
  36. package/lib/scope-inspector.js +51 -0
  37. package/lib/stream-rotate.js +31 -10
  38. package/lib/updater.js +21 -0
  39. package/lib/vault-sync.js +412 -0
  40. package/lib/vault.js +1 -1
  41. package/lib/vendor-factory.js +6 -2
  42. package/lib/vendors/antigravity.js +11 -2
  43. package/lib/vendors/claude.js +56 -8
  44. package/lib/vendors/codex.js +34 -6
  45. package/lib/vendors/copilot-cli.js +8 -1
  46. package/lib/vendors/ernie.js +9 -2
  47. package/lib/vendors/grok.js +66 -6
  48. package/lib/vendors/jetbrains.js +133 -13
  49. package/lib/vendors/perplexity.js +148 -17
  50. 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 { openaiMessages } from '../messages.js'
3
- import { openaiChatStream, httpError } from '../wire.js'
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
- inputModalities: ['text']
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
- return 'https://www.perplexity.ai/settings/api'
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
- export async function listModels() {
41
- return PERPLEXITY_MODELS
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 usage(blob) {
45
- if (blob && (blob.accessToken || blob.apiKey)) {
46
- return { usedPercent: null, plan: 'Perplexity Pro', timestamp: Date.now() }
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 null
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.71",
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",