hedgequantx 2.9.3 → 2.9.5
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
CHANGED
package/src/pages/ai-agents.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** AI Agents Configuration Page -
|
|
1
|
+
/** AI Agents Configuration Page - HQX Connector (OAuth) + API Key */
|
|
2
2
|
|
|
3
3
|
const chalk = require('chalk');
|
|
4
4
|
const os = require('os');
|
|
@@ -23,8 +23,8 @@ const CONFIG_DIR = path.join(os.homedir(), '.hqx');
|
|
|
23
23
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'ai-config.json');
|
|
24
24
|
|
|
25
25
|
// AI Providers list with OAuth (paid plan) and API Key support
|
|
26
|
-
//
|
|
27
|
-
//
|
|
26
|
+
// HQX Connector (port 8317): OAuth for Anthropic, OpenAI, Google, Qwen, iFlow
|
|
27
|
+
// Direct API Key: For MiniMax, DeepSeek, Mistral, xAI, OpenRouter
|
|
28
28
|
const AI_PROVIDERS = [
|
|
29
29
|
// OAuth + API Key supported (can use paid plan OR API key)
|
|
30
30
|
{ id: 'anthropic', name: 'Anthropic (Claude)', color: 'magenta', supportsOAuth: true, supportsApiKey: true },
|
|
@@ -116,33 +116,33 @@ const waitForProcessExit = (cp, timeoutMs = 15000, intervalMs = 500) => new Prom
|
|
|
116
116
|
}, intervalMs);
|
|
117
117
|
});
|
|
118
118
|
|
|
119
|
-
/** Handle
|
|
119
|
+
/** Handle HQX Connector connection (with auto-install) */
|
|
120
120
|
const handleCliProxyConnection = async (provider, config, boxWidth) => {
|
|
121
121
|
console.log();
|
|
122
|
-
// Check/install
|
|
122
|
+
// Check/install HQX Connector
|
|
123
123
|
if (!cliproxy.isInstalled()) {
|
|
124
|
-
console.log(chalk.yellow('
|
|
124
|
+
console.log(chalk.yellow(' HQX CONNECTOR NOT INSTALLED. INSTALLING...'));
|
|
125
125
|
const spinner = ora({ text: 'DOWNLOADING...', color: 'yellow' }).start();
|
|
126
126
|
const installResult = await cliproxy.install((msg, percent) => { spinner.text = `${msg.toUpperCase()} ${percent}%`; });
|
|
127
127
|
if (!installResult.success) { spinner.fail(`INSTALL FAILED: ${installResult.error}`); await prompts.waitForEnter(); return false; }
|
|
128
|
-
spinner.succeed('
|
|
128
|
+
spinner.succeed('HQX CONNECTOR INSTALLED');
|
|
129
129
|
}
|
|
130
|
-
// Check/start
|
|
130
|
+
// Check/start HQX Connector
|
|
131
131
|
let status = await cliproxy.isRunning();
|
|
132
132
|
if (!status.running) {
|
|
133
|
-
const spinner = ora({ text: 'STARTING
|
|
133
|
+
const spinner = ora({ text: 'STARTING HQX CONNECTOR...', color: 'yellow' }).start();
|
|
134
134
|
const startResult = await cliproxy.start();
|
|
135
135
|
if (!startResult.success) { spinner.fail(`START FAILED: ${startResult.error}`); await prompts.waitForEnter(); return false; }
|
|
136
|
-
spinner.succeed('
|
|
136
|
+
spinner.succeed('HQX CONNECTOR STARTED');
|
|
137
137
|
} else {
|
|
138
138
|
const cfgPath = path.join(os.homedir(), '.hqx', 'cliproxy', 'config.yaml');
|
|
139
139
|
if (!fs.existsSync(cfgPath)) {
|
|
140
|
-
console.log(chalk.yellow(' RESTARTING
|
|
140
|
+
console.log(chalk.yellow(' RESTARTING HQX CONNECTOR...'));
|
|
141
141
|
await cliproxy.stop();
|
|
142
142
|
const res = await cliproxy.start();
|
|
143
143
|
if (!res.success) { console.log(chalk.red(` RESTART FAILED: ${res.error}`)); await prompts.waitForEnter(); return false; }
|
|
144
144
|
console.log(chalk.green(' ✓ RESTARTED'));
|
|
145
|
-
} else console.log(chalk.green(' ✓
|
|
145
|
+
} else console.log(chalk.green(' ✓ HQX CONNECTOR RUNNING'));
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
// First, check if models are already available (existing auth)
|
|
@@ -4,12 +4,13 @@
|
|
|
4
4
|
* Verifies that AI agents are properly connected and responding
|
|
5
5
|
* before allowing algo trading with AI supervision.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Supports both connection types:
|
|
8
|
+
* - HQX Connector (OAuth) for Anthropic, OpenAI, Google, Qwen, iFlow
|
|
9
|
+
* - Direct API Key for MiniMax, DeepSeek, Mistral, xAI, OpenRouter
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
const cliproxy = require('../cliproxy');
|
|
13
|
+
const https = require('https');
|
|
13
14
|
|
|
14
15
|
/** Test prompt to verify agent understands directive format */
|
|
15
16
|
const TEST_PROMPT = `You are being tested. Respond ONLY with this exact JSON, nothing else:
|
|
@@ -33,34 +34,111 @@ const checkCliproxyRunning = async () => {
|
|
|
33
34
|
return { success: true, latency, error: null };
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
return { success: false, latency, error: '
|
|
37
|
+
return { success: false, latency, error: 'HQX Connector not running' };
|
|
37
38
|
} catch (error) {
|
|
38
39
|
return { success: false, latency: Date.now() - startTime, error: error.message };
|
|
39
40
|
}
|
|
40
41
|
};
|
|
41
42
|
|
|
43
|
+
/**
|
|
44
|
+
* API endpoints for direct API key providers
|
|
45
|
+
*/
|
|
46
|
+
const API_CHAT_ENDPOINTS = {
|
|
47
|
+
minimax: 'https://api.minimax.io/v1/chat/completions',
|
|
48
|
+
deepseek: 'https://api.deepseek.com/v1/chat/completions',
|
|
49
|
+
mistral: 'https://api.mistral.ai/v1/chat/completions',
|
|
50
|
+
xai: 'https://api.x.ai/v1/chat/completions',
|
|
51
|
+
openrouter: 'https://openrouter.ai/api/v1/chat/completions',
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Test connection via direct API key
|
|
56
|
+
* @param {Object} agent - Agent config
|
|
57
|
+
* @returns {Promise<Object>} { success, latency, formatValid, error }
|
|
58
|
+
*/
|
|
59
|
+
const testApiKeyConnection = (agent) => {
|
|
60
|
+
return new Promise((resolve) => {
|
|
61
|
+
const startTime = Date.now();
|
|
62
|
+
const endpoint = API_CHAT_ENDPOINTS[agent.provider];
|
|
63
|
+
|
|
64
|
+
if (!endpoint || !agent.apiKey) {
|
|
65
|
+
resolve({ success: false, latency: 0, formatValid: false, error: 'Missing endpoint or API key' });
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const url = new URL(endpoint);
|
|
70
|
+
const body = JSON.stringify({
|
|
71
|
+
model: agent.modelId,
|
|
72
|
+
messages: [{ role: 'user', content: TEST_PROMPT }],
|
|
73
|
+
max_tokens: 100
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const options = {
|
|
77
|
+
hostname: url.hostname,
|
|
78
|
+
path: url.pathname,
|
|
79
|
+
method: 'POST',
|
|
80
|
+
headers: {
|
|
81
|
+
'Content-Type': 'application/json',
|
|
82
|
+
'Authorization': `Bearer ${agent.apiKey}`,
|
|
83
|
+
'Content-Length': Buffer.byteLength(body)
|
|
84
|
+
},
|
|
85
|
+
timeout: AGENT_TIMEOUT
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const req = https.request(options, (res) => {
|
|
89
|
+
let data = '';
|
|
90
|
+
res.on('data', chunk => data += chunk);
|
|
91
|
+
res.on('end', () => {
|
|
92
|
+
const latency = Date.now() - startTime;
|
|
93
|
+
try {
|
|
94
|
+
const parsed = JSON.parse(data);
|
|
95
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
96
|
+
const content = parsed.choices?.[0]?.message?.content || '';
|
|
97
|
+
const formatResult = validateResponseFormat(content);
|
|
98
|
+
resolve({
|
|
99
|
+
success: formatResult.valid,
|
|
100
|
+
latency,
|
|
101
|
+
formatValid: formatResult.valid,
|
|
102
|
+
error: formatResult.valid ? null : formatResult.error,
|
|
103
|
+
response: content
|
|
104
|
+
});
|
|
105
|
+
} else {
|
|
106
|
+
resolve({ success: false, latency, formatValid: false,
|
|
107
|
+
error: parsed.error?.message || `HTTP ${res.statusCode}` });
|
|
108
|
+
}
|
|
109
|
+
} catch (e) {
|
|
110
|
+
resolve({ success: false, latency, formatValid: false, error: 'Invalid response' });
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
req.on('error', (e) => resolve({
|
|
116
|
+
success: false, latency: Date.now() - startTime, formatValid: false, error: e.message
|
|
117
|
+
}));
|
|
118
|
+
req.on('timeout', () => {
|
|
119
|
+
req.destroy();
|
|
120
|
+
resolve({ success: false, latency: AGENT_TIMEOUT, formatValid: false, error: 'Timeout' });
|
|
121
|
+
});
|
|
122
|
+
req.write(body);
|
|
123
|
+
req.end();
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
|
|
42
127
|
/**
|
|
43
128
|
* Test a single agent connection and response format
|
|
44
|
-
* @param {Object} agent - Agent config { id, provider, modelId, connectionType, ... }
|
|
129
|
+
* @param {Object} agent - Agent config { id, provider, modelId, connectionType, apiKey, ... }
|
|
45
130
|
* @returns {Promise<Object>} { success, latency, formatValid, error }
|
|
46
131
|
*/
|
|
47
132
|
const testAgentConnection = async (agent) => {
|
|
48
133
|
const startTime = Date.now();
|
|
49
134
|
|
|
50
135
|
try {
|
|
51
|
-
//
|
|
52
|
-
if (agent.connectionType
|
|
53
|
-
|
|
54
|
-
// For now, mark as needing CLIProxy
|
|
55
|
-
return {
|
|
56
|
-
success: false,
|
|
57
|
-
latency: 0,
|
|
58
|
-
formatValid: false,
|
|
59
|
-
error: 'Only CLIProxy connections supported for pre-check'
|
|
60
|
-
};
|
|
136
|
+
// Route based on connection type
|
|
137
|
+
if (agent.connectionType === 'apikey') {
|
|
138
|
+
return await testApiKeyConnection(agent);
|
|
61
139
|
}
|
|
62
140
|
|
|
63
|
-
//
|
|
141
|
+
// CLIProxy connection
|
|
64
142
|
const result = await cliproxy.chat(agent.provider, agent.modelId, TEST_PROMPT, AGENT_TIMEOUT);
|
|
65
143
|
const latency = Date.now() - startTime;
|
|
66
144
|
|
|
@@ -156,29 +234,53 @@ const validateResponseFormat = (content) => {
|
|
|
156
234
|
/**
|
|
157
235
|
* Run pre-flight check on all agents
|
|
158
236
|
* @param {Array} agents - Array of agent configs
|
|
159
|
-
* @returns {Promise<Object>} { success, cliproxy, agents, summary }
|
|
237
|
+
* @returns {Promise<Object>} { success, cliproxy, needsCliproxy, agents, summary }
|
|
160
238
|
*/
|
|
161
239
|
const runPreflightCheck = async (agents) => {
|
|
162
240
|
const results = {
|
|
163
241
|
success: false,
|
|
164
242
|
cliproxy: null,
|
|
243
|
+
needsCliproxy: false,
|
|
165
244
|
agents: [],
|
|
166
245
|
summary: { total: 0, passed: 0, failed: 0 }
|
|
167
246
|
};
|
|
168
247
|
|
|
169
|
-
//
|
|
170
|
-
results.
|
|
248
|
+
// Check if any agent needs CLIProxy (non-apikey connection)
|
|
249
|
+
results.needsCliproxy = agents.some(a => a.connectionType !== 'apikey');
|
|
171
250
|
|
|
172
|
-
if
|
|
173
|
-
|
|
174
|
-
results.
|
|
175
|
-
|
|
251
|
+
// Step 1: Check CLIProxy only if needed
|
|
252
|
+
if (results.needsCliproxy) {
|
|
253
|
+
results.cliproxy = await checkCliproxyRunning();
|
|
254
|
+
if (!results.cliproxy.success) {
|
|
255
|
+
// Mark only CLIProxy agents as failed, still test API Key agents
|
|
256
|
+
results.summary.total = agents.length;
|
|
257
|
+
}
|
|
258
|
+
} else {
|
|
259
|
+
// No CLIProxy needed, mark as success
|
|
260
|
+
results.cliproxy = { success: true, latency: 0, error: null, notNeeded: true };
|
|
176
261
|
}
|
|
177
262
|
|
|
178
263
|
// Step 2: Test each agent
|
|
179
264
|
results.summary.total = agents.length;
|
|
180
265
|
|
|
181
266
|
for (const agent of agents) {
|
|
267
|
+
// Skip CLIProxy agents if CLIProxy is not running
|
|
268
|
+
if (agent.connectionType !== 'apikey' && !results.cliproxy.success) {
|
|
269
|
+
results.agents.push({
|
|
270
|
+
id: agent.id,
|
|
271
|
+
name: agent.name,
|
|
272
|
+
provider: agent.provider,
|
|
273
|
+
modelId: agent.modelId,
|
|
274
|
+
connectionType: agent.connectionType,
|
|
275
|
+
success: false,
|
|
276
|
+
latency: 0,
|
|
277
|
+
formatValid: false,
|
|
278
|
+
error: 'HQX Connector not running'
|
|
279
|
+
});
|
|
280
|
+
results.summary.failed++;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
|
|
182
284
|
const agentResult = await testAgentConnection(agent);
|
|
183
285
|
|
|
184
286
|
results.agents.push({
|
|
@@ -186,6 +288,7 @@ const runPreflightCheck = async (agents) => {
|
|
|
186
288
|
name: agent.name,
|
|
187
289
|
provider: agent.provider,
|
|
188
290
|
modelId: agent.modelId,
|
|
291
|
+
connectionType: agent.connectionType,
|
|
189
292
|
...agentResult
|
|
190
293
|
});
|
|
191
294
|
|
|
@@ -221,13 +324,13 @@ const formatPreflightResults = (results, boxWidth) => {
|
|
|
221
324
|
return ' '.repeat(labelPad) + chalk.white(label) + chalk.gray('.'.repeat(Math.max(3, dotsLen))) + value;
|
|
222
325
|
};
|
|
223
326
|
|
|
224
|
-
// CLIProxy status
|
|
225
|
-
if (results.
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
327
|
+
// CLIProxy status (only show if needed)
|
|
328
|
+
if (results.needsCliproxy) {
|
|
329
|
+
if (results.cliproxy.success) {
|
|
330
|
+
lines.push(dottedLine('HQX Connector', chalk.green('✓ RUNNING')));
|
|
331
|
+
} else {
|
|
332
|
+
lines.push(dottedLine('HQX Connector', chalk.red('✗ NOT RUNNING')));
|
|
333
|
+
}
|
|
231
334
|
}
|
|
232
335
|
|
|
233
336
|
lines.push('');
|
|
@@ -238,8 +341,9 @@ const formatPreflightResults = (results, boxWidth) => {
|
|
|
238
341
|
for (let i = 0; i < results.agents.length; i++) {
|
|
239
342
|
const agent = results.agents[i];
|
|
240
343
|
const num = `[${i + 1}/${results.summary.total}]`;
|
|
344
|
+
const connType = agent.connectionType === 'apikey' ? 'API Key' : 'OAuth';
|
|
241
345
|
|
|
242
|
-
lines.push(chalk.cyan(` ${num} ${agent.name} (${agent.modelId || agent.provider})`));
|
|
346
|
+
lines.push(chalk.cyan(` ${num} ${agent.name} (${agent.modelId || agent.provider}) [${connType}]`));
|
|
243
347
|
|
|
244
348
|
if (agent.success) {
|
|
245
349
|
const latencyStr = `✓ OK ${agent.latency}ms`;
|
|
@@ -264,9 +368,10 @@ const formatPreflightResults = (results, boxWidth) => {
|
|
|
264
368
|
const getPreflightSummary = (results) => {
|
|
265
369
|
const chalk = require('chalk');
|
|
266
370
|
|
|
267
|
-
if
|
|
371
|
+
// Only show CLIProxy error if it was needed and failed
|
|
372
|
+
if (results.needsCliproxy && !results.cliproxy.success) {
|
|
268
373
|
return {
|
|
269
|
-
text: chalk.red('✗
|
|
374
|
+
text: chalk.red('✗ HQX Connector not running - some agents cannot be verified'),
|
|
270
375
|
success: false
|
|
271
376
|
};
|
|
272
377
|
}
|
|
@@ -97,9 +97,9 @@ const install = async (onProgress = null) => {
|
|
|
97
97
|
const archivePath = path.join(INSTALL_DIR, download.filename);
|
|
98
98
|
|
|
99
99
|
// Download
|
|
100
|
-
if (onProgress) onProgress('Downloading
|
|
100
|
+
if (onProgress) onProgress('Downloading HQX Connector...', 0);
|
|
101
101
|
await downloadFile(download.url, archivePath, (percent) => {
|
|
102
|
-
if (onProgress) onProgress('Downloading
|
|
102
|
+
if (onProgress) onProgress('Downloading HQX Connector...', percent);
|
|
103
103
|
});
|
|
104
104
|
|
|
105
105
|
// Extract
|
|
@@ -165,7 +165,7 @@ api-keys:
|
|
|
165
165
|
/** Start CLIProxyAPI */
|
|
166
166
|
const start = async () => {
|
|
167
167
|
if (!isInstalled()) {
|
|
168
|
-
return { success: false, error: '
|
|
168
|
+
return { success: false, error: 'HQX Connector not installed', pid: null };
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
const status = await isRunning();
|
|
@@ -204,7 +204,7 @@ const start = async () => {
|
|
|
204
204
|
return { success: true, error: null, pid: child.pid };
|
|
205
205
|
} else {
|
|
206
206
|
// Read log for error details
|
|
207
|
-
let errorDetail = 'Failed to start
|
|
207
|
+
let errorDetail = 'Failed to start HQX Connector';
|
|
208
208
|
if (fs.existsSync(logPath)) {
|
|
209
209
|
const log = fs.readFileSync(logPath, 'utf8').slice(-500);
|
|
210
210
|
if (log) errorDetail += `: ${log.split('\n').pop()}`;
|
|
@@ -274,13 +274,13 @@ const stop = async () => {
|
|
|
274
274
|
/** Ensure CLIProxyAPI is installed and running */
|
|
275
275
|
const ensureRunning = async (onProgress = null) => {
|
|
276
276
|
if (!isInstalled()) {
|
|
277
|
-
if (onProgress) onProgress('Installing
|
|
277
|
+
if (onProgress) onProgress('Installing HQX Connector...', 0);
|
|
278
278
|
const installResult = await install(onProgress);
|
|
279
279
|
if (!installResult.success) return installResult;
|
|
280
280
|
}
|
|
281
281
|
const status = await isRunning();
|
|
282
282
|
if (status.running) return { success: true, error: null };
|
|
283
|
-
if (onProgress) onProgress('Starting
|
|
283
|
+
if (onProgress) onProgress('Starting HQX Connector...', 100);
|
|
284
284
|
return start();
|
|
285
285
|
};
|
|
286
286
|
|