hedgequantx 2.9.2 → 2.9.4

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.2",
3
+ "version": "2.9.4",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -34,8 +34,8 @@ const AI_PROVIDERS = [
34
34
  { id: 'iflow', name: 'iFlow (DeepSeek/GLM)', color: 'yellow', supportsOAuth: true, supportsApiKey: true },
35
35
  // API Key only (no OAuth - uses LLM Proxy via LiteLLM)
36
36
  { id: 'minimax', name: 'MiniMax', color: 'magenta', supportsOAuth: false, supportsApiKey: true },
37
+ { id: 'deepseek', name: 'DeepSeek', color: 'blue', supportsOAuth: false, supportsApiKey: true },
37
38
  { id: 'mistral', name: 'Mistral AI', color: 'yellow', supportsOAuth: false, supportsApiKey: true },
38
- { id: 'groq', name: 'Groq', color: 'cyan', supportsOAuth: false, supportsApiKey: true },
39
39
  { id: 'xai', name: 'xAI (Grok)', color: 'white', supportsOAuth: false, supportsApiKey: true },
40
40
  { id: 'openrouter', name: 'OpenRouter', color: 'gray', supportsOAuth: false, supportsApiKey: true },
41
41
  ];
@@ -16,8 +16,8 @@ const API_ENDPOINTS = {
16
16
  openai: 'https://api.openai.com/v1/models',
17
17
  google: 'https://generativelanguage.googleapis.com/v1beta/models',
18
18
  minimax: null, // No /models API - uses MINIMAX_MODELS (see RULES.md exception)
19
+ deepseek: 'https://api.deepseek.com/v1/models',
19
20
  mistral: 'https://api.mistral.ai/v1/models',
20
- groq: 'https://api.groq.com/openai/v1/models',
21
21
  xai: 'https://api.x.ai/v1/models',
22
22
  openrouter: 'https://openrouter.ai/api/v1/models',
23
23
  };
@@ -98,9 +98,7 @@ const getAuthHeaders = (providerId, apiKey) => {
98
98
  case 'openai':
99
99
  case 'deepseek':
100
100
  case 'minimax':
101
- case 'groq':
102
101
  case 'xai':
103
- case 'perplexity':
104
102
  case 'openrouter':
105
103
  case 'mistral':
106
104
  return { 'Authorization': `Bearer ${apiKey}` };
@@ -237,16 +235,6 @@ const parseModelsResponse = (providerId, data) => {
237
235
  }));
238
236
  break;
239
237
 
240
- case 'groq':
241
- // Groq format: { data: [{ id, ... }] }
242
- models = (data.data || [])
243
- .filter(m => m.id && !shouldExcludeModel(m.id))
244
- .map(m => ({
245
- id: m.id,
246
- name: m.id
247
- }));
248
- break;
249
-
250
238
  case 'deepseek':
251
239
  // DeepSeek format: { data: [{ id, ... }] } - OpenAI compatible
252
240
  models = (data.data || [])
@@ -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
+ * - CLIProxy (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:
@@ -39,28 +40,105 @@ const checkCliproxyRunning = async () => {
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: 'CLIProxy 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('CLIProxy Status', chalk.green('✓ RUNNING')));
331
+ } else {
332
+ lines.push(dottedLine('CLIProxy Status', 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('✗ CLIProxy not running - some agents cannot be verified'),
270
375
  success: false
271
376
  };
272
377
  }