hedgequantx 2.6.163 → 2.7.0
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/README.md +15 -88
- package/bin/cli.js +0 -11
- package/dist/lib/api.jsc +0 -0
- package/dist/lib/api2.jsc +0 -0
- package/dist/lib/core.jsc +0 -0
- package/dist/lib/core2.jsc +0 -0
- package/dist/lib/data.js +1 -1
- package/dist/lib/data.jsc +0 -0
- package/dist/lib/data2.jsc +0 -0
- package/dist/lib/decoder.jsc +0 -0
- package/dist/lib/m/mod1.jsc +0 -0
- package/dist/lib/m/mod2.jsc +0 -0
- package/dist/lib/n/r1.jsc +0 -0
- package/dist/lib/n/r2.jsc +0 -0
- package/dist/lib/n/r3.jsc +0 -0
- package/dist/lib/n/r4.jsc +0 -0
- package/dist/lib/n/r5.jsc +0 -0
- package/dist/lib/n/r6.jsc +0 -0
- package/dist/lib/n/r7.jsc +0 -0
- package/dist/lib/o/util1.jsc +0 -0
- package/dist/lib/o/util2.jsc +0 -0
- package/package.json +6 -3
- package/src/app.js +40 -162
- package/src/config/constants.js +31 -33
- package/src/config/propfirms.js +13 -217
- package/src/config/settings.js +0 -43
- package/src/lib/api.js +198 -0
- package/src/lib/api2.js +353 -0
- package/src/lib/core.js +539 -0
- package/src/lib/core2.js +341 -0
- package/src/lib/data.js +555 -0
- package/src/lib/data2.js +492 -0
- package/src/lib/decoder.js +599 -0
- package/src/lib/m/s1.js +804 -0
- package/src/lib/m/s2.js +34 -0
- package/src/lib/n/r1.js +454 -0
- package/src/lib/n/r2.js +514 -0
- package/src/lib/n/r3.js +631 -0
- package/src/lib/n/r4.js +401 -0
- package/src/lib/n/r5.js +335 -0
- package/src/lib/n/r6.js +425 -0
- package/src/lib/n/r7.js +530 -0
- package/src/lib/o/l1.js +44 -0
- package/src/lib/o/l2.js +427 -0
- package/src/lib/python-bridge.js +206 -0
- package/src/menus/connect.js +14 -176
- package/src/menus/dashboard.js +65 -110
- package/src/pages/accounts.js +18 -18
- package/src/pages/algo/copy-trading.js +210 -240
- package/src/pages/algo/index.js +41 -104
- package/src/pages/algo/one-account.js +386 -33
- package/src/pages/algo/ui.js +312 -151
- package/src/pages/orders.js +3 -3
- package/src/pages/positions.js +3 -3
- package/src/pages/stats/chart.js +74 -0
- package/src/pages/stats/display.js +228 -0
- package/src/pages/stats/index.js +236 -0
- package/src/pages/stats/metrics.js +213 -0
- package/src/pages/user.js +6 -6
- package/src/services/hqx-server/constants.js +55 -0
- package/src/services/hqx-server/index.js +401 -0
- package/src/services/hqx-server/latency.js +81 -0
- package/src/services/index.js +12 -3
- package/src/services/rithmic/accounts.js +7 -32
- package/src/services/rithmic/connection.js +1 -204
- package/src/services/rithmic/contracts.js +116 -99
- package/src/services/rithmic/handlers.js +21 -196
- package/src/services/rithmic/index.js +63 -120
- package/src/services/rithmic/market.js +31 -0
- package/src/services/rithmic/orders.js +5 -111
- package/src/services/rithmic/protobuf.js +384 -138
- package/src/services/session.js +22 -173
- package/src/ui/box.js +10 -18
- package/src/ui/index.js +1 -3
- package/src/ui/menu.js +1 -1
- package/src/utils/prompts.js +2 -2
- package/dist/lib/m/s1.js +0 -1
- package/src/menus/ai-agent-connect.js +0 -181
- package/src/menus/ai-agent-models.js +0 -219
- package/src/menus/ai-agent-oauth.js +0 -292
- package/src/menus/ai-agent-ui.js +0 -141
- package/src/menus/ai-agent.js +0 -484
- package/src/pages/algo/algo-config.js +0 -195
- package/src/pages/algo/algo-multi.js +0 -801
- package/src/pages/algo/algo-utils.js +0 -58
- package/src/pages/algo/copy-engine.js +0 -449
- package/src/pages/algo/custom-strategy.js +0 -459
- package/src/pages/algo/logger.js +0 -245
- package/src/pages/algo/smart-logs-data.js +0 -218
- package/src/pages/algo/smart-logs.js +0 -387
- package/src/pages/algo/ui-constants.js +0 -144
- package/src/pages/algo/ui-summary.js +0 -184
- package/src/pages/stats-calculations.js +0 -191
- package/src/pages/stats-ui.js +0 -381
- package/src/pages/stats.js +0 -339
- package/src/services/ai/client-analysis.js +0 -194
- package/src/services/ai/client-models.js +0 -333
- package/src/services/ai/client.js +0 -343
- package/src/services/ai/index.js +0 -384
- package/src/services/ai/oauth-anthropic.js +0 -265
- package/src/services/ai/oauth-gemini.js +0 -223
- package/src/services/ai/oauth-iflow.js +0 -269
- package/src/services/ai/oauth-openai.js +0 -233
- package/src/services/ai/oauth-qwen.js +0 -279
- package/src/services/ai/providers/direct-providers.js +0 -323
- package/src/services/ai/providers/index.js +0 -62
- package/src/services/ai/providers/other-providers.js +0 -104
- package/src/services/ai/proxy-install.js +0 -249
- package/src/services/ai/proxy-manager.js +0 -494
- package/src/services/ai/proxy-remote.js +0 -161
- package/src/services/ai/strategy-supervisor.js +0 -1312
- package/src/services/ai/supervisor-data.js +0 -195
- package/src/services/ai/supervisor-optimize.js +0 -215
- package/src/services/ai/supervisor-sync.js +0 -178
- package/src/services/ai/supervisor-utils.js +0 -158
- package/src/services/ai/supervisor.js +0 -484
- package/src/services/ai/validation.js +0 -250
- package/src/services/hqx-server-events.js +0 -110
- package/src/services/hqx-server-handlers.js +0 -217
- package/src/services/hqx-server-latency.js +0 -136
- package/src/services/hqx-server.js +0 -403
- package/src/services/position-constants.js +0 -28
- package/src/services/position-exit-logic.js +0 -174
- package/src/services/position-manager.js +0 -438
- package/src/services/position-momentum.js +0 -206
- package/src/services/projectx/accounts.js +0 -142
- package/src/services/projectx/index.js +0 -443
- package/src/services/projectx/market.js +0 -172
- package/src/services/projectx/stats.js +0 -110
- package/src/services/projectx/trading.js +0 -180
- package/src/services/rithmic/latency-tracker.js +0 -182
- package/src/services/rithmic/market-data-decoders.js +0 -229
- package/src/services/rithmic/market-data.js +0 -272
- package/src/services/rithmic/orders-fast.js +0 -246
- package/src/services/rithmic/proto-decoders.js +0 -403
- package/src/services/rithmic/specs.js +0 -146
- package/src/services/rithmic/trade-history.js +0 -254
- package/src/services/session-history.js +0 -475
- package/src/services/strategy/hft-signal-calc.js +0 -147
- package/src/services/strategy/hft-tick.js +0 -407
- package/src/services/strategy/recovery-math.js +0 -402
- package/src/services/tradovate/constants.js +0 -109
- package/src/services/tradovate/index.js +0 -392
- package/src/services/tradovate/market.js +0 -47
- package/src/services/tradovate/orders.js +0 -145
- package/src/services/tradovate/websocket.js +0 -97
|
@@ -1,333 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview AI Client - Model Fetching Functions
|
|
3
|
-
*
|
|
4
|
-
* Functions to fetch available models from various AI providers
|
|
5
|
-
* Data comes from provider APIs - NO hardcoded fallbacks
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const https = require('https');
|
|
9
|
-
const http = require('http');
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Make HTTP request
|
|
13
|
-
* @param {string} url - Full URL
|
|
14
|
-
* @param {Object} options - Request options
|
|
15
|
-
* @returns {Promise<Object>} Response data
|
|
16
|
-
*/
|
|
17
|
-
const makeRequest = (url, options) => {
|
|
18
|
-
return new Promise((resolve, reject) => {
|
|
19
|
-
const parsedUrl = new URL(url);
|
|
20
|
-
const protocol = parsedUrl.protocol === 'https:' ? https : http;
|
|
21
|
-
|
|
22
|
-
const req = protocol.request(url, {
|
|
23
|
-
method: options.method || 'POST',
|
|
24
|
-
headers: options.headers || {},
|
|
25
|
-
timeout: options.timeout || 30000
|
|
26
|
-
}, (res) => {
|
|
27
|
-
let data = '';
|
|
28
|
-
res.on('data', chunk => data += chunk);
|
|
29
|
-
res.on('end', () => {
|
|
30
|
-
try {
|
|
31
|
-
const json = JSON.parse(data);
|
|
32
|
-
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
33
|
-
resolve(json);
|
|
34
|
-
} else {
|
|
35
|
-
reject(new Error(json.error?.message || `HTTP ${res.statusCode}`));
|
|
36
|
-
}
|
|
37
|
-
} catch (e) {
|
|
38
|
-
reject(new Error(`Invalid JSON response: ${data.substring(0, 100)}`));
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
req.on('error', reject);
|
|
44
|
-
req.on('timeout', () => reject(new Error('Request timeout')));
|
|
45
|
-
|
|
46
|
-
if (options.body) {
|
|
47
|
-
req.write(JSON.stringify(options.body));
|
|
48
|
-
}
|
|
49
|
-
req.end();
|
|
50
|
-
});
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Fetch available models from Anthropic API (API Key auth)
|
|
55
|
-
* @param {string} apiKey - API key
|
|
56
|
-
* @returns {Promise<Array|null>} Array of model IDs or null on error
|
|
57
|
-
*/
|
|
58
|
-
const fetchAnthropicModels = async (apiKey) => {
|
|
59
|
-
if (!apiKey) return null;
|
|
60
|
-
|
|
61
|
-
const url = 'https://api.anthropic.com/v1/models';
|
|
62
|
-
|
|
63
|
-
const headers = {
|
|
64
|
-
'x-api-key': apiKey,
|
|
65
|
-
'anthropic-version': '2023-06-01'
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
try {
|
|
69
|
-
const response = await makeRequest(url, { method: 'GET', headers, timeout: 10000 });
|
|
70
|
-
if (response.data && Array.isArray(response.data)) {
|
|
71
|
-
return response.data.map(m => m.id).filter(Boolean);
|
|
72
|
-
}
|
|
73
|
-
return null;
|
|
74
|
-
} catch (error) {
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Fetch available models from Anthropic API (OAuth auth)
|
|
81
|
-
* @param {string} accessToken - OAuth access token
|
|
82
|
-
* @returns {Promise<Object>} { models: Array, error: string|null }
|
|
83
|
-
*/
|
|
84
|
-
const fetchAnthropicModelsOAuth = async (accessToken) => {
|
|
85
|
-
if (!accessToken) return { models: null, error: 'No access token provided' };
|
|
86
|
-
|
|
87
|
-
const modelsUrl = 'https://api.anthropic.com/v1/models';
|
|
88
|
-
|
|
89
|
-
const headers = {
|
|
90
|
-
'Authorization': `Bearer ${accessToken}`,
|
|
91
|
-
'anthropic-version': '2023-06-01',
|
|
92
|
-
'anthropic-beta': 'oauth-2025-04-20'
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
try {
|
|
96
|
-
const response = await makeRequest(modelsUrl, { method: 'GET', headers, timeout: 15000 });
|
|
97
|
-
if (response.data && Array.isArray(response.data)) {
|
|
98
|
-
const models = response.data.map(m => m.id).filter(Boolean);
|
|
99
|
-
if (models.length > 0) return { models, error: null };
|
|
100
|
-
}
|
|
101
|
-
return { models: null, error: 'API returned empty or invalid response' };
|
|
102
|
-
} catch (error) {
|
|
103
|
-
return { models: null, error: error.message };
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Fetch available models from Google Gemini API
|
|
109
|
-
* @param {string} apiKey - API key
|
|
110
|
-
* @returns {Promise<Array|null>} Array of model IDs or null on error
|
|
111
|
-
*/
|
|
112
|
-
const fetchGeminiModels = async (apiKey) => {
|
|
113
|
-
if (!apiKey) return null;
|
|
114
|
-
|
|
115
|
-
const url = `https://generativelanguage.googleapis.com/v1/models?key=${apiKey}`;
|
|
116
|
-
|
|
117
|
-
try {
|
|
118
|
-
const response = await makeRequest(url, { method: 'GET', timeout: 10000 });
|
|
119
|
-
if (response.models && Array.isArray(response.models)) {
|
|
120
|
-
return response.models
|
|
121
|
-
.filter(m => m.supportedGenerationMethods?.includes('generateContent'))
|
|
122
|
-
.map(m => m.name.replace('models/', ''))
|
|
123
|
-
.filter(Boolean);
|
|
124
|
-
}
|
|
125
|
-
return null;
|
|
126
|
-
} catch (error) {
|
|
127
|
-
return null;
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Fetch available models from OpenAI-compatible API
|
|
133
|
-
* @param {string} endpoint - API endpoint base URL
|
|
134
|
-
* @param {string} apiKey - API key or OAuth token
|
|
135
|
-
* @returns {Promise<Array|null>} Array of model IDs from API, null if unavailable
|
|
136
|
-
*/
|
|
137
|
-
const fetchOpenAIModels = async (endpoint, apiKey) => {
|
|
138
|
-
if (!endpoint) return null;
|
|
139
|
-
|
|
140
|
-
const url = `${endpoint}/models`;
|
|
141
|
-
|
|
142
|
-
const headers = {
|
|
143
|
-
'Content-Type': 'application/json'
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
if (apiKey) {
|
|
147
|
-
headers['Authorization'] = `Bearer ${apiKey}`;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
try {
|
|
151
|
-
const response = await makeRequest(url, { method: 'GET', headers, timeout: 10000 });
|
|
152
|
-
if (response.data && Array.isArray(response.data)) {
|
|
153
|
-
const chatModels = response.data
|
|
154
|
-
.map(m => m.id)
|
|
155
|
-
.filter(id => id && (
|
|
156
|
-
id.includes('gpt') ||
|
|
157
|
-
id.includes('o1') ||
|
|
158
|
-
id.includes('o3') ||
|
|
159
|
-
id.includes('claude') ||
|
|
160
|
-
id.includes('gemini')
|
|
161
|
-
))
|
|
162
|
-
.filter(id =>
|
|
163
|
-
!id.includes('embedding') &&
|
|
164
|
-
!id.includes('whisper') &&
|
|
165
|
-
!id.includes('tts') &&
|
|
166
|
-
!id.includes('dall-e')
|
|
167
|
-
);
|
|
168
|
-
|
|
169
|
-
return chatModels.length > 0 ? chatModels : null;
|
|
170
|
-
}
|
|
171
|
-
return null;
|
|
172
|
-
} catch (error) {
|
|
173
|
-
return null;
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Fetch available models for OAuth-authenticated providers
|
|
179
|
-
* @param {string} providerId - Provider ID
|
|
180
|
-
* @param {string} accessToken - OAuth access token
|
|
181
|
-
* @returns {Promise<Array|null>} Array of model IDs from API, null if unavailable
|
|
182
|
-
*/
|
|
183
|
-
const fetchModelsWithOAuth = async (providerId, accessToken) => {
|
|
184
|
-
if (!accessToken) return null;
|
|
185
|
-
|
|
186
|
-
try {
|
|
187
|
-
switch (providerId) {
|
|
188
|
-
case 'anthropic':
|
|
189
|
-
return await fetchAnthropicModelsOAuth(accessToken);
|
|
190
|
-
|
|
191
|
-
case 'openai': {
|
|
192
|
-
const openaiModels = await fetchOpenAIModels('https://api.openai.com/v1', accessToken);
|
|
193
|
-
if (openaiModels && openaiModels.length > 0) {
|
|
194
|
-
return openaiModels;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Try ChatGPT backend API (for Plus/Pro plans)
|
|
198
|
-
try {
|
|
199
|
-
const chatgptUrl = 'https://chatgpt.com/backend-api/models';
|
|
200
|
-
const chatgptHeaders = {
|
|
201
|
-
'Authorization': `Bearer ${accessToken}`,
|
|
202
|
-
'Content-Type': 'application/json'
|
|
203
|
-
};
|
|
204
|
-
const chatgptResponse = await makeRequest(chatgptUrl, {
|
|
205
|
-
method: 'GET',
|
|
206
|
-
headers: chatgptHeaders,
|
|
207
|
-
timeout: 10000
|
|
208
|
-
});
|
|
209
|
-
if (chatgptResponse.models && Array.isArray(chatgptResponse.models)) {
|
|
210
|
-
return chatgptResponse.models
|
|
211
|
-
.map(m => m.slug || m.id || m.name)
|
|
212
|
-
.filter(Boolean);
|
|
213
|
-
}
|
|
214
|
-
} catch (e) {
|
|
215
|
-
if (process.env.HQX_DEBUG) {
|
|
216
|
-
console.error('[DEBUG] ChatGPT backend error:', e.message);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
return null;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
case 'gemini': {
|
|
224
|
-
try {
|
|
225
|
-
const geminiUrl = 'https://generativelanguage.googleapis.com/v1/models';
|
|
226
|
-
const geminiHeaders = {
|
|
227
|
-
'Authorization': `Bearer ${accessToken}`
|
|
228
|
-
};
|
|
229
|
-
const geminiResponse = await makeRequest(geminiUrl, {
|
|
230
|
-
method: 'GET',
|
|
231
|
-
headers: geminiHeaders,
|
|
232
|
-
timeout: 15000
|
|
233
|
-
});
|
|
234
|
-
if (geminiResponse.models && Array.isArray(geminiResponse.models)) {
|
|
235
|
-
const models = geminiResponse.models
|
|
236
|
-
.filter(m => m.supportedGenerationMethods?.includes('generateContent'))
|
|
237
|
-
.map(m => m.name.replace('models/', ''))
|
|
238
|
-
.filter(Boolean);
|
|
239
|
-
if (models.length > 0) return models;
|
|
240
|
-
}
|
|
241
|
-
} catch (e) {
|
|
242
|
-
if (process.env.HQX_DEBUG) {
|
|
243
|
-
console.error('[DEBUG] Gemini models API error:', e.message);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return null;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
case 'qwen': {
|
|
251
|
-
try {
|
|
252
|
-
const qwenUrl = 'https://dashscope.aliyuncs.com/api/v1/models';
|
|
253
|
-
const qwenHeaders = {
|
|
254
|
-
'Authorization': `Bearer ${accessToken}`,
|
|
255
|
-
'Content-Type': 'application/json'
|
|
256
|
-
};
|
|
257
|
-
const qwenResponse = await makeRequest(qwenUrl, {
|
|
258
|
-
method: 'GET',
|
|
259
|
-
headers: qwenHeaders,
|
|
260
|
-
timeout: 10000
|
|
261
|
-
});
|
|
262
|
-
if (qwenResponse.data && Array.isArray(qwenResponse.data)) {
|
|
263
|
-
return qwenResponse.data
|
|
264
|
-
.map(m => m.id || m.model_id)
|
|
265
|
-
.filter(Boolean);
|
|
266
|
-
}
|
|
267
|
-
} catch (e) {
|
|
268
|
-
// Qwen API may not support model listing
|
|
269
|
-
}
|
|
270
|
-
return null;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
case 'iflow': {
|
|
274
|
-
try {
|
|
275
|
-
const iflowUrl = 'https://apis.iflow.cn/v1/models';
|
|
276
|
-
const iflowHeaders = {
|
|
277
|
-
'Authorization': `Bearer ${accessToken}`,
|
|
278
|
-
'Content-Type': 'application/json'
|
|
279
|
-
};
|
|
280
|
-
const iflowResponse = await makeRequest(iflowUrl, {
|
|
281
|
-
method: 'GET',
|
|
282
|
-
headers: iflowHeaders,
|
|
283
|
-
timeout: 10000
|
|
284
|
-
});
|
|
285
|
-
if (iflowResponse.data && Array.isArray(iflowResponse.data)) {
|
|
286
|
-
return iflowResponse.data
|
|
287
|
-
.map(m => m.id)
|
|
288
|
-
.filter(Boolean);
|
|
289
|
-
}
|
|
290
|
-
} catch (e) {
|
|
291
|
-
// iFlow API may not support model listing
|
|
292
|
-
}
|
|
293
|
-
return null;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
default:
|
|
297
|
-
return null;
|
|
298
|
-
}
|
|
299
|
-
} catch (error) {
|
|
300
|
-
return null;
|
|
301
|
-
}
|
|
302
|
-
};
|
|
303
|
-
|
|
304
|
-
/**
|
|
305
|
-
* Fetch models via local CLIProxyAPI proxy
|
|
306
|
-
* @returns {Promise<Array|null>} Array of model IDs or null
|
|
307
|
-
*/
|
|
308
|
-
const fetchModelsViaProxy = async (proxyPort = 8317) => {
|
|
309
|
-
const url = `http://127.0.0.1:${proxyPort}/v1/models`;
|
|
310
|
-
|
|
311
|
-
const headers = {
|
|
312
|
-
'Authorization': 'Bearer hqx-local-key'
|
|
313
|
-
};
|
|
314
|
-
|
|
315
|
-
try {
|
|
316
|
-
const response = await makeRequest(url, { method: 'GET', headers, timeout: 10000 });
|
|
317
|
-
if (response.data && Array.isArray(response.data)) {
|
|
318
|
-
return response.data.map(m => m.id || m).filter(Boolean);
|
|
319
|
-
}
|
|
320
|
-
return null;
|
|
321
|
-
} catch (error) {
|
|
322
|
-
return null;
|
|
323
|
-
}
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
module.exports = {
|
|
327
|
-
fetchAnthropicModels,
|
|
328
|
-
fetchAnthropicModelsOAuth,
|
|
329
|
-
fetchGeminiModels,
|
|
330
|
-
fetchOpenAIModels,
|
|
331
|
-
fetchModelsWithOAuth,
|
|
332
|
-
fetchModelsViaProxy,
|
|
333
|
-
};
|
|
@@ -1,343 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AI Client - Makes real API calls to AI providers
|
|
3
|
-
*
|
|
4
|
-
* STRICT RULE: No mock responses. Real API calls only.
|
|
5
|
-
* If API fails → return null, not fake data.
|
|
6
|
-
*
|
|
7
|
-
* Supports two modes:
|
|
8
|
-
* 1. Direct API calls (with API keys)
|
|
9
|
-
* 2. Proxy mode (via CLIProxyAPI for subscription accounts)
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
const https = require('https');
|
|
13
|
-
const http = require('http');
|
|
14
|
-
const { getProvider } = require('./providers');
|
|
15
|
-
const { analyzeTrading: analyzeTradingImpl, analyzePerformance: analyzePerformanceImpl, getMarketAdvice: getMarketAdviceImpl } = require('./client-analysis');
|
|
16
|
-
const { fetchAnthropicModels, fetchAnthropicModelsOAuth, fetchGeminiModels, fetchOpenAIModels, fetchModelsWithOAuth, fetchModelsViaProxy } = require('./client-models');
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Make HTTP request to AI provider
|
|
20
|
-
* @param {string} url - Full URL
|
|
21
|
-
* @param {Object} options - Request options
|
|
22
|
-
* @returns {Promise<Object>} Response data
|
|
23
|
-
*/
|
|
24
|
-
const makeRequest = (url, options) => {
|
|
25
|
-
return new Promise((resolve, reject) => {
|
|
26
|
-
const parsedUrl = new URL(url);
|
|
27
|
-
const protocol = parsedUrl.protocol === 'https:' ? https : http;
|
|
28
|
-
|
|
29
|
-
const req = protocol.request(url, {
|
|
30
|
-
method: options.method || 'POST',
|
|
31
|
-
headers: options.headers || {},
|
|
32
|
-
timeout: options.timeout || 30000
|
|
33
|
-
}, (res) => {
|
|
34
|
-
let data = '';
|
|
35
|
-
res.on('data', chunk => data += chunk);
|
|
36
|
-
res.on('end', () => {
|
|
37
|
-
try {
|
|
38
|
-
const json = JSON.parse(data);
|
|
39
|
-
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
40
|
-
resolve(json);
|
|
41
|
-
} else {
|
|
42
|
-
reject(new Error(json.error?.message || `HTTP ${res.statusCode}`));
|
|
43
|
-
}
|
|
44
|
-
} catch (e) {
|
|
45
|
-
reject(new Error(`Invalid JSON response: ${data.substring(0, 100)}`));
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
req.on('error', reject);
|
|
51
|
-
req.on('timeout', () => reject(new Error('Request timeout')));
|
|
52
|
-
|
|
53
|
-
if (options.body) {
|
|
54
|
-
req.write(JSON.stringify(options.body));
|
|
55
|
-
}
|
|
56
|
-
req.end();
|
|
57
|
-
});
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Call OpenAI-compatible API
|
|
62
|
-
* Works with: OpenAI, Groq, Together, DeepSeek, Mistral, xAI, etc.
|
|
63
|
-
* @param {Object} agent - Agent configuration
|
|
64
|
-
* @param {string} prompt - User prompt
|
|
65
|
-
* @param {string} systemPrompt - System prompt
|
|
66
|
-
* @param {Object} options - Optional settings { maxTokens, temperature, timeout }
|
|
67
|
-
* @returns {Promise<string|null>} Response text or null on error
|
|
68
|
-
*/
|
|
69
|
-
const callOpenAICompatible = async (agent, prompt, systemPrompt, options = {}) => {
|
|
70
|
-
const provider = getProvider(agent.providerId);
|
|
71
|
-
if (!provider) return null;
|
|
72
|
-
|
|
73
|
-
const endpoint = agent.credentials?.endpoint || provider.endpoint;
|
|
74
|
-
const apiKey = agent.credentials?.apiKey;
|
|
75
|
-
const model = agent.model || provider.defaultModel;
|
|
76
|
-
|
|
77
|
-
if (!apiKey && provider.category !== 'local') {
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const url = `${endpoint}/chat/completions`;
|
|
82
|
-
|
|
83
|
-
const headers = {
|
|
84
|
-
'Content-Type': 'application/json'
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
if (apiKey) {
|
|
88
|
-
headers['Authorization'] = `Bearer ${apiKey}`;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// OpenRouter requires additional headers
|
|
92
|
-
if (agent.providerId === 'openrouter') {
|
|
93
|
-
headers['HTTP-Referer'] = 'https://hedgequantx.com';
|
|
94
|
-
headers['X-Title'] = 'HQX-CLI';
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const body = {
|
|
98
|
-
model,
|
|
99
|
-
messages: [
|
|
100
|
-
{ role: 'system', content: systemPrompt },
|
|
101
|
-
{ role: 'user', content: prompt }
|
|
102
|
-
],
|
|
103
|
-
temperature: options.temperature || 0.3
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
try {
|
|
107
|
-
const timeout = options.timeout || 30000;
|
|
108
|
-
const response = await makeRequest(url, { headers, body, timeout });
|
|
109
|
-
return response.choices?.[0]?.message?.content || null;
|
|
110
|
-
} catch (error) {
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Get valid OAuth token (refresh if needed)
|
|
117
|
-
* @param {Object} credentials - Agent credentials with oauth data
|
|
118
|
-
* @returns {Promise<string|null>} Valid access token or null
|
|
119
|
-
*/
|
|
120
|
-
const getValidOAuthToken = async (credentials) => {
|
|
121
|
-
if (!credentials?.oauth) return null;
|
|
122
|
-
|
|
123
|
-
const oauthAnthropic = require('./oauth-anthropic');
|
|
124
|
-
const validToken = await oauthAnthropic.getValidToken(credentials.oauth);
|
|
125
|
-
|
|
126
|
-
if (!validToken) return null;
|
|
127
|
-
|
|
128
|
-
// If token was refreshed, we should update storage (handled by caller)
|
|
129
|
-
if (validToken.refreshed) {
|
|
130
|
-
credentials.oauth.access = validToken.access;
|
|
131
|
-
credentials.oauth.refresh = validToken.refresh;
|
|
132
|
-
credentials.oauth.expires = validToken.expires;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return validToken.access;
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Call Anthropic Claude API
|
|
140
|
-
* Supports both API key and OAuth authentication
|
|
141
|
-
* @param {Object} agent - Agent configuration
|
|
142
|
-
* @param {string} prompt - User prompt
|
|
143
|
-
* @param {string} systemPrompt - System prompt
|
|
144
|
-
* @param {Object} options - Optional settings { maxTokens, temperature, timeout }
|
|
145
|
-
* @returns {Promise<string|null>} Response text or null on error
|
|
146
|
-
*/
|
|
147
|
-
const callAnthropic = async (agent, prompt, systemPrompt, options = {}) => {
|
|
148
|
-
const provider = getProvider('anthropic');
|
|
149
|
-
if (!provider) return null;
|
|
150
|
-
|
|
151
|
-
const model = agent.model || provider.defaultModel;
|
|
152
|
-
const url = `${provider.endpoint}/messages`;
|
|
153
|
-
|
|
154
|
-
// Determine authentication method
|
|
155
|
-
const isOAuth = agent.credentials?.oauth?.refresh;
|
|
156
|
-
let headers = {
|
|
157
|
-
'Content-Type': 'application/json',
|
|
158
|
-
'anthropic-version': '2023-06-01'
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
if (isOAuth) {
|
|
162
|
-
// OAuth Bearer token authentication
|
|
163
|
-
const accessToken = await getValidOAuthToken(agent.credentials);
|
|
164
|
-
if (!accessToken) return null;
|
|
165
|
-
|
|
166
|
-
headers['Authorization'] = `Bearer ${accessToken}`;
|
|
167
|
-
headers['anthropic-beta'] = 'oauth-2025-04-20,interleaved-thinking-2025-05-14';
|
|
168
|
-
} else {
|
|
169
|
-
// Standard API key authentication
|
|
170
|
-
const apiKey = agent.credentials?.apiKey;
|
|
171
|
-
if (!apiKey) return null;
|
|
172
|
-
|
|
173
|
-
headers['x-api-key'] = apiKey;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const body = {
|
|
177
|
-
model,
|
|
178
|
-
system: systemPrompt,
|
|
179
|
-
messages: [
|
|
180
|
-
{ role: 'user', content: prompt }
|
|
181
|
-
]
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
try {
|
|
185
|
-
const timeout = options.timeout || 30000;
|
|
186
|
-
const response = await makeRequest(url, { headers, body, timeout });
|
|
187
|
-
return response.content?.[0]?.text || null;
|
|
188
|
-
} catch (error) {
|
|
189
|
-
return null;
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Call Google Gemini API
|
|
195
|
-
* @param {Object} agent - Agent configuration
|
|
196
|
-
* @param {string} prompt - User prompt
|
|
197
|
-
* @param {string} systemPrompt - System prompt
|
|
198
|
-
* @returns {Promise<string|null>} Response text or null on error
|
|
199
|
-
*/
|
|
200
|
-
const callGemini = async (agent, prompt, systemPrompt, options = {}) => {
|
|
201
|
-
const provider = getProvider('gemini');
|
|
202
|
-
if (!provider) return null;
|
|
203
|
-
|
|
204
|
-
const apiKey = agent.credentials?.apiKey;
|
|
205
|
-
const model = agent.model || provider.defaultModel;
|
|
206
|
-
|
|
207
|
-
if (!apiKey) return null;
|
|
208
|
-
|
|
209
|
-
const url = `${provider.endpoint}/models/${model}:generateContent?key=${apiKey}`;
|
|
210
|
-
|
|
211
|
-
const headers = {
|
|
212
|
-
'Content-Type': 'application/json'
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
const body = {
|
|
216
|
-
contents: [
|
|
217
|
-
{ role: 'user', parts: [{ text: `${systemPrompt}\n\n${prompt}` }] }
|
|
218
|
-
],
|
|
219
|
-
generationConfig: {
|
|
220
|
-
temperature: 0.3
|
|
221
|
-
}
|
|
222
|
-
};
|
|
223
|
-
|
|
224
|
-
try {
|
|
225
|
-
const timeout = options.timeout || 60000;
|
|
226
|
-
const response = await makeRequest(url, { headers, body, timeout });
|
|
227
|
-
return response.candidates?.[0]?.content?.parts?.[0]?.text || null;
|
|
228
|
-
} catch (error) {
|
|
229
|
-
return null;
|
|
230
|
-
}
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Call AI via local CLIProxyAPI proxy
|
|
235
|
-
* Used for subscription accounts (ChatGPT Plus, Claude Pro, etc.)
|
|
236
|
-
* @param {Object} agent - Agent configuration
|
|
237
|
-
* @param {string} prompt - User prompt
|
|
238
|
-
* @param {string} systemPrompt - System prompt
|
|
239
|
-
* @returns {Promise<string|null>} Response text or null on error
|
|
240
|
-
*/
|
|
241
|
-
const callViaProxy = async (agent, prompt, systemPrompt, options = {}) => {
|
|
242
|
-
const proxyPort = agent.credentials?.proxyPort || 8317;
|
|
243
|
-
const model = agent.model;
|
|
244
|
-
|
|
245
|
-
if (!model) return null;
|
|
246
|
-
|
|
247
|
-
const url = `http://127.0.0.1:${proxyPort}/v1/chat/completions`;
|
|
248
|
-
|
|
249
|
-
const headers = {
|
|
250
|
-
'Content-Type': 'application/json',
|
|
251
|
-
'Authorization': 'Bearer hqx-local-key'
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
const body = {
|
|
255
|
-
model,
|
|
256
|
-
messages: [
|
|
257
|
-
{ role: 'system', content: systemPrompt },
|
|
258
|
-
{ role: 'user', content: prompt }
|
|
259
|
-
],
|
|
260
|
-
temperature: 0.3
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
try {
|
|
264
|
-
const timeout = options.timeout || 60000;
|
|
265
|
-
const response = await makeRequest(url, { headers, body, timeout });
|
|
266
|
-
return response.choices?.[0]?.message?.content || null;
|
|
267
|
-
} catch (error) {
|
|
268
|
-
return null;
|
|
269
|
-
}
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Call AI provider based on agent configuration
|
|
274
|
-
* @param {Object} agent - Agent with providerId and credentials
|
|
275
|
-
* @param {string} prompt - User prompt
|
|
276
|
-
* @param {string} systemPrompt - System prompt
|
|
277
|
-
* @param {Object} options - Optional settings { maxTokens, temperature, timeout }
|
|
278
|
-
* @returns {Promise<string|null>} AI response or null on error
|
|
279
|
-
*/
|
|
280
|
-
const callAI = async (agent, prompt, systemPrompt = '', options = {}) => {
|
|
281
|
-
if (!agent || !agent.providerId) return null;
|
|
282
|
-
|
|
283
|
-
// Default timeout 60s for code generation
|
|
284
|
-
const opts = { timeout: 60000, ...options };
|
|
285
|
-
|
|
286
|
-
// Check if using proxy mode (subscription accounts)
|
|
287
|
-
if (agent.credentials?.useProxy) {
|
|
288
|
-
return callViaProxy(agent, prompt, systemPrompt, opts);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
switch (agent.providerId) {
|
|
292
|
-
case 'anthropic':
|
|
293
|
-
return callAnthropic(agent, prompt, systemPrompt, opts);
|
|
294
|
-
|
|
295
|
-
case 'gemini':
|
|
296
|
-
return callGemini(agent, prompt, systemPrompt, opts);
|
|
297
|
-
|
|
298
|
-
// All OpenAI-compatible APIs
|
|
299
|
-
case 'openai':
|
|
300
|
-
case 'openrouter':
|
|
301
|
-
case 'deepseek':
|
|
302
|
-
case 'groq':
|
|
303
|
-
case 'xai':
|
|
304
|
-
case 'mistral':
|
|
305
|
-
case 'perplexity':
|
|
306
|
-
case 'together':
|
|
307
|
-
case 'qwen':
|
|
308
|
-
case 'moonshot':
|
|
309
|
-
case 'yi':
|
|
310
|
-
case 'zhipu':
|
|
311
|
-
case 'baichuan':
|
|
312
|
-
case 'ollama':
|
|
313
|
-
case 'lmstudio':
|
|
314
|
-
case 'custom':
|
|
315
|
-
return callOpenAICompatible(agent, prompt, systemPrompt, opts);
|
|
316
|
-
|
|
317
|
-
default:
|
|
318
|
-
return null;
|
|
319
|
-
}
|
|
320
|
-
};
|
|
321
|
-
|
|
322
|
-
// Wrapper functions for analysis (pass callAI to implementation)
|
|
323
|
-
const analyzeTrading = (agent, data) => analyzeTradingImpl(callAI, agent, data);
|
|
324
|
-
const analyzePerformance = (agent, performanceData) => analyzePerformanceImpl(callAI, agent, performanceData);
|
|
325
|
-
const getMarketAdvice = (agent, marketData) => getMarketAdviceImpl(callAI, agent, marketData);
|
|
326
|
-
|
|
327
|
-
module.exports = {
|
|
328
|
-
callAI,
|
|
329
|
-
callViaProxy,
|
|
330
|
-
analyzeTrading,
|
|
331
|
-
analyzePerformance,
|
|
332
|
-
getMarketAdvice,
|
|
333
|
-
callOpenAICompatible,
|
|
334
|
-
callAnthropic,
|
|
335
|
-
callGemini,
|
|
336
|
-
fetchAnthropicModels,
|
|
337
|
-
fetchAnthropicModelsOAuth,
|
|
338
|
-
fetchGeminiModels,
|
|
339
|
-
fetchOpenAIModels,
|
|
340
|
-
fetchModelsWithOAuth,
|
|
341
|
-
fetchModelsViaProxy,
|
|
342
|
-
getValidOAuthToken
|
|
343
|
-
};
|