hedgequantx 2.7.19 → 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 +113 -67
- package/src/pages/ai-models.js +224 -0
package/package.json
CHANGED
package/src/pages/ai-agents.js
CHANGED
|
@@ -10,8 +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');
|
|
16
|
+
const { fetchModelsFromApi } = require('./ai-models');
|
|
15
17
|
|
|
16
18
|
// Config file path
|
|
17
19
|
const CONFIG_DIR = path.join(os.homedir(), '.hqx');
|
|
@@ -63,7 +65,7 @@ const saveConfig = (config) => {
|
|
|
63
65
|
};
|
|
64
66
|
|
|
65
67
|
/**
|
|
66
|
-
* Mask API key for display
|
|
68
|
+
* Mask API key for display
|
|
67
69
|
* @param {string} key - API key
|
|
68
70
|
* @returns {string} Masked key
|
|
69
71
|
*/
|
|
@@ -73,71 +75,107 @@ const maskKey = (key) => {
|
|
|
73
75
|
};
|
|
74
76
|
|
|
75
77
|
/**
|
|
76
|
-
* Draw
|
|
77
|
-
* @param {Object} config - Current config
|
|
78
|
-
* @param {number} boxWidth - Box width
|
|
78
|
+
* Draw a 2-column row
|
|
79
79
|
*/
|
|
80
|
-
const
|
|
81
|
-
const W = boxWidth - 2;
|
|
80
|
+
const draw2ColRow = (leftText, rightText, W) => {
|
|
82
81
|
const col1Width = Math.floor(W / 2);
|
|
83
82
|
const col2Width = W - col1Width;
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
const leftLen = visibleLength(leftText);
|
|
84
|
+
const leftPad = col1Width - leftLen;
|
|
85
|
+
const leftPadL = Math.floor(leftPad / 2);
|
|
86
|
+
const rightLen = visibleLength(rightText || '');
|
|
87
|
+
const rightPad = col2Width - rightLen;
|
|
88
|
+
const rightPadL = Math.floor(rightPad / 2);
|
|
89
|
+
console.log(
|
|
90
|
+
chalk.cyan('║') +
|
|
91
|
+
' '.repeat(leftPadL) + leftText + ' '.repeat(leftPad - leftPadL) +
|
|
92
|
+
' '.repeat(rightPadL) + (rightText || '') + ' '.repeat(rightPad - rightPadL) +
|
|
93
|
+
chalk.cyan('║')
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Draw 2-column table
|
|
99
|
+
*/
|
|
100
|
+
const draw2ColTable = (title, titleColor, items, backText, W) => {
|
|
86
101
|
console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
|
|
87
|
-
console.log(chalk.cyan('║') +
|
|
102
|
+
console.log(chalk.cyan('║') + titleColor(centerText(title, W)) + chalk.cyan('║'));
|
|
88
103
|
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
89
104
|
|
|
90
|
-
|
|
91
|
-
const maxNameLen = Math.max(...AI_PROVIDERS.map(p => p.name.length));
|
|
92
|
-
|
|
93
|
-
// Provider rows (2 columns)
|
|
94
|
-
const rows = Math.ceil(AI_PROVIDERS.length / 2);
|
|
105
|
+
const rows = Math.ceil(items.length / 2);
|
|
95
106
|
for (let row = 0; row < rows; row++) {
|
|
96
|
-
const
|
|
97
|
-
const
|
|
107
|
+
const left = items[row];
|
|
108
|
+
const right = items[row + rows];
|
|
109
|
+
draw2ColRow(left || '', right || '', W);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.log(chalk.cyan('╠' + '─'.repeat(W) + '╣'));
|
|
113
|
+
console.log(chalk.cyan('║') + chalk.red(centerText(backText, W)) + chalk.cyan('║'));
|
|
114
|
+
console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Draw providers table
|
|
119
|
+
*/
|
|
120
|
+
const drawProvidersTable = (config, boxWidth) => {
|
|
121
|
+
const W = boxWidth - 2;
|
|
122
|
+
const items = AI_PROVIDERS.map((p, i) => {
|
|
123
|
+
const status = config.providers[p.id]?.active ? chalk.green(' ●') : '';
|
|
124
|
+
return chalk.cyan(`[${i + 1}]`) + ' ' + chalk[p.color](p.name) + status;
|
|
125
|
+
});
|
|
126
|
+
draw2ColTable('AI AGENTS CONFIGURATION', chalk.yellow.bold, items, '[B] Back to Menu', W);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Draw models table
|
|
131
|
+
*/
|
|
132
|
+
const drawModelsTable = (provider, models, boxWidth) => {
|
|
133
|
+
const W = boxWidth - 2;
|
|
134
|
+
const items = models.map((m, i) => chalk.cyan(`[${i + 1}]`) + ' ' + chalk.white(m.name));
|
|
135
|
+
draw2ColTable(`${provider.name.toUpperCase()} - MODELS`, chalk[provider.color].bold, items, '[B] Back', W);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Select a model for a provider (fetches from API)
|
|
140
|
+
* @param {Object} provider - Provider object
|
|
141
|
+
* @param {string} apiKey - API key for fetching models
|
|
142
|
+
* @returns {Object|null} Selected model or null if cancelled/failed
|
|
143
|
+
*/
|
|
144
|
+
const selectModel = async (provider, apiKey) => {
|
|
145
|
+
const boxWidth = getLogoWidth();
|
|
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();
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
spinner.succeed(`Found ${result.models.length} models`);
|
|
158
|
+
const models = result.models;
|
|
159
|
+
|
|
160
|
+
while (true) {
|
|
161
|
+
console.clear();
|
|
162
|
+
drawModelsTable(provider, models, boxWidth);
|
|
98
163
|
|
|
99
|
-
const
|
|
100
|
-
const
|
|
164
|
+
const input = await prompts.textInput(chalk.cyan('Select model: '));
|
|
165
|
+
const choice = (input || '').toLowerCase().trim();
|
|
101
166
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const leftConfig = config.providers[leftProvider.id] || {};
|
|
106
|
-
const leftStatus = leftConfig.active ? chalk.green('●') : '';
|
|
107
|
-
const leftText = chalk.cyan(leftNum) + ' ' + chalk[leftProvider.color](leftName) + ' ' + leftStatus;
|
|
108
|
-
const leftLen = visibleLength(leftText);
|
|
109
|
-
const leftPadTotal = col1Width - leftLen;
|
|
110
|
-
const leftPadL = Math.floor(leftPadTotal / 2);
|
|
111
|
-
const leftPadR = leftPadTotal - leftPadL;
|
|
167
|
+
if (choice === 'b' || choice === '') {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
112
170
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
let rightPadR = col2Width;
|
|
117
|
-
if (rightProvider) {
|
|
118
|
-
const rightNum = `[${rightIdx + 1}]`;
|
|
119
|
-
const rightName = rightProvider.name;
|
|
120
|
-
const rightConfig = config.providers[rightProvider.id] || {};
|
|
121
|
-
const rightStatus = rightConfig.active ? chalk.green('●') : '';
|
|
122
|
-
rightText = chalk.cyan(rightNum) + ' ' + chalk[rightProvider.color](rightName) + ' ' + rightStatus;
|
|
123
|
-
const rightLen = visibleLength(rightText);
|
|
124
|
-
const rightPadTotal = col2Width - rightLen;
|
|
125
|
-
rightPadL = Math.floor(rightPadTotal / 2);
|
|
126
|
-
rightPadR = rightPadTotal - rightPadL;
|
|
171
|
+
const num = parseInt(choice);
|
|
172
|
+
if (!isNaN(num) && num >= 1 && num <= models.length) {
|
|
173
|
+
return models[num - 1];
|
|
127
174
|
}
|
|
128
175
|
|
|
129
|
-
console.log(
|
|
130
|
-
|
|
131
|
-
' '.repeat(leftPadL) + leftText + ' '.repeat(leftPadR) +
|
|
132
|
-
' '.repeat(rightPadL) + rightText + ' '.repeat(rightPadR) +
|
|
133
|
-
chalk.cyan('║')
|
|
134
|
-
);
|
|
176
|
+
console.log(chalk.red(' Invalid option.'));
|
|
177
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
135
178
|
}
|
|
136
|
-
|
|
137
|
-
// Footer
|
|
138
|
-
console.log(chalk.cyan('╠' + '─'.repeat(W) + '╣'));
|
|
139
|
-
console.log(chalk.cyan('║') + chalk.red(centerText('[B] Back to Menu', W)) + chalk.cyan('║'));
|
|
140
|
-
console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
|
|
141
179
|
};
|
|
142
180
|
|
|
143
181
|
/**
|
|
@@ -213,11 +251,8 @@ const drawProviderWindow = (provider, config, boxWidth) => {
|
|
|
213
251
|
let statusText = '';
|
|
214
252
|
if (providerConfig.active) {
|
|
215
253
|
const connType = providerConfig.connectionType === 'cliproxy' ? 'CLIProxy' : 'API Key';
|
|
216
|
-
const
|
|
217
|
-
statusText = chalk.green('● ACTIVE') + chalk.gray(' via ') + chalk.cyan(connType);
|
|
218
|
-
if (providerConfig.connectionType === 'apikey' && providerConfig.apiKey) {
|
|
219
|
-
statusText += chalk.gray(' Key: ') + chalk.cyan(keyDisplay);
|
|
220
|
-
}
|
|
254
|
+
const modelName = providerConfig.modelName || 'N/A';
|
|
255
|
+
statusText = chalk.green('● ACTIVE') + chalk.gray(' Model: ') + chalk.yellow(modelName) + chalk.gray(' via ') + chalk.cyan(connType);
|
|
221
256
|
} else if (providerConfig.apiKey || providerConfig.connectionType) {
|
|
222
257
|
statusText = chalk.yellow('● CONFIGURED') + chalk.gray(' (not active)');
|
|
223
258
|
} else {
|
|
@@ -269,10 +304,10 @@ const handleProviderConfig = async (provider, config) => {
|
|
|
269
304
|
}
|
|
270
305
|
|
|
271
306
|
if (choice === '1') {
|
|
272
|
-
// CLIProxy connection
|
|
307
|
+
// CLIProxy connection - models will be fetched via proxy
|
|
273
308
|
console.log();
|
|
274
|
-
console.log(chalk.cyan('
|
|
275
|
-
console.log(chalk.gray('
|
|
309
|
+
console.log(chalk.cyan(' CLIProxy uses your paid plan subscription.'));
|
|
310
|
+
console.log(chalk.gray(' Model selection will be available after connecting.'));
|
|
276
311
|
console.log();
|
|
277
312
|
|
|
278
313
|
// Deactivate all other providers
|
|
@@ -282,6 +317,8 @@ const handleProviderConfig = async (provider, config) => {
|
|
|
282
317
|
|
|
283
318
|
if (!config.providers[provider.id]) config.providers[provider.id] = {};
|
|
284
319
|
config.providers[provider.id].connectionType = 'cliproxy';
|
|
320
|
+
config.providers[provider.id].modelId = null;
|
|
321
|
+
config.providers[provider.id].modelName = 'N/A';
|
|
285
322
|
config.providers[provider.id].active = true;
|
|
286
323
|
config.providers[provider.id].configuredAt = new Date().toISOString();
|
|
287
324
|
|
|
@@ -295,9 +332,9 @@ const handleProviderConfig = async (provider, config) => {
|
|
|
295
332
|
}
|
|
296
333
|
|
|
297
334
|
if (choice === '2') {
|
|
298
|
-
// API Key connection
|
|
299
|
-
console.
|
|
300
|
-
console.log(chalk.yellow(
|
|
335
|
+
// API Key connection - get key first, then fetch models
|
|
336
|
+
console.clear();
|
|
337
|
+
console.log(chalk.yellow(`\n Enter your ${provider.name} API key:`));
|
|
301
338
|
console.log(chalk.gray(' (Press Enter to cancel)'));
|
|
302
339
|
console.log();
|
|
303
340
|
|
|
@@ -315,6 +352,10 @@ const handleProviderConfig = async (provider, config) => {
|
|
|
315
352
|
continue;
|
|
316
353
|
}
|
|
317
354
|
|
|
355
|
+
// Fetch models from API with the provided key
|
|
356
|
+
const selectedModel = await selectModel(provider, apiKey.trim());
|
|
357
|
+
if (!selectedModel) continue;
|
|
358
|
+
|
|
318
359
|
// Deactivate all other providers
|
|
319
360
|
Object.keys(config.providers).forEach(id => {
|
|
320
361
|
if (config.providers[id]) config.providers[id].active = false;
|
|
@@ -323,13 +364,16 @@ const handleProviderConfig = async (provider, config) => {
|
|
|
323
364
|
if (!config.providers[provider.id]) config.providers[provider.id] = {};
|
|
324
365
|
config.providers[provider.id].connectionType = 'apikey';
|
|
325
366
|
config.providers[provider.id].apiKey = apiKey.trim();
|
|
367
|
+
config.providers[provider.id].modelId = selectedModel.id;
|
|
368
|
+
config.providers[provider.id].modelName = selectedModel.name;
|
|
326
369
|
config.providers[provider.id].active = true;
|
|
327
370
|
config.providers[provider.id].configuredAt = new Date().toISOString();
|
|
328
371
|
|
|
329
372
|
if (saveConfig(config)) {
|
|
330
|
-
console.log(chalk.green(
|
|
373
|
+
console.log(chalk.green(`\n ✓ ${provider.name} connected via API Key.`));
|
|
374
|
+
console.log(chalk.cyan(` Model: ${selectedModel.name}`));
|
|
331
375
|
} else {
|
|
332
|
-
console.log(chalk.red(' Failed to save config.'));
|
|
376
|
+
console.log(chalk.red('\n Failed to save config.'));
|
|
333
377
|
}
|
|
334
378
|
await prompts.waitForEnter();
|
|
335
379
|
continue;
|
|
@@ -352,7 +396,9 @@ const getActiveProvider = () => {
|
|
|
352
396
|
id: provider.id,
|
|
353
397
|
name: provider.name,
|
|
354
398
|
connectionType: providerConfig.connectionType,
|
|
355
|
-
apiKey: providerConfig.apiKey || null
|
|
399
|
+
apiKey: providerConfig.apiKey || null,
|
|
400
|
+
modelId: providerConfig.modelId || null,
|
|
401
|
+
modelName: providerConfig.modelName || null
|
|
356
402
|
};
|
|
357
403
|
}
|
|
358
404
|
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Models - Fetch from provider APIs
|
|
3
|
+
*
|
|
4
|
+
* Models are fetched dynamically from each provider's API.
|
|
5
|
+
* No hardcoded model lists - data comes from real APIs only.
|
|
6
|
+
*/
|
|
7
|
+
|
|
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
|
+
});
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get auth headers for provider
|
|
76
|
+
* @param {string} providerId - Provider ID
|
|
77
|
+
* @param {string} apiKey - API key
|
|
78
|
+
* @returns {Object} Headers object
|
|
79
|
+
*/
|
|
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
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Parse models response based on provider
|
|
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
|
|
212
|
+
* @param {string} modelId - Model ID
|
|
213
|
+
* @returns {null} Always null
|
|
214
|
+
*/
|
|
215
|
+
const getModelById = (providerId, modelId) => {
|
|
216
|
+
return null;
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
module.exports = {
|
|
220
|
+
fetchModelsFromApi,
|
|
221
|
+
getModelsForProvider,
|
|
222
|
+
getModelById,
|
|
223
|
+
API_ENDPOINTS
|
|
224
|
+
};
|