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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "2.9.3",
3
+ "version": "2.9.5",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -1,4 +1,4 @@
1
- /** AI Agents Configuration Page - CLIProxy (OAuth) + LLM Proxy (API Key) */
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
- // CLIProxyAPI (port 8317): OAuth for Anthropic, OpenAI, Google, Qwen, iFlow
27
- // LLM Proxy (port 8318): API Key for all providers via LiteLLM
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 CLIProxy connection (with auto-install) */
119
+ /** Handle HQX Connector connection (with auto-install) */
120
120
  const handleCliProxyConnection = async (provider, config, boxWidth) => {
121
121
  console.log();
122
- // Check/install CLIProxyAPI
122
+ // Check/install HQX Connector
123
123
  if (!cliproxy.isInstalled()) {
124
- console.log(chalk.yellow(' CLIPROXYAPI NOT INSTALLED. INSTALLING...'));
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('CLIPROXYAPI INSTALLED');
128
+ spinner.succeed('HQX CONNECTOR INSTALLED');
129
129
  }
130
- // Check/start CLIProxy
130
+ // Check/start HQX Connector
131
131
  let status = await cliproxy.isRunning();
132
132
  if (!status.running) {
133
- const spinner = ora({ text: 'STARTING CLIPROXYAPI...', color: 'yellow' }).start();
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('CLIPROXYAPI STARTED');
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 CLIPROXYAPI...'));
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(' ✓ CLIPROXYAPI RUNNING'));
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
- * Two verification points:
8
- * 1. [T] TEST in AI Agents menu - manual verification
9
- * 2. Pre-check before algo launch - automatic verification
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: 'CLIProxy not running' };
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
- // Only test CLIProxy connections for now
52
- if (agent.connectionType !== 'cliproxy') {
53
- // For API key connections, we would need different logic
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
- // Send test prompt with short timeout
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
- // Step 1: Check CLIProxy
170
- results.cliproxy = await checkCliproxyRunning();
248
+ // Check if any agent needs CLIProxy (non-apikey connection)
249
+ results.needsCliproxy = agents.some(a => a.connectionType !== 'apikey');
171
250
 
172
- if (!results.cliproxy.success) {
173
- results.summary.total = agents.length;
174
- results.summary.failed = agents.length;
175
- return results;
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.cliproxy.success) {
226
- lines.push(dottedLine('CLIProxy Status', chalk.green('✓ RUNNING')));
227
- } else {
228
- lines.push(dottedLine('CLIProxy Status', chalk.red('✗ NOT RUNNING')));
229
- lines.push(chalk.red(` Error: ${results.cliproxy.error}`));
230
- return lines;
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 (!results.cliproxy.success) {
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('✗ CLIProxy not running - cannot verify agents'),
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 CLIProxyAPI...', 0);
100
+ if (onProgress) onProgress('Downloading HQX Connector...', 0);
101
101
  await downloadFile(download.url, archivePath, (percent) => {
102
- if (onProgress) onProgress('Downloading CLIProxyAPI...', percent);
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: 'CLIProxyAPI not installed', pid: null };
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 CLIProxyAPI';
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 CLIProxyAPI...', 0);
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 CLIProxyAPI...', 100);
283
+ if (onProgress) onProgress('Starting HQX Connector...', 100);
284
284
  return start();
285
285
  };
286
286