hedgequantx 2.6.68 → 2.6.69

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.
@@ -0,0 +1,279 @@
1
+ /**
2
+ * Qwen OAuth Authentication (Device Flow)
3
+ *
4
+ * Implements OAuth 2.0 Device Authorization Grant for Qwen Chat subscription.
5
+ * Based on the public OAuth flow used by Qwen Code CLI.
6
+ *
7
+ * Data source: Qwen OAuth API (https://chat.qwen.ai/api/v1/oauth2)
8
+ */
9
+
10
+ const crypto = require('crypto');
11
+ const https = require('https');
12
+
13
+ // Public OAuth Client ID (from Qwen CLI)
14
+ const CLIENT_ID = 'f0304373b74a44d2b584a3fb70ca9e56';
15
+ const DEVICE_CODE_URL = 'https://chat.qwen.ai/api/v1/oauth2/device/code';
16
+ const TOKEN_URL = 'https://chat.qwen.ai/api/v1/oauth2/token';
17
+ const SCOPES = 'openid profile email model.completion';
18
+ const GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:device_code';
19
+
20
+ /**
21
+ * Generate PKCE code verifier and challenge
22
+ * @returns {Object} { verifier: string, challenge: string }
23
+ */
24
+ const generatePKCE = () => {
25
+ const verifier = crypto.randomBytes(32)
26
+ .toString('base64')
27
+ .replace(/\+/g, '-')
28
+ .replace(/\//g, '_')
29
+ .replace(/=/g, '');
30
+
31
+ const challenge = crypto.createHash('sha256')
32
+ .update(verifier)
33
+ .digest('base64')
34
+ .replace(/\+/g, '-')
35
+ .replace(/\//g, '_')
36
+ .replace(/=/g, '');
37
+
38
+ return { verifier, challenge };
39
+ };
40
+
41
+ /**
42
+ * Make HTTPS request
43
+ */
44
+ const makeRequest = (urlStr, options) => {
45
+ return new Promise((resolve, reject) => {
46
+ const url = new URL(urlStr);
47
+ const req = https.request({
48
+ hostname: url.hostname,
49
+ port: url.port || 443,
50
+ path: url.pathname + url.search,
51
+ method: options.method || 'POST',
52
+ headers: options.headers || {}
53
+ }, (res) => {
54
+ let data = '';
55
+ res.on('data', chunk => data += chunk);
56
+ res.on('end', () => {
57
+ try {
58
+ const json = JSON.parse(data);
59
+ if (res.statusCode >= 200 && res.statusCode < 300) {
60
+ resolve(json);
61
+ } else {
62
+ resolve({ ...json, statusCode: res.statusCode }); // Return error response for polling
63
+ }
64
+ } catch (e) {
65
+ reject(new Error(`Invalid JSON response: ${data.substring(0, 200)}`));
66
+ }
67
+ });
68
+ });
69
+
70
+ req.on('error', reject);
71
+
72
+ if (options.body) {
73
+ req.write(options.body);
74
+ }
75
+ req.end();
76
+ });
77
+ };
78
+
79
+ /**
80
+ * Initiate device authorization flow
81
+ * @returns {Promise<Object>} { deviceCode, userCode, verificationUri, verificationUriComplete, expiresIn, interval, verifier }
82
+ */
83
+ const initiateDeviceFlow = async () => {
84
+ try {
85
+ const pkce = generatePKCE();
86
+
87
+ const body = new URLSearchParams({
88
+ client_id: CLIENT_ID,
89
+ scope: SCOPES,
90
+ code_challenge: pkce.challenge,
91
+ code_challenge_method: 'S256'
92
+ }).toString();
93
+
94
+ const response = await makeRequest(DEVICE_CODE_URL, {
95
+ method: 'POST',
96
+ headers: {
97
+ 'Content-Type': 'application/x-www-form-urlencoded',
98
+ 'Accept': 'application/json'
99
+ },
100
+ body
101
+ });
102
+
103
+ if (!response.device_code) {
104
+ return {
105
+ type: 'failed',
106
+ error: 'Device code not found in response'
107
+ };
108
+ }
109
+
110
+ return {
111
+ type: 'success',
112
+ deviceCode: response.device_code,
113
+ userCode: response.user_code,
114
+ verificationUri: response.verification_uri,
115
+ verificationUriComplete: response.verification_uri_complete,
116
+ expiresIn: response.expires_in,
117
+ interval: response.interval || 5,
118
+ verifier: pkce.verifier
119
+ };
120
+ } catch (error) {
121
+ return {
122
+ type: 'failed',
123
+ error: error.message
124
+ };
125
+ }
126
+ };
127
+
128
+ /**
129
+ * Poll for token (single attempt)
130
+ * @param {string} deviceCode - Device code from initiateDeviceFlow
131
+ * @param {string} verifier - PKCE code verifier
132
+ * @returns {Promise<Object>}
133
+ */
134
+ const pollForToken = async (deviceCode, verifier) => {
135
+ try {
136
+ const body = new URLSearchParams({
137
+ grant_type: GRANT_TYPE,
138
+ client_id: CLIENT_ID,
139
+ device_code: deviceCode,
140
+ code_verifier: verifier
141
+ }).toString();
142
+
143
+ const response = await makeRequest(TOKEN_URL, {
144
+ method: 'POST',
145
+ headers: {
146
+ 'Content-Type': 'application/x-www-form-urlencoded',
147
+ 'Accept': 'application/json'
148
+ },
149
+ body
150
+ });
151
+
152
+ // Check for polling errors (RFC 8628)
153
+ if (response.error) {
154
+ switch (response.error) {
155
+ case 'authorization_pending':
156
+ return { type: 'pending' };
157
+ case 'slow_down':
158
+ return { type: 'slow_down' };
159
+ case 'expired_token':
160
+ return { type: 'failed', error: 'Device code expired. Please restart authentication.' };
161
+ case 'access_denied':
162
+ return { type: 'failed', error: 'Authorization denied by user.' };
163
+ default:
164
+ return { type: 'failed', error: response.error_description || response.error };
165
+ }
166
+ }
167
+
168
+ // Success
169
+ if (response.access_token) {
170
+ return {
171
+ type: 'success',
172
+ access: response.access_token,
173
+ refresh: response.refresh_token,
174
+ resourceUrl: response.resource_url,
175
+ expires: Date.now() + (response.expires_in * 1000)
176
+ };
177
+ }
178
+
179
+ return { type: 'pending' };
180
+ } catch (error) {
181
+ return {
182
+ type: 'failed',
183
+ error: error.message
184
+ };
185
+ }
186
+ };
187
+
188
+ /**
189
+ * Refresh access token using refresh token
190
+ * @param {string} refreshTokenValue - The refresh token
191
+ * @returns {Promise<Object>}
192
+ */
193
+ const refreshToken = async (refreshTokenValue) => {
194
+ try {
195
+ const body = new URLSearchParams({
196
+ grant_type: 'refresh_token',
197
+ refresh_token: refreshTokenValue,
198
+ client_id: CLIENT_ID
199
+ }).toString();
200
+
201
+ const response = await makeRequest(TOKEN_URL, {
202
+ method: 'POST',
203
+ headers: {
204
+ 'Content-Type': 'application/x-www-form-urlencoded',
205
+ 'Accept': 'application/json'
206
+ },
207
+ body
208
+ });
209
+
210
+ if (response.error) {
211
+ return {
212
+ type: 'failed',
213
+ error: response.error_description || response.error
214
+ };
215
+ }
216
+
217
+ return {
218
+ type: 'success',
219
+ access: response.access_token,
220
+ refresh: response.refresh_token,
221
+ resourceUrl: response.resource_url,
222
+ expires: Date.now() + (response.expires_in * 1000)
223
+ };
224
+ } catch (error) {
225
+ return {
226
+ type: 'failed',
227
+ error: error.message
228
+ };
229
+ }
230
+ };
231
+
232
+ /**
233
+ * Get valid access token (refresh if expired)
234
+ * @param {Object} oauthData - OAuth data { access, refresh, expires }
235
+ * @returns {Promise<Object>}
236
+ */
237
+ const getValidToken = async (oauthData) => {
238
+ if (!oauthData || !oauthData.refresh) {
239
+ return null;
240
+ }
241
+
242
+ const expirationBuffer = 5 * 60 * 1000; // 5 minutes
243
+ if (oauthData.expires && oauthData.expires > Date.now() + expirationBuffer) {
244
+ return {
245
+ ...oauthData,
246
+ refreshed: false
247
+ };
248
+ }
249
+
250
+ const result = await refreshToken(oauthData.refresh);
251
+ if (result.type === 'success') {
252
+ return {
253
+ access: result.access,
254
+ refresh: result.refresh,
255
+ expires: result.expires,
256
+ refreshed: true
257
+ };
258
+ }
259
+
260
+ return null;
261
+ };
262
+
263
+ /**
264
+ * Check if credentials are OAuth tokens
265
+ */
266
+ const isOAuthCredentials = (credentials) => {
267
+ return credentials && credentials.oauth && credentials.oauth.refresh;
268
+ };
269
+
270
+ module.exports = {
271
+ CLIENT_ID,
272
+ SCOPES,
273
+ generatePKCE,
274
+ initiateDeviceFlow,
275
+ pollForToken,
276
+ refreshToken,
277
+ getValidToken,
278
+ isOAuthCredentials
279
+ };
@@ -66,12 +66,22 @@ const PROVIDERS = {
66
66
 
67
67
  openai: {
68
68
  id: 'openai',
69
- name: 'OPENAI (GPT-4)',
70
- description: 'Direct connection to GPT-4',
69
+ name: 'OPENAI (GPT-4/5)',
70
+ description: 'Plus/Pro or API Key',
71
71
  category: 'direct',
72
72
  models: [], // Fetched from API at runtime
73
73
  defaultModel: null, // Will use first model from API
74
74
  options: [
75
+ {
76
+ id: 'oauth_plus',
77
+ label: 'PLUS/PRO SUBSCRIPTION (OAUTH)',
78
+ description: [
79
+ 'Login with your ChatGPT account',
80
+ 'Unlimited with your plan'
81
+ ],
82
+ fields: ['oauth'],
83
+ authType: 'oauth'
84
+ },
75
85
  {
76
86
  id: 'api_key',
77
87
  label: 'API KEY (PAY-PER-USE)',
@@ -89,11 +99,21 @@ const PROVIDERS = {
89
99
  gemini: {
90
100
  id: 'gemini',
91
101
  name: 'GEMINI (GOOGLE)',
92
- description: 'Direct connection to Gemini',
102
+ description: 'Advanced or API Key',
93
103
  category: 'direct',
94
104
  models: [], // Fetched from API at runtime
95
105
  defaultModel: null, // Will use first model from API
96
106
  options: [
107
+ {
108
+ id: 'oauth_advanced',
109
+ label: 'ADVANCED SUBSCRIPTION (OAUTH)',
110
+ description: [
111
+ 'Login with your Google account',
112
+ 'Unlimited with your plan'
113
+ ],
114
+ fields: ['oauth'],
115
+ authType: 'oauth'
116
+ },
97
117
  {
98
118
  id: 'api_key',
99
119
  label: 'API KEY (FREE TIER)',
@@ -243,15 +263,47 @@ const PROVIDERS = {
243
263
  endpoint: 'https://api.together.xyz/v1'
244
264
  },
245
265
 
266
+ iflow: {
267
+ id: 'iflow',
268
+ name: 'IFLOW',
269
+ description: 'Subscription (OAuth)',
270
+ category: 'direct',
271
+ models: [], // Fetched from API at runtime
272
+ defaultModel: null, // Will use first model from API
273
+ options: [
274
+ {
275
+ id: 'oauth_sub',
276
+ label: 'SUBSCRIPTION (OAUTH)',
277
+ description: [
278
+ 'Login with your iFlow account',
279
+ 'Access to DeepSeek, Kimi, GLM & more'
280
+ ],
281
+ fields: ['oauth'],
282
+ authType: 'oauth'
283
+ }
284
+ ],
285
+ endpoint: 'https://api.iflow.com/v1'
286
+ },
287
+
246
288
 
247
289
  qwen: {
248
290
  id: 'qwen',
249
291
  name: 'QWEN (ALIBABA)',
250
- description: 'Alibaba\'s top AI model',
292
+ description: 'Chat subscription or API Key',
251
293
  category: 'direct',
252
294
  models: [], // Fetched from API at runtime
253
295
  defaultModel: null, // Will use first model from API
254
296
  options: [
297
+ {
298
+ id: 'oauth_chat',
299
+ label: 'CHAT SUBSCRIPTION (OAUTH)',
300
+ description: [
301
+ 'Login with your Qwen account',
302
+ 'Unlimited with your plan'
303
+ ],
304
+ fields: ['oauth'],
305
+ authType: 'oauth'
306
+ },
255
307
  {
256
308
  id: 'api_key',
257
309
  label: 'API KEY (DASHSCOPE)',