hedgequantx 2.6.102 → 2.6.104
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/package.json +1 -1
- package/src/services/ai/proxy-manager.js +134 -4
package/package.json
CHANGED
|
@@ -27,6 +27,7 @@ const PROXY_BIN = path.join(PROXY_DIR, process.platform === 'win32' ? 'cli-proxy
|
|
|
27
27
|
const PROXY_CONFIG = path.join(PROXY_DIR, 'config.yaml');
|
|
28
28
|
const PROXY_AUTH_DIR = path.join(PROXY_DIR, 'auths');
|
|
29
29
|
const API_KEY = 'hqx-local-key';
|
|
30
|
+
const MANAGEMENT_KEY = 'hqx-mgmt-key';
|
|
30
31
|
|
|
31
32
|
// GitHub release URLs
|
|
32
33
|
const getDownloadUrl = () => {
|
|
@@ -202,6 +203,9 @@ const install = async (onProgress = () => {}) => {
|
|
|
202
203
|
auth-dir: "${PROXY_AUTH_DIR}"
|
|
203
204
|
api-keys:
|
|
204
205
|
- "${API_KEY}"
|
|
206
|
+
remote-management:
|
|
207
|
+
secret-key: "${MANAGEMENT_KEY}"
|
|
208
|
+
allow-remote-management: false
|
|
205
209
|
request-retry: 3
|
|
206
210
|
quota-exceeded:
|
|
207
211
|
switch-project: true
|
|
@@ -267,18 +271,92 @@ const stop = async () => {
|
|
|
267
271
|
});
|
|
268
272
|
};
|
|
269
273
|
|
|
274
|
+
/**
|
|
275
|
+
* Check if config has correct management key (plain text, not hashed)
|
|
276
|
+
* CLIProxyAPI expects plain text key in config, it does bcrypt hashing internally
|
|
277
|
+
* @returns {boolean} true if config is correct, false if needs update
|
|
278
|
+
*/
|
|
279
|
+
const isConfigValid = () => {
|
|
280
|
+
if (!fs.existsSync(PROXY_CONFIG)) return false;
|
|
281
|
+
|
|
282
|
+
try {
|
|
283
|
+
const config = fs.readFileSync(PROXY_CONFIG, 'utf8');
|
|
284
|
+
|
|
285
|
+
// Check if management key section exists
|
|
286
|
+
if (!config.includes('remote-management:') || !config.includes('secret-key:')) {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Check if key is hashed (bcrypt hashes start with $2a$, $2b$, or $2y$)
|
|
291
|
+
// Plain text key should be 'hqx-mgmt-key', not a bcrypt hash
|
|
292
|
+
if (config.includes('$2a$') || config.includes('$2b$') || config.includes('$2y$')) {
|
|
293
|
+
return false; // Config has hashed key, needs to be plain text
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Check if it has our expected management key
|
|
297
|
+
if (!config.includes(MANAGEMENT_KEY)) {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return true;
|
|
302
|
+
} catch (e) {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Rewrite config file with correct settings
|
|
309
|
+
*/
|
|
310
|
+
const rewriteConfig = () => {
|
|
311
|
+
const config = `port: ${PROXY_PORT}
|
|
312
|
+
auth-dir: "${PROXY_AUTH_DIR}"
|
|
313
|
+
api-keys:
|
|
314
|
+
- "${API_KEY}"
|
|
315
|
+
remote-management:
|
|
316
|
+
secret-key: "${MANAGEMENT_KEY}"
|
|
317
|
+
allow-remote-management: false
|
|
318
|
+
request-retry: 3
|
|
319
|
+
quota-exceeded:
|
|
320
|
+
switch-project: true
|
|
321
|
+
switch-preview-model: true
|
|
322
|
+
`;
|
|
323
|
+
|
|
324
|
+
// Ensure directory exists
|
|
325
|
+
if (!fs.existsSync(PROXY_DIR)) {
|
|
326
|
+
fs.mkdirSync(PROXY_DIR, { recursive: true });
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
fs.writeFileSync(PROXY_CONFIG, config);
|
|
330
|
+
};
|
|
331
|
+
|
|
270
332
|
/**
|
|
271
333
|
* Ensure CLIProxyAPI is installed and running
|
|
272
334
|
* @param {Function} onProgress - Progress callback
|
|
273
335
|
*/
|
|
274
336
|
const ensureRunning = async (onProgress = () => {}) => {
|
|
337
|
+
// Check if config needs to be fixed (e.g., has hashed key instead of plain text)
|
|
338
|
+
const configValid = isConfigValid();
|
|
339
|
+
|
|
275
340
|
if (await isRunning()) {
|
|
341
|
+
if (!configValid) {
|
|
342
|
+
// Config is invalid but proxy is running - need to restart with correct config
|
|
343
|
+
onProgress('Updating proxy configuration...');
|
|
344
|
+
rewriteConfig();
|
|
345
|
+
await stop();
|
|
346
|
+
await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for process to fully stop
|
|
347
|
+
onProgress('Restarting proxy with updated config...');
|
|
348
|
+
await start();
|
|
349
|
+
}
|
|
276
350
|
return true;
|
|
277
351
|
}
|
|
278
352
|
|
|
279
353
|
if (!isInstalled()) {
|
|
280
354
|
onProgress('Installing AI proxy (one-time setup)...');
|
|
281
355
|
await install(onProgress);
|
|
356
|
+
} else if (!configValid) {
|
|
357
|
+
// Binary exists but config is invalid - fix it
|
|
358
|
+
onProgress('Fixing proxy configuration...');
|
|
359
|
+
rewriteConfig();
|
|
282
360
|
}
|
|
283
361
|
|
|
284
362
|
onProgress('Starting AI proxy...');
|
|
@@ -288,7 +366,7 @@ const ensureRunning = async (onProgress = () => {}) => {
|
|
|
288
366
|
};
|
|
289
367
|
|
|
290
368
|
/**
|
|
291
|
-
* Make request to local proxy
|
|
369
|
+
* Make request to local proxy (API endpoints)
|
|
292
370
|
*/
|
|
293
371
|
const proxyRequest = (method, endpoint, body = null) => {
|
|
294
372
|
return new Promise((resolve, reject) => {
|
|
@@ -329,6 +407,58 @@ const proxyRequest = (method, endpoint, body = null) => {
|
|
|
329
407
|
});
|
|
330
408
|
};
|
|
331
409
|
|
|
410
|
+
/**
|
|
411
|
+
* Make request to local proxy (Management API endpoints)
|
|
412
|
+
* Uses management key for authentication
|
|
413
|
+
*/
|
|
414
|
+
const managementRequest = (method, endpoint, body = null) => {
|
|
415
|
+
return new Promise((resolve, reject) => {
|
|
416
|
+
const options = {
|
|
417
|
+
hostname: '127.0.0.1',
|
|
418
|
+
port: PROXY_PORT,
|
|
419
|
+
path: endpoint,
|
|
420
|
+
method,
|
|
421
|
+
headers: {
|
|
422
|
+
'Authorization': `Bearer ${MANAGEMENT_KEY}`,
|
|
423
|
+
'Content-Type': 'application/json'
|
|
424
|
+
},
|
|
425
|
+
timeout: 30000
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
const req = http.request(options, (res) => {
|
|
429
|
+
let data = '';
|
|
430
|
+
res.on('data', chunk => data += chunk);
|
|
431
|
+
res.on('end', () => {
|
|
432
|
+
try {
|
|
433
|
+
const json = JSON.parse(data);
|
|
434
|
+
if (res.statusCode >= 400) {
|
|
435
|
+
reject(new Error(json.error || `HTTP ${res.statusCode}`));
|
|
436
|
+
} else {
|
|
437
|
+
resolve(json);
|
|
438
|
+
}
|
|
439
|
+
} catch (e) {
|
|
440
|
+
if (res.statusCode >= 400) {
|
|
441
|
+
reject(new Error(`HTTP ${res.statusCode}: ${data}`));
|
|
442
|
+
} else {
|
|
443
|
+
resolve(data);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
req.on('error', reject);
|
|
450
|
+
req.on('timeout', () => {
|
|
451
|
+
req.destroy();
|
|
452
|
+
reject(new Error('Request timeout'));
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
if (body) {
|
|
456
|
+
req.write(JSON.stringify(body));
|
|
457
|
+
}
|
|
458
|
+
req.end();
|
|
459
|
+
});
|
|
460
|
+
};
|
|
461
|
+
|
|
332
462
|
/**
|
|
333
463
|
* Get OAuth authorization URL for a provider
|
|
334
464
|
* @param {string} provider - Provider ID (anthropic, openai, gemini, qwen, iflow)
|
|
@@ -350,7 +480,7 @@ const getAuthUrl = async (provider) => {
|
|
|
350
480
|
throw new Error(`Unknown provider: ${provider}`);
|
|
351
481
|
}
|
|
352
482
|
|
|
353
|
-
const response = await
|
|
483
|
+
const response = await managementRequest('GET', endpoint);
|
|
354
484
|
|
|
355
485
|
if (response.status !== 'ok') {
|
|
356
486
|
throw new Error(response.error || 'Failed to get auth URL');
|
|
@@ -368,7 +498,7 @@ const getAuthUrl = async (provider) => {
|
|
|
368
498
|
* @returns {Promise<{status: string, error?: string}>}
|
|
369
499
|
*/
|
|
370
500
|
const pollAuthStatus = async (state) => {
|
|
371
|
-
const response = await
|
|
501
|
+
const response = await managementRequest('GET', `/v0/management/get-auth-status?state=${state}`);
|
|
372
502
|
return response;
|
|
373
503
|
};
|
|
374
504
|
|
|
@@ -422,7 +552,7 @@ const getAuthFiles = async () => {
|
|
|
422
552
|
await ensureRunning();
|
|
423
553
|
|
|
424
554
|
try {
|
|
425
|
-
const response = await
|
|
555
|
+
const response = await managementRequest('GET', '/v0/management/auth-files');
|
|
426
556
|
return response.files || [];
|
|
427
557
|
} catch (e) {
|
|
428
558
|
return [];
|