hedgequantx 2.7.20 → 2.7.21
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/pages/ai-agents.js +30 -18
- package/src/pages/ai-models.js +203 -63
package/package.json
CHANGED
package/src/pages/ai-agents.js
CHANGED
|
@@ -10,9 +10,10 @@ const os = require('os');
|
|
|
10
10
|
const path = require('path');
|
|
11
11
|
const fs = require('fs');
|
|
12
12
|
|
|
13
|
+
const ora = require('ora');
|
|
13
14
|
const { getLogoWidth, centerText, visibleLength } = require('../ui');
|
|
14
15
|
const { prompts } = require('../utils');
|
|
15
|
-
const {
|
|
16
|
+
const { fetchModelsFromApi } = require('./ai-models');
|
|
16
17
|
|
|
17
18
|
// Config file path
|
|
18
19
|
const CONFIG_DIR = path.join(os.homedir(), '.hqx');
|
|
@@ -135,18 +136,27 @@ const drawModelsTable = (provider, models, boxWidth) => {
|
|
|
135
136
|
};
|
|
136
137
|
|
|
137
138
|
/**
|
|
138
|
-
* Select a model for a provider
|
|
139
|
+
* Select a model for a provider (fetches from API)
|
|
139
140
|
* @param {Object} provider - Provider object
|
|
140
|
-
* @
|
|
141
|
+
* @param {string} apiKey - API key for fetching models
|
|
142
|
+
* @returns {Object|null} Selected model or null if cancelled/failed
|
|
141
143
|
*/
|
|
142
|
-
const selectModel = async (provider) => {
|
|
144
|
+
const selectModel = async (provider, apiKey) => {
|
|
143
145
|
const boxWidth = getLogoWidth();
|
|
144
|
-
const models = getModelsForProvider(provider.id);
|
|
145
146
|
|
|
146
|
-
|
|
147
|
+
// Fetch models from API
|
|
148
|
+
const spinner = ora({ text: 'Fetching models from API...', color: 'yellow' }).start();
|
|
149
|
+
const result = await fetchModelsFromApi(provider.id, apiKey);
|
|
150
|
+
|
|
151
|
+
if (!result.success || result.models.length === 0) {
|
|
152
|
+
spinner.fail(result.error || 'No models available');
|
|
153
|
+
await prompts.waitForEnter();
|
|
147
154
|
return null;
|
|
148
155
|
}
|
|
149
156
|
|
|
157
|
+
spinner.succeed(`Found ${result.models.length} models`);
|
|
158
|
+
const models = result.models;
|
|
159
|
+
|
|
150
160
|
while (true) {
|
|
151
161
|
console.clear();
|
|
152
162
|
drawModelsTable(provider, models, boxWidth);
|
|
@@ -294,9 +304,11 @@ const handleProviderConfig = async (provider, config) => {
|
|
|
294
304
|
}
|
|
295
305
|
|
|
296
306
|
if (choice === '1') {
|
|
297
|
-
// CLIProxy connection -
|
|
298
|
-
|
|
299
|
-
|
|
307
|
+
// CLIProxy connection - models will be fetched via proxy
|
|
308
|
+
console.log();
|
|
309
|
+
console.log(chalk.cyan(' CLIProxy uses your paid plan subscription.'));
|
|
310
|
+
console.log(chalk.gray(' Model selection will be available after connecting.'));
|
|
311
|
+
console.log();
|
|
300
312
|
|
|
301
313
|
// Deactivate all other providers
|
|
302
314
|
Object.keys(config.providers).forEach(id => {
|
|
@@ -305,26 +317,22 @@ const handleProviderConfig = async (provider, config) => {
|
|
|
305
317
|
|
|
306
318
|
if (!config.providers[provider.id]) config.providers[provider.id] = {};
|
|
307
319
|
config.providers[provider.id].connectionType = 'cliproxy';
|
|
308
|
-
config.providers[provider.id].modelId =
|
|
309
|
-
config.providers[provider.id].modelName =
|
|
320
|
+
config.providers[provider.id].modelId = null;
|
|
321
|
+
config.providers[provider.id].modelName = 'N/A';
|
|
310
322
|
config.providers[provider.id].active = true;
|
|
311
323
|
config.providers[provider.id].configuredAt = new Date().toISOString();
|
|
312
324
|
|
|
313
325
|
if (saveConfig(config)) {
|
|
314
|
-
console.log(chalk.green(
|
|
315
|
-
console.log(chalk.cyan(` Model: ${selectedModel.name}`));
|
|
326
|
+
console.log(chalk.green(` ✓ ${provider.name} connected via CLIProxy.`));
|
|
316
327
|
} else {
|
|
317
|
-
console.log(chalk.red('
|
|
328
|
+
console.log(chalk.red(' Failed to save config.'));
|
|
318
329
|
}
|
|
319
330
|
await prompts.waitForEnter();
|
|
320
331
|
continue;
|
|
321
332
|
}
|
|
322
333
|
|
|
323
334
|
if (choice === '2') {
|
|
324
|
-
// API Key connection -
|
|
325
|
-
const selectedModel = await selectModel(provider);
|
|
326
|
-
if (!selectedModel) continue;
|
|
327
|
-
|
|
335
|
+
// API Key connection - get key first, then fetch models
|
|
328
336
|
console.clear();
|
|
329
337
|
console.log(chalk.yellow(`\n Enter your ${provider.name} API key:`));
|
|
330
338
|
console.log(chalk.gray(' (Press Enter to cancel)'));
|
|
@@ -344,6 +352,10 @@ const handleProviderConfig = async (provider, config) => {
|
|
|
344
352
|
continue;
|
|
345
353
|
}
|
|
346
354
|
|
|
355
|
+
// Fetch models from API with the provided key
|
|
356
|
+
const selectedModel = await selectModel(provider, apiKey.trim());
|
|
357
|
+
if (!selectedModel) continue;
|
|
358
|
+
|
|
347
359
|
// Deactivate all other providers
|
|
348
360
|
Object.keys(config.providers).forEach(id => {
|
|
349
361
|
if (config.providers[id]) config.providers[id].active = false;
|
package/src/pages/ai-models.js
CHANGED
|
@@ -1,84 +1,224 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* AI Models
|
|
2
|
+
* AI Models - Fetch from provider APIs
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Models are fetched dynamically from each provider's API.
|
|
5
|
+
* No hardcoded model lists - data comes from real APIs only.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
8
|
+
const https = require('https');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* API endpoints for fetching models
|
|
12
|
+
*/
|
|
13
|
+
const API_ENDPOINTS = {
|
|
14
|
+
anthropic: 'https://api.anthropic.com/v1/models',
|
|
15
|
+
openai: 'https://api.openai.com/v1/models',
|
|
16
|
+
google: 'https://generativelanguage.googleapis.com/v1/models',
|
|
17
|
+
mistral: 'https://api.mistral.ai/v1/models',
|
|
18
|
+
groq: 'https://api.groq.com/openai/v1/models',
|
|
19
|
+
xai: 'https://api.x.ai/v1/models',
|
|
20
|
+
perplexity: 'https://api.perplexity.ai/models',
|
|
21
|
+
openrouter: 'https://openrouter.ai/api/v1/models',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Make HTTPS request
|
|
26
|
+
* @param {string} url - API URL
|
|
27
|
+
* @param {Object} headers - Request headers
|
|
28
|
+
* @param {number} timeout - Timeout in ms (default 60000 per RULES.md #15)
|
|
29
|
+
* @returns {Promise<Object>} Response data
|
|
30
|
+
*/
|
|
31
|
+
const fetchApi = (url, headers = {}, timeout = 60000) => {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
const urlObj = new URL(url);
|
|
34
|
+
const options = {
|
|
35
|
+
hostname: urlObj.hostname,
|
|
36
|
+
path: urlObj.pathname + urlObj.search,
|
|
37
|
+
method: 'GET',
|
|
38
|
+
headers: {
|
|
39
|
+
'Content-Type': 'application/json',
|
|
40
|
+
...headers
|
|
41
|
+
},
|
|
42
|
+
timeout
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const req = https.request(options, (res) => {
|
|
46
|
+
let data = '';
|
|
47
|
+
res.on('data', chunk => data += chunk);
|
|
48
|
+
res.on('end', () => {
|
|
49
|
+
try {
|
|
50
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
51
|
+
resolve({ success: true, data: JSON.parse(data) });
|
|
52
|
+
} else {
|
|
53
|
+
resolve({ success: false, error: `HTTP ${res.statusCode}` });
|
|
54
|
+
}
|
|
55
|
+
} catch (error) {
|
|
56
|
+
resolve({ success: false, error: 'Invalid JSON response' });
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
req.on('error', (error) => {
|
|
62
|
+
resolve({ success: false, error: error.message });
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
req.on('timeout', () => {
|
|
66
|
+
req.destroy();
|
|
67
|
+
resolve({ success: false, error: 'Request timeout' });
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
req.end();
|
|
71
|
+
});
|
|
58
72
|
};
|
|
59
73
|
|
|
60
74
|
/**
|
|
61
|
-
* Get
|
|
75
|
+
* Get auth headers for provider
|
|
62
76
|
* @param {string} providerId - Provider ID
|
|
63
|
-
* @
|
|
77
|
+
* @param {string} apiKey - API key
|
|
78
|
+
* @returns {Object} Headers object
|
|
64
79
|
*/
|
|
65
|
-
const
|
|
66
|
-
|
|
80
|
+
const getAuthHeaders = (providerId, apiKey) => {
|
|
81
|
+
if (!apiKey) return {};
|
|
82
|
+
|
|
83
|
+
switch (providerId) {
|
|
84
|
+
case 'anthropic':
|
|
85
|
+
return { 'x-api-key': apiKey, 'anthropic-version': '2023-06-01' };
|
|
86
|
+
case 'openai':
|
|
87
|
+
case 'groq':
|
|
88
|
+
case 'xai':
|
|
89
|
+
case 'perplexity':
|
|
90
|
+
case 'openrouter':
|
|
91
|
+
return { 'Authorization': `Bearer ${apiKey}` };
|
|
92
|
+
case 'google':
|
|
93
|
+
return {}; // Google uses query param
|
|
94
|
+
case 'mistral':
|
|
95
|
+
return { 'Authorization': `Bearer ${apiKey}` };
|
|
96
|
+
default:
|
|
97
|
+
return { 'Authorization': `Bearer ${apiKey}` };
|
|
98
|
+
}
|
|
67
99
|
};
|
|
68
100
|
|
|
69
101
|
/**
|
|
70
|
-
*
|
|
102
|
+
* Parse models response based on provider
|
|
71
103
|
* @param {string} providerId - Provider ID
|
|
104
|
+
* @param {Object} data - API response data
|
|
105
|
+
* @returns {Array} Parsed models list
|
|
106
|
+
*/
|
|
107
|
+
const parseModelsResponse = (providerId, data) => {
|
|
108
|
+
if (!data) return [];
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
switch (providerId) {
|
|
112
|
+
case 'anthropic':
|
|
113
|
+
// Anthropic returns { data: [{ id, display_name, ... }] }
|
|
114
|
+
return (data.data || []).map(m => ({
|
|
115
|
+
id: m.id,
|
|
116
|
+
name: m.display_name || m.id
|
|
117
|
+
}));
|
|
118
|
+
|
|
119
|
+
case 'openai':
|
|
120
|
+
case 'groq':
|
|
121
|
+
case 'xai':
|
|
122
|
+
// OpenAI format: { data: [{ id, ... }] }
|
|
123
|
+
return (data.data || [])
|
|
124
|
+
.filter(m => m.id && !m.id.includes('whisper') && !m.id.includes('tts') && !m.id.includes('dall-e'))
|
|
125
|
+
.map(m => ({
|
|
126
|
+
id: m.id,
|
|
127
|
+
name: m.id
|
|
128
|
+
}));
|
|
129
|
+
|
|
130
|
+
case 'google':
|
|
131
|
+
// Google format: { models: [{ name, displayName, ... }] }
|
|
132
|
+
return (data.models || []).map(m => ({
|
|
133
|
+
id: m.name?.replace('models/', '') || m.name,
|
|
134
|
+
name: m.displayName || m.name
|
|
135
|
+
}));
|
|
136
|
+
|
|
137
|
+
case 'mistral':
|
|
138
|
+
// Mistral format: { data: [{ id, ... }] }
|
|
139
|
+
return (data.data || []).map(m => ({
|
|
140
|
+
id: m.id,
|
|
141
|
+
name: m.id
|
|
142
|
+
}));
|
|
143
|
+
|
|
144
|
+
case 'perplexity':
|
|
145
|
+
// Perplexity format varies
|
|
146
|
+
return (data.models || data.data || []).map(m => ({
|
|
147
|
+
id: m.id || m.model,
|
|
148
|
+
name: m.id || m.model
|
|
149
|
+
}));
|
|
150
|
+
|
|
151
|
+
case 'openrouter':
|
|
152
|
+
// OpenRouter format: { data: [{ id, name, ... }] }
|
|
153
|
+
return (data.data || []).map(m => ({
|
|
154
|
+
id: m.id,
|
|
155
|
+
name: m.name || m.id
|
|
156
|
+
}));
|
|
157
|
+
|
|
158
|
+
default:
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
} catch (error) {
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Fetch models from provider API
|
|
168
|
+
* @param {string} providerId - Provider ID
|
|
169
|
+
* @param {string} apiKey - API key (required for most providers)
|
|
170
|
+
* @returns {Promise<Object>} { success, models, error }
|
|
171
|
+
*/
|
|
172
|
+
const fetchModelsFromApi = async (providerId, apiKey) => {
|
|
173
|
+
const endpoint = API_ENDPOINTS[providerId];
|
|
174
|
+
if (!endpoint) {
|
|
175
|
+
return { success: false, models: [], error: 'Unknown provider' };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Build URL (Google needs API key in query)
|
|
179
|
+
let url = endpoint;
|
|
180
|
+
if (providerId === 'google' && apiKey) {
|
|
181
|
+
url += `?key=${apiKey}`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const headers = getAuthHeaders(providerId, apiKey);
|
|
185
|
+
const result = await fetchApi(url, headers);
|
|
186
|
+
|
|
187
|
+
if (!result.success) {
|
|
188
|
+
return { success: false, models: [], error: result.error };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const models = parseModelsResponse(providerId, result.data);
|
|
192
|
+
|
|
193
|
+
if (models.length === 0) {
|
|
194
|
+
return { success: false, models: [], error: 'No models returned' };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return { success: true, models, error: null };
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Get models for a provider - returns empty, use fetchModelsFromApi
|
|
202
|
+
* @param {string} providerId - Provider ID
|
|
203
|
+
* @returns {Array} Empty array
|
|
204
|
+
*/
|
|
205
|
+
const getModelsForProvider = (providerId) => {
|
|
206
|
+
return [];
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Get model by ID - returns null, use API data
|
|
211
|
+
* @param {string} providerId - Provider ID
|
|
72
212
|
* @param {string} modelId - Model ID
|
|
73
|
-
* @returns {
|
|
213
|
+
* @returns {null} Always null
|
|
74
214
|
*/
|
|
75
215
|
const getModelById = (providerId, modelId) => {
|
|
76
|
-
|
|
77
|
-
return models.find(m => m.id === modelId) || null;
|
|
216
|
+
return null;
|
|
78
217
|
};
|
|
79
218
|
|
|
80
219
|
module.exports = {
|
|
81
|
-
|
|
220
|
+
fetchModelsFromApi,
|
|
82
221
|
getModelsForProvider,
|
|
83
|
-
getModelById
|
|
222
|
+
getModelById,
|
|
223
|
+
API_ENDPOINTS
|
|
84
224
|
};
|