ai-speedometer 1.0.0 → 1.0.1
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/cli.js +328 -255
- package/dist/ai-speedometer +106 -98
- package/docs/models-dev-integration.md +218 -93
- package/docs/publish-and-build.md +68 -0
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -15,6 +15,14 @@ import {
|
|
|
15
15
|
migrateFromOldConfig,
|
|
16
16
|
getDebugInfo
|
|
17
17
|
} from './opencode-integration.js';
|
|
18
|
+
import {
|
|
19
|
+
readAIConfig,
|
|
20
|
+
getCustomProvidersFromConfig,
|
|
21
|
+
getVerifiedProvidersFromConfig,
|
|
22
|
+
addCustomProvider,
|
|
23
|
+
addModelToCustomProvider,
|
|
24
|
+
getAIConfigDebugPaths
|
|
25
|
+
} from './ai-config.js';
|
|
18
26
|
import 'dotenv/config';
|
|
19
27
|
import Table from 'cli-table3';
|
|
20
28
|
|
|
@@ -45,17 +53,12 @@ function createAnthropicProvider(baseUrl, apiKey) {
|
|
|
45
53
|
log(`Creating Anthropic provider with baseUrl: ${baseUrl}`);
|
|
46
54
|
log(`API Key length: ${apiKey ? apiKey.length : 0}`);
|
|
47
55
|
|
|
48
|
-
//
|
|
49
|
-
let normalizedBaseUrl = baseUrl;
|
|
50
|
-
if (!baseUrl.endsWith('/v1')) {
|
|
51
|
-
normalizedBaseUrl = baseUrl.endsWith('/') ? `${baseUrl}v1` : `${baseUrl}/v1`;
|
|
52
|
-
log(`Normalized base URL to: ${normalizedBaseUrl}`);
|
|
53
|
-
}
|
|
56
|
+
// Use baseUrl as provided - no automatic normalization needed
|
|
54
57
|
|
|
55
58
|
// Try with baseURL parameter (correct according to docs)
|
|
56
59
|
const provider = createAnthropic({
|
|
57
60
|
apiKey: apiKey,
|
|
58
|
-
baseURL:
|
|
61
|
+
baseURL: baseUrl,
|
|
59
62
|
// Add minimal fetch logging for debugging
|
|
60
63
|
fetch: debugMode ? async (input, init) => {
|
|
61
64
|
log(`API Request to: ${input}`);
|
|
@@ -123,13 +126,13 @@ function showHeader() {
|
|
|
123
126
|
console.log('');
|
|
124
127
|
}
|
|
125
128
|
|
|
126
|
-
// Configuration management - now using
|
|
129
|
+
// Configuration management - now using ai-benchmark-config.json for custom providers
|
|
127
130
|
async function loadConfig() {
|
|
128
131
|
try {
|
|
129
132
|
// Check if we need to migrate from old config
|
|
130
133
|
const oldConfigFile = 'ai-benchmark-config.json';
|
|
131
134
|
if (fs.existsSync(oldConfigFile)) {
|
|
132
|
-
console.log(colorText('Migrating from old config format to
|
|
135
|
+
console.log(colorText('Migrating from old config format to new format...', 'yellow'));
|
|
133
136
|
|
|
134
137
|
try {
|
|
135
138
|
const data = fs.readFileSync(oldConfigFile, 'utf8');
|
|
@@ -156,7 +159,7 @@ async function loadConfig() {
|
|
|
156
159
|
}
|
|
157
160
|
}
|
|
158
161
|
|
|
159
|
-
// Load providers from
|
|
162
|
+
// Load providers from both auth.json (verified) and ai-benchmark-config.json (custom)
|
|
160
163
|
const providers = await getAllAvailableProviders();
|
|
161
164
|
|
|
162
165
|
return {
|
|
@@ -169,11 +172,11 @@ async function loadConfig() {
|
|
|
169
172
|
}
|
|
170
173
|
}
|
|
171
174
|
|
|
172
|
-
// Save config - now using
|
|
175
|
+
// Save config - now using ai-benchmark-config.json and auth.json
|
|
173
176
|
async function saveConfig(config) {
|
|
174
177
|
// Note: This function is kept for compatibility but the actual saving
|
|
175
|
-
// is handled by the opencode
|
|
176
|
-
console.log(colorText('Note: Configuration is now automatically saved to
|
|
178
|
+
// is handled by the ai-config.js and opencode-integration.js functions
|
|
179
|
+
console.log(colorText('Note: Configuration is now automatically saved to ai-benchmark-config.json and auth.json', 'cyan'));
|
|
177
180
|
}
|
|
178
181
|
|
|
179
182
|
// Keyboard input handling
|
|
@@ -822,18 +825,15 @@ async function displayColorfulResults(results, method = 'AI SDK') {
|
|
|
822
825
|
}
|
|
823
826
|
|
|
824
827
|
// Helper function to calculate visible items based on terminal height
|
|
825
|
-
function getVisibleItemsCount(headerHeight =
|
|
828
|
+
function getVisibleItemsCount(headerHeight = 10) {
|
|
826
829
|
const terminalHeight = process.stdout.rows || 24;
|
|
827
|
-
return Math.max(
|
|
830
|
+
return Math.max(3, terminalHeight - headerHeight);
|
|
828
831
|
}
|
|
829
832
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
console.log(colorText('Add Provider', 'magenta'));
|
|
835
|
-
console.log('');
|
|
836
|
-
|
|
833
|
+
|
|
834
|
+
|
|
835
|
+
// Add a verified provider (saves to both auth.json and ai-benchmark-config.json)
|
|
836
|
+
async function addVerifiedProvider() {
|
|
837
837
|
let searchQuery = '';
|
|
838
838
|
let allProviders = [];
|
|
839
839
|
let filteredProviders = [];
|
|
@@ -845,6 +845,10 @@ async function addProvider() {
|
|
|
845
845
|
allProviders = await getAllProviders();
|
|
846
846
|
filteredProviders = allProviders;
|
|
847
847
|
} catch (error) {
|
|
848
|
+
clearScreen();
|
|
849
|
+
showHeader();
|
|
850
|
+
console.log(colorText('Add Verified Provider', 'magenta'));
|
|
851
|
+
console.log('');
|
|
848
852
|
console.log(colorText('Error loading providers: ', 'red') + error.message);
|
|
849
853
|
await question(colorText('Press Enter to continue...', 'yellow'));
|
|
850
854
|
return;
|
|
@@ -860,19 +864,19 @@ async function addProvider() {
|
|
|
860
864
|
screenContent += colorText('Note: opencode uses ai-sdk', 'dim') + '\n';
|
|
861
865
|
screenContent += '\n';
|
|
862
866
|
|
|
863
|
-
screenContent += colorText('Add Provider', 'magenta') + '\n';
|
|
867
|
+
screenContent += colorText('Add Verified Provider', 'magenta') + '\n';
|
|
864
868
|
screenContent += colorText('Use ↑↓ arrows to navigate, ENTER to select', 'cyan') + '\n';
|
|
865
869
|
screenContent += colorText('Type to search (real-time filtering)', 'cyan') + '\n';
|
|
866
870
|
screenContent += colorText('Navigation is circular', 'dim') + '\n';
|
|
867
871
|
screenContent += '\n';
|
|
868
872
|
|
|
869
873
|
// Search interface - always visible
|
|
870
|
-
screenContent += colorText('Search: ', 'yellow') + colorText(searchQuery + '_', 'bright') + '\n';
|
|
874
|
+
screenContent += colorText('🔍 Search: ', 'yellow') + colorText(searchQuery + '_', 'bright') + '\n';
|
|
871
875
|
screenContent += '\n';
|
|
872
876
|
|
|
873
877
|
// Calculate pagination
|
|
874
|
-
const visibleItemsCount = getVisibleItemsCount();
|
|
875
|
-
const totalItems = filteredProviders.length
|
|
878
|
+
const visibleItemsCount = getVisibleItemsCount(11); // Account for search bar and header
|
|
879
|
+
const totalItems = filteredProviders.length;
|
|
876
880
|
const totalPages = Math.ceil(totalItems / visibleItemsCount);
|
|
877
881
|
|
|
878
882
|
// Ensure current page is valid
|
|
@@ -883,7 +887,7 @@ async function addProvider() {
|
|
|
883
887
|
const endIndex = Math.min(startIndex + visibleItemsCount, totalItems);
|
|
884
888
|
|
|
885
889
|
// Display providers with pagination
|
|
886
|
-
screenContent += colorText('Available Providers:', 'cyan') + '\n';
|
|
890
|
+
screenContent += colorText('Available Verified Providers:', 'cyan') + '\n';
|
|
887
891
|
screenContent += '\n';
|
|
888
892
|
|
|
889
893
|
// Show current page of providers
|
|
@@ -897,16 +901,6 @@ async function addProvider() {
|
|
|
897
901
|
screenContent += `${indicator} ${providerName} ${providerType}\n`;
|
|
898
902
|
}
|
|
899
903
|
|
|
900
|
-
// Show "Add Custom Provider" option if it's on current page
|
|
901
|
-
const customIndex = filteredProviders.length;
|
|
902
|
-
if (customIndex >= startIndex && customIndex < endIndex) {
|
|
903
|
-
const isCustomCurrent = customIndex === currentIndex;
|
|
904
|
-
const customIndicator = isCustomCurrent ? colorText('●', 'green') : colorText('○', 'dim');
|
|
905
|
-
const customText = isCustomCurrent ? colorText('Add Custom Provider', 'bright') : colorText('Add Custom Provider', 'yellow');
|
|
906
|
-
|
|
907
|
-
screenContent += `${customIndicator} ${customText}\n`;
|
|
908
|
-
}
|
|
909
|
-
|
|
910
904
|
// Show pagination info
|
|
911
905
|
screenContent += '\n';
|
|
912
906
|
if (totalPages > 1) {
|
|
@@ -959,14 +953,8 @@ async function addProvider() {
|
|
|
959
953
|
currentIndex = currentPage * visibleItemsCount;
|
|
960
954
|
}
|
|
961
955
|
} else if (key === '\r') {
|
|
962
|
-
// Enter - select current
|
|
963
|
-
|
|
964
|
-
// Custom provider selected
|
|
965
|
-
await addCustomProvider();
|
|
966
|
-
} else {
|
|
967
|
-
// Verified provider selected - auto-add all models
|
|
968
|
-
await addVerifiedProviderAuto(filteredProviders[currentIndex]);
|
|
969
|
-
}
|
|
956
|
+
// Enter - select current provider
|
|
957
|
+
await addVerifiedProviderAuto(filteredProviders[currentIndex]);
|
|
970
958
|
break;
|
|
971
959
|
} else if (key === '\u0003') {
|
|
972
960
|
// Ctrl+C
|
|
@@ -1036,18 +1024,26 @@ async function addVerifiedProviderAuto(provider) {
|
|
|
1036
1024
|
return;
|
|
1037
1025
|
}
|
|
1038
1026
|
|
|
1039
|
-
// Add API key to
|
|
1040
|
-
const
|
|
1027
|
+
// Add API key to auth.json (for opencode integration)
|
|
1028
|
+
const authSuccess = await addApiKey(provider.id, apiKey);
|
|
1041
1029
|
|
|
1042
|
-
if (!
|
|
1043
|
-
console.log(colorText('Failed to save API key to
|
|
1030
|
+
if (!authSuccess) {
|
|
1031
|
+
console.log(colorText('Failed to save API key to auth.json', 'red'));
|
|
1044
1032
|
await question(colorText('Press Enter to continue...', 'yellow'));
|
|
1045
1033
|
return;
|
|
1046
1034
|
}
|
|
1047
1035
|
|
|
1036
|
+
// Also add to ai-benchmark-config.json for consistency
|
|
1037
|
+
const { addVerifiedProvider: addVerifiedProviderToConfig } = await import('./ai-config.js');
|
|
1038
|
+
const configSuccess = await addVerifiedProviderToConfig(provider.id, apiKey);
|
|
1039
|
+
|
|
1040
|
+
if (!configSuccess) {
|
|
1041
|
+
console.log(colorText('Warning: Could not save to ai-benchmark-config.json', 'yellow'));
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1048
1044
|
console.log('');
|
|
1049
1045
|
console.log(colorText('Provider added successfully!', 'green'));
|
|
1050
|
-
console.log(colorText(`API key saved to
|
|
1046
|
+
console.log(colorText(`API key saved to auth.json`, 'cyan'));
|
|
1051
1047
|
console.log(colorText(`Models will be loaded dynamically from ${provider.name}`, 'cyan'));
|
|
1052
1048
|
console.log(colorText(`Found ${models.length} available models`, 'cyan'));
|
|
1053
1049
|
|
|
@@ -1056,19 +1052,19 @@ async function addVerifiedProviderAuto(provider) {
|
|
|
1056
1052
|
|
|
1057
1053
|
|
|
1058
1054
|
|
|
1059
|
-
// Add a custom provider (now
|
|
1060
|
-
async function
|
|
1055
|
+
// Add a custom provider (now using ai-benchmark-config.json)
|
|
1056
|
+
async function addCustomProviderCLI() {
|
|
1061
1057
|
clearScreen();
|
|
1062
1058
|
showHeader();
|
|
1063
1059
|
console.log(colorText('Add Custom Provider', 'magenta'));
|
|
1064
1060
|
console.log('');
|
|
1065
|
-
console.log(colorText('Note: Custom providers are saved to
|
|
1061
|
+
console.log(colorText('Note: Custom providers are saved to ai-benchmark-config.json', 'cyan'));
|
|
1066
1062
|
console.log('');
|
|
1067
1063
|
|
|
1068
1064
|
const providerOptions = [
|
|
1069
1065
|
{ id: 1, text: 'OpenAI Compatible', type: 'openai-compatible' },
|
|
1070
1066
|
{ id: 2, text: 'Anthropic', type: 'anthropic' },
|
|
1071
|
-
{ id: 3, text: 'Back to
|
|
1067
|
+
{ id: 3, text: 'Back to Custom Models menu', action: 'back' }
|
|
1072
1068
|
];
|
|
1073
1069
|
|
|
1074
1070
|
let currentIndex = 0;
|
|
@@ -1125,178 +1121,108 @@ async function addCustomProvider() {
|
|
|
1125
1121
|
|
|
1126
1122
|
if (selectedChoice.action === 'back') return;
|
|
1127
1123
|
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1124
|
+
const providerId = await question(colorText('Enter provider ID (e.g., my-openai): ', 'cyan'));
|
|
1125
|
+
const name = await question(colorText('Enter provider name (e.g., MyOpenAI): ', 'cyan'));
|
|
1126
|
+
const baseUrl = await question(colorText('Enter base URL (e.g., https://api.openai.com/v1): ', 'cyan'));
|
|
1127
|
+
const apiKey = await question(colorText('Enter API key: ', 'cyan'));
|
|
1128
|
+
|
|
1129
|
+
// Ask if user wants to add multiple models
|
|
1130
|
+
console.log('');
|
|
1131
|
+
console.log(colorText('Do you want to add multiple models?', 'cyan'));
|
|
1132
|
+
console.log(colorText('1. Add single model', 'yellow'));
|
|
1133
|
+
console.log(colorText('2. Add multiple models', 'yellow'));
|
|
1134
|
+
|
|
1135
|
+
const modelChoice = await question(colorText('Enter choice (1 or 2): ', 'cyan'));
|
|
1136
|
+
|
|
1137
|
+
let models = [];
|
|
1138
|
+
|
|
1139
|
+
if (modelChoice === '2') {
|
|
1140
|
+
// Multiple models mode
|
|
1136
1141
|
console.log('');
|
|
1137
|
-
console.log(colorText('
|
|
1138
|
-
console.log(colorText('
|
|
1139
|
-
console.log(colorText('2. Add multiple models', 'yellow'));
|
|
1140
|
-
|
|
1141
|
-
const modelChoice = await question(colorText('Enter choice (1 or 2): ', 'cyan'));
|
|
1142
|
-
|
|
1143
|
-
let models = {};
|
|
1144
|
-
|
|
1145
|
-
if (modelChoice === '2') {
|
|
1146
|
-
// Multiple models mode
|
|
1147
|
-
console.log('');
|
|
1148
|
-
console.log(colorText('Enter model names (one per line, empty line to finish):', 'cyan'));
|
|
1149
|
-
console.log(colorText('Examples: gpt-4, gpt-4-turbo, gpt-3.5-turbo', 'dim'));
|
|
1150
|
-
console.log('');
|
|
1151
|
-
|
|
1152
|
-
while (true) {
|
|
1153
|
-
const modelName = await question(colorText('Model name: ', 'cyan'));
|
|
1154
|
-
if (!modelName.trim()) break;
|
|
1155
|
-
|
|
1156
|
-
const modelId = modelName.trim().toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
1157
|
-
models[modelId] = {
|
|
1158
|
-
name: modelName.trim()
|
|
1159
|
-
};
|
|
1160
|
-
}
|
|
1161
|
-
} else {
|
|
1162
|
-
// Single model mode
|
|
1163
|
-
const modelName = await question(colorText('Enter model name (e.g., gpt-4): ', 'cyan'));
|
|
1164
|
-
const modelId = modelName.toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
1165
|
-
models[modelId] = {
|
|
1166
|
-
name: modelName
|
|
1167
|
-
};
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
if (Object.keys(models).length === 0) {
|
|
1171
|
-
console.log(colorText('At least one model is required.', 'red'));
|
|
1172
|
-
await question(colorText('Press Enter to continue...', 'yellow'));
|
|
1173
|
-
return;
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
// Create opencode.json format
|
|
1177
|
-
const { readOpencodeConfig } = await import('./opencode-integration.js');
|
|
1178
|
-
const config = await readOpencodeConfig();
|
|
1179
|
-
|
|
1180
|
-
config.provider = config.provider || {};
|
|
1181
|
-
config.provider[providerId] = {
|
|
1182
|
-
name,
|
|
1183
|
-
options: {
|
|
1184
|
-
apiKey,
|
|
1185
|
-
baseURL: baseUrl
|
|
1186
|
-
},
|
|
1187
|
-
models
|
|
1188
|
-
};
|
|
1189
|
-
|
|
1190
|
-
// Save to opencode.json using the integration module
|
|
1191
|
-
const { writeOpencodeConfig } = await import('./opencode-integration.js');
|
|
1192
|
-
const success = await writeOpencodeConfig(config);
|
|
1193
|
-
|
|
1194
|
-
if (!success) {
|
|
1195
|
-
console.log(colorText('Warning: Could not save to opencode.json', 'yellow'));
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
console.log(colorText('Provider added successfully!', 'green'));
|
|
1199
|
-
console.log(colorText(`Added ${Object.keys(models).length} model(s)`, 'cyan'));
|
|
1200
|
-
console.log(colorText(`Saved to opencode.json`, 'cyan'));
|
|
1201
|
-
|
|
1202
|
-
} else if (selectedChoice.type === 'anthropic') {
|
|
1203
|
-
// Anthropic
|
|
1204
|
-
const providerId = await question(colorText('Enter provider ID (e.g., my-anthropic): ', 'cyan'));
|
|
1205
|
-
const name = await question(colorText('Enter provider name (e.g., MyAnthropic): ', 'cyan'));
|
|
1206
|
-
const baseUrl = await question(colorText('Enter base URL (e.g., https://api.anthropic.com): ', 'cyan'));
|
|
1207
|
-
const apiKey = await question(colorText('Enter Anthropic API key: ', 'cyan'));
|
|
1208
|
-
|
|
1209
|
-
// Ask if user wants to add multiple models
|
|
1142
|
+
console.log(colorText('Enter model names (one per line, empty line to finish):', 'cyan'));
|
|
1143
|
+
console.log(colorText('Examples: gpt-4, gpt-4-turbo, gpt-3.5-turbo', 'dim'));
|
|
1210
1144
|
console.log('');
|
|
1211
|
-
console.log(colorText('Do you want to add multiple models?', 'cyan'));
|
|
1212
|
-
console.log(colorText('1. Add single model', 'yellow'));
|
|
1213
|
-
console.log(colorText('2. Add multiple models', 'yellow'));
|
|
1214
|
-
|
|
1215
|
-
const modelChoice = await question(colorText('Enter choice (1 or 2): ', 'cyan'));
|
|
1216
1145
|
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
// Multiple models mode
|
|
1221
|
-
console.log('');
|
|
1222
|
-
console.log(colorText('Enter model names (one per line, empty line to finish):', 'cyan'));
|
|
1223
|
-
console.log(colorText('Examples: claude-3-sonnet-20240229, claude-3-haiku-20240307', 'dim'));
|
|
1224
|
-
console.log('');
|
|
1146
|
+
while (true) {
|
|
1147
|
+
const modelName = await question(colorText('Model name: ', 'cyan'));
|
|
1148
|
+
if (!modelName.trim()) break;
|
|
1225
1149
|
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
models[modelId] = {
|
|
1232
|
-
name: modelName.trim()
|
|
1233
|
-
};
|
|
1234
|
-
}
|
|
1235
|
-
} else {
|
|
1236
|
-
// Single model mode
|
|
1237
|
-
const modelName = await question(colorText('Enter model name (e.g., claude-3-sonnet-20240229): ', 'cyan'));
|
|
1238
|
-
const modelId = modelName.toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
1239
|
-
models[modelId] = {
|
|
1240
|
-
name: modelName
|
|
1241
|
-
};
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
|
-
if (Object.keys(models).length === 0) {
|
|
1245
|
-
console.log(colorText('At least one model is required.', 'red'));
|
|
1246
|
-
await question(colorText('Press Enter to continue...', 'yellow'));
|
|
1247
|
-
return;
|
|
1150
|
+
const modelId = modelName.trim().toLowerCase().replace(/[^a-z0-9-]/g, '-') + '_' + Date.now();
|
|
1151
|
+
models.push({
|
|
1152
|
+
name: modelName.trim(),
|
|
1153
|
+
id: modelId
|
|
1154
|
+
});
|
|
1248
1155
|
}
|
|
1249
|
-
|
|
1250
|
-
//
|
|
1251
|
-
const
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
apiKey,
|
|
1259
|
-
baseURL: baseUrl
|
|
1260
|
-
},
|
|
1261
|
-
models
|
|
1262
|
-
};
|
|
1263
|
-
|
|
1264
|
-
// Save to opencode.json using the integration module
|
|
1265
|
-
const { writeOpencodeConfig } = await import('./opencode-integration.js');
|
|
1266
|
-
const success = await writeOpencodeConfig(config);
|
|
1267
|
-
|
|
1268
|
-
if (!success) {
|
|
1269
|
-
console.log(colorText('Warning: Could not save to opencode.json', 'yellow'));
|
|
1156
|
+
} else {
|
|
1157
|
+
// Single model mode
|
|
1158
|
+
const modelName = await question(colorText('Enter model name (e.g., gpt-4): ', 'cyan'));
|
|
1159
|
+
if (modelName.trim()) {
|
|
1160
|
+
const modelId = modelName.trim().toLowerCase().replace(/[^a-z0-9-]/g, '-') + '_' + Date.now();
|
|
1161
|
+
models.push({
|
|
1162
|
+
name: modelName.trim(),
|
|
1163
|
+
id: modelId
|
|
1164
|
+
});
|
|
1270
1165
|
}
|
|
1271
|
-
|
|
1272
|
-
console.log(colorText('Provider added successfully!', 'green'));
|
|
1273
|
-
console.log(colorText(`Added ${Object.keys(models).length} model(s)`, 'cyan'));
|
|
1274
|
-
console.log(colorText(`Saved to opencode.json`, 'cyan'));
|
|
1275
1166
|
}
|
|
1276
1167
|
|
|
1168
|
+
if (models.length === 0) {
|
|
1169
|
+
console.log(colorText('At least one model is required.', 'red'));
|
|
1170
|
+
await question(colorText('Press Enter to continue...', 'yellow'));
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
// Create provider data for ai-benchmark-config.json format
|
|
1175
|
+
const providerData = {
|
|
1176
|
+
id: providerId,
|
|
1177
|
+
name: name,
|
|
1178
|
+
type: selectedChoice.type,
|
|
1179
|
+
baseUrl: baseUrl,
|
|
1180
|
+
apiKey: apiKey,
|
|
1181
|
+
models: models
|
|
1182
|
+
};
|
|
1183
|
+
|
|
1184
|
+
// Save to ai-benchmark-config.json
|
|
1185
|
+
const success = await addCustomProvider(providerData);
|
|
1186
|
+
|
|
1187
|
+
if (!success) {
|
|
1188
|
+
console.log(colorText('Failed to save custom provider.', 'red'));
|
|
1189
|
+
await question(colorText('Press Enter to continue...', 'yellow'));
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
console.log(colorText('Custom provider added successfully!', 'green'));
|
|
1194
|
+
console.log(colorText(`Added ${models.length} model(s)`, 'cyan'));
|
|
1195
|
+
console.log(colorText(`Saved to ai-benchmark-config.json`, 'cyan'));
|
|
1196
|
+
|
|
1277
1197
|
await question(colorText('\nPress Enter to continue...', 'yellow'));
|
|
1278
1198
|
}
|
|
1279
1199
|
|
|
1280
|
-
// Show debug information about
|
|
1200
|
+
// Show debug information about config system
|
|
1281
1201
|
async function showDebugInfo() {
|
|
1282
1202
|
clearScreen();
|
|
1283
1203
|
showHeader();
|
|
1284
|
-
console.log(colorText('
|
|
1204
|
+
console.log(colorText('Config System Debug Info', 'magenta'));
|
|
1285
1205
|
console.log('');
|
|
1286
1206
|
|
|
1287
1207
|
const debugInfo = await getDebugInfo();
|
|
1288
1208
|
|
|
1289
|
-
console.log(colorText('
|
|
1290
|
-
console.log(colorText(` auth.json: ${debugInfo.
|
|
1291
|
-
console.log(colorText(` opencode.json: ${debugInfo.
|
|
1209
|
+
console.log(colorText('OpenCode Paths (deprecated):', 'cyan'));
|
|
1210
|
+
console.log(colorText(` auth.json: ${debugInfo.opencodePaths.authJson}`, 'white'));
|
|
1211
|
+
console.log(colorText(` opencode.json: ${debugInfo.opencodePaths.opencodeJson}`, 'white'));
|
|
1212
|
+
console.log('');
|
|
1213
|
+
|
|
1214
|
+
console.log(colorText('AI Speedometer Config Paths:', 'cyan'));
|
|
1215
|
+
console.log(colorText(` ai-benchmark-config.json: ${debugInfo.aiConfigPaths.configJson}`, 'white'));
|
|
1216
|
+
console.log(colorText(` Config directory: ${debugInfo.aiConfigPaths.configDir}`, 'white'));
|
|
1292
1217
|
console.log('');
|
|
1293
1218
|
|
|
1294
1219
|
console.log(colorText('File Status:', 'cyan'));
|
|
1295
1220
|
console.log(colorText(` auth.json exists: ${debugInfo.authExists ? 'Yes' : 'No'}`, 'white'));
|
|
1296
|
-
console.log(colorText(` opencode.json exists: ${debugInfo.configExists ? '
|
|
1221
|
+
console.log(colorText(` opencode.json exists: ${debugInfo.configExists ? 'No' : 'No'}`, 'white'));
|
|
1222
|
+
console.log(colorText(` ai-benchmark-config.json exists: ${debugInfo.aiConfigPaths.configExists ? 'Yes' : 'No'}`, 'white'));
|
|
1297
1223
|
console.log('');
|
|
1298
1224
|
|
|
1299
|
-
console.log(colorText('Authenticated Providers:', 'cyan'));
|
|
1225
|
+
console.log(colorText('Authenticated Providers (auth.json):', 'cyan'));
|
|
1300
1226
|
if (debugInfo.authData.length === 0) {
|
|
1301
1227
|
console.log(colorText(' None', 'dim'));
|
|
1302
1228
|
} else {
|
|
@@ -1306,17 +1232,27 @@ async function showDebugInfo() {
|
|
|
1306
1232
|
}
|
|
1307
1233
|
console.log('');
|
|
1308
1234
|
|
|
1309
|
-
console.log(colorText('
|
|
1310
|
-
if (debugInfo.
|
|
1235
|
+
console.log(colorText('Verified Providers (ai-benchmark-config.json):', 'cyan'));
|
|
1236
|
+
if (debugInfo.aiConfigData.verifiedProviders.length === 0) {
|
|
1237
|
+
console.log(colorText(' None', 'dim'));
|
|
1238
|
+
} else {
|
|
1239
|
+
debugInfo.aiConfigData.verifiedProviders.forEach(provider => {
|
|
1240
|
+
console.log(colorText(` - ${provider}`, 'white'));
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
console.log('');
|
|
1244
|
+
|
|
1245
|
+
console.log(colorText('Custom Providers (ai-benchmark-config.json):', 'cyan'));
|
|
1246
|
+
if (debugInfo.aiConfigData.customProviders.length === 0) {
|
|
1311
1247
|
console.log(colorText(' None', 'dim'));
|
|
1312
1248
|
} else {
|
|
1313
|
-
debugInfo.
|
|
1249
|
+
debugInfo.aiConfigData.customProviders.forEach(provider => {
|
|
1314
1250
|
console.log(colorText(` - ${provider}`, 'white'));
|
|
1315
1251
|
});
|
|
1316
1252
|
}
|
|
1317
1253
|
console.log('');
|
|
1318
1254
|
|
|
1319
|
-
console.log(colorText('XDG Paths:', 'cyan'));
|
|
1255
|
+
console.log(colorText('XDG Paths (for OpenCode):', 'cyan'));
|
|
1320
1256
|
console.log(colorText(` Data: ${debugInfo.xdgPaths.data}`, 'white'));
|
|
1321
1257
|
console.log(colorText(` Config: ${debugInfo.xdgPaths.config}`, 'white'));
|
|
1322
1258
|
console.log('');
|
|
@@ -1335,35 +1271,129 @@ async function listProviders() {
|
|
|
1335
1271
|
if (config.providers.length === 0) {
|
|
1336
1272
|
console.log(colorText('No providers configured yet.', 'yellow'));
|
|
1337
1273
|
} else {
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
console.log(colorText(' Models:', 'dim'));
|
|
1343
|
-
provider.models.forEach((model, modelIndex) => {
|
|
1344
|
-
console.log(colorText(` ${modelIndex + 1}. ${model.name}`, 'yellow'));
|
|
1345
|
-
});
|
|
1346
|
-
} else {
|
|
1347
|
-
console.log(colorText(' Models: None', 'dim'));
|
|
1348
|
-
}
|
|
1349
|
-
|
|
1350
|
-
console.log('');
|
|
1274
|
+
// Separate verified and custom providers
|
|
1275
|
+
const verifiedProviders = config.providers.filter(p => {
|
|
1276
|
+
// Verified providers come from auth.json via models.dev
|
|
1277
|
+
return p.baseUrl && p.baseUrl.includes('api.'); // Simple heuristic
|
|
1351
1278
|
});
|
|
1279
|
+
|
|
1280
|
+
const customProviders = config.providers.filter(p => {
|
|
1281
|
+
return !verifiedProviders.includes(p);
|
|
1282
|
+
});
|
|
1283
|
+
|
|
1284
|
+
// Show verified providers
|
|
1285
|
+
if (verifiedProviders.length > 0) {
|
|
1286
|
+
console.log(colorText('Verified Providers (from models.dev):', 'green'));
|
|
1287
|
+
verifiedProviders.forEach((provider, index) => {
|
|
1288
|
+
console.log(colorText(`${index + 1}. ${provider.name} (${provider.type})`, 'cyan'));
|
|
1289
|
+
|
|
1290
|
+
if (provider.models.length > 0) {
|
|
1291
|
+
console.log(colorText(' Models:', 'dim'));
|
|
1292
|
+
provider.models.forEach((model, modelIndex) => {
|
|
1293
|
+
console.log(colorText(` ${modelIndex + 1}. ${model.name}`, 'yellow'));
|
|
1294
|
+
});
|
|
1295
|
+
} else {
|
|
1296
|
+
console.log(colorText(' Models: None', 'dim'));
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
console.log('');
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
// Show custom providers
|
|
1304
|
+
if (customProviders.length > 0) {
|
|
1305
|
+
console.log(colorText('Custom Providers:', 'magenta'));
|
|
1306
|
+
customProviders.forEach((provider, index) => {
|
|
1307
|
+
console.log(colorText(`${index + 1}. ${provider.name} (${provider.type})`, 'cyan'));
|
|
1308
|
+
|
|
1309
|
+
if (provider.models.length > 0) {
|
|
1310
|
+
console.log(colorText(' Models:', 'dim'));
|
|
1311
|
+
provider.models.forEach((model, modelIndex) => {
|
|
1312
|
+
console.log(colorText(` ${modelIndex + 1}. ${model.name}`, 'yellow'));
|
|
1313
|
+
});
|
|
1314
|
+
} else {
|
|
1315
|
+
console.log(colorText(' Models: None', 'dim'));
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
console.log('');
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1352
1321
|
}
|
|
1353
1322
|
|
|
1354
1323
|
await question(colorText('Press Enter to continue...', 'yellow'));
|
|
1355
1324
|
}
|
|
1356
1325
|
|
|
1357
|
-
|
|
1326
|
+
// Add Custom Models submenu
|
|
1327
|
+
async function addCustomModelsMenu() {
|
|
1328
|
+
const menuOptions = [
|
|
1329
|
+
{ id: 1, text: 'Add Models to Existing Provider', action: () => addModelsToExistingProvider() },
|
|
1330
|
+
{ id: 2, text: 'Add Custom Provider', action: () => addCustomProviderCLI() },
|
|
1331
|
+
{ id: 3, text: 'Back to Model Management', action: () => 'back' }
|
|
1332
|
+
];
|
|
1333
|
+
|
|
1334
|
+
let currentIndex = 0;
|
|
1335
|
+
|
|
1336
|
+
while (true) {
|
|
1337
|
+
// Build screen content in memory (double buffering)
|
|
1338
|
+
let screenContent = '';
|
|
1339
|
+
|
|
1340
|
+
// Add header
|
|
1341
|
+
screenContent += colorText('Ai-speedometer', 'cyan') + '\n';
|
|
1342
|
+
screenContent += colorText('=============================', 'cyan') + '\n';
|
|
1343
|
+
screenContent += colorText('Note: opencode uses ai-sdk', 'dim') + '\n';
|
|
1344
|
+
screenContent += '\n';
|
|
1345
|
+
|
|
1346
|
+
screenContent += colorText('Add Custom Models', 'magenta') + '\n';
|
|
1347
|
+
screenContent += colorText('Use ↑↓ arrows to navigate, ENTER to select', 'cyan') + '\n';
|
|
1348
|
+
screenContent += colorText('Navigation is circular', 'dim') + '\n';
|
|
1349
|
+
screenContent += '\n';
|
|
1350
|
+
|
|
1351
|
+
// Display menu options
|
|
1352
|
+
menuOptions.forEach((option, index) => {
|
|
1353
|
+
const isCurrent = index === currentIndex;
|
|
1354
|
+
const indicator = isCurrent ? colorText('●', 'green') : colorText('○', 'dim');
|
|
1355
|
+
const optionText = isCurrent ? colorText(option.text, 'bright') : colorText(option.text, 'yellow');
|
|
1356
|
+
|
|
1357
|
+
screenContent += `${indicator} ${optionText}\n`;
|
|
1358
|
+
});
|
|
1359
|
+
|
|
1360
|
+
// Clear screen and output entire buffer at once
|
|
1361
|
+
clearScreen();
|
|
1362
|
+
console.log(screenContent);
|
|
1363
|
+
|
|
1364
|
+
const key = await getKeyPress();
|
|
1365
|
+
|
|
1366
|
+
if (key === '\u001b[A') {
|
|
1367
|
+
// Up arrow - circular navigation
|
|
1368
|
+
currentIndex = (currentIndex - 1 + menuOptions.length) % menuOptions.length;
|
|
1369
|
+
} else if (key === '\u001b[B') {
|
|
1370
|
+
// Down arrow - circular navigation
|
|
1371
|
+
currentIndex = (currentIndex + 1) % menuOptions.length;
|
|
1372
|
+
} else if (key === '\r') {
|
|
1373
|
+
// Enter - select current option
|
|
1374
|
+
const result = await menuOptions[currentIndex].action();
|
|
1375
|
+
if (result === 'back') {
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
} else if (key === '\u0003') {
|
|
1379
|
+
// Ctrl+C
|
|
1380
|
+
process.exit(0);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
// Add models to existing custom provider
|
|
1386
|
+
async function addModelsToExistingProvider() {
|
|
1358
1387
|
clearScreen();
|
|
1359
1388
|
showHeader();
|
|
1360
|
-
console.log(colorText('Add
|
|
1389
|
+
console.log(colorText('Add Models to Existing Provider', 'magenta'));
|
|
1361
1390
|
console.log('');
|
|
1362
1391
|
|
|
1363
|
-
|
|
1392
|
+
// Get custom providers from config
|
|
1393
|
+
const customProviders = await getCustomProvidersFromConfig();
|
|
1364
1394
|
|
|
1365
|
-
if (
|
|
1366
|
-
console.log(colorText('No providers available. Please add a provider first.', 'red'));
|
|
1395
|
+
if (customProviders.length === 0) {
|
|
1396
|
+
console.log(colorText('No custom providers available. Please add a custom provider first.', 'red'));
|
|
1367
1397
|
await question(colorText('Press Enter to continue...', 'yellow'));
|
|
1368
1398
|
return;
|
|
1369
1399
|
}
|
|
@@ -1380,21 +1410,22 @@ async function addModelToProvider() {
|
|
|
1380
1410
|
screenContent += colorText('Note: opencode uses ai-sdk', 'dim') + '\n';
|
|
1381
1411
|
screenContent += '\n';
|
|
1382
1412
|
|
|
1383
|
-
screenContent += colorText('Add
|
|
1413
|
+
screenContent += colorText('Add Models to Existing Provider', 'magenta') + '\n';
|
|
1384
1414
|
screenContent += colorText('Use ↑↓ arrows to navigate, ENTER to select', 'cyan') + '\n';
|
|
1385
1415
|
screenContent += colorText('Navigation is circular', 'dim') + '\n';
|
|
1386
1416
|
screenContent += '\n';
|
|
1387
1417
|
|
|
1388
|
-
screenContent += colorText('Select provider:', 'cyan') + '\n';
|
|
1418
|
+
screenContent += colorText('Select custom provider:', 'cyan') + '\n';
|
|
1389
1419
|
screenContent += '\n';
|
|
1390
1420
|
|
|
1391
|
-
// Display providers with arrow key navigation
|
|
1392
|
-
|
|
1421
|
+
// Display custom providers with arrow key navigation
|
|
1422
|
+
customProviders.forEach((provider, index) => {
|
|
1393
1423
|
const isCurrent = index === currentIndex;
|
|
1394
1424
|
const indicator = isCurrent ? colorText('●', 'green') : colorText('○', 'dim');
|
|
1395
1425
|
const providerName = isCurrent ? colorText(provider.name, 'bright') : colorText(provider.name, 'yellow');
|
|
1426
|
+
const providerType = isCurrent ? colorText(`(${provider.type})`, 'cyan') : colorText(`(${provider.type})`, 'dim');
|
|
1396
1427
|
|
|
1397
|
-
screenContent += `${indicator} ${providerName}\n`;
|
|
1428
|
+
screenContent += `${indicator} ${providerName} ${providerType}\n`;
|
|
1398
1429
|
});
|
|
1399
1430
|
|
|
1400
1431
|
// Clear screen and output entire buffer at once
|
|
@@ -1405,10 +1436,10 @@ async function addModelToProvider() {
|
|
|
1405
1436
|
|
|
1406
1437
|
if (key === '\u001b[A') {
|
|
1407
1438
|
// Up arrow - circular navigation
|
|
1408
|
-
currentIndex = (currentIndex - 1 +
|
|
1439
|
+
currentIndex = (currentIndex - 1 + customProviders.length) % customProviders.length;
|
|
1409
1440
|
} else if (key === '\u001b[B') {
|
|
1410
1441
|
// Down arrow - circular navigation
|
|
1411
|
-
currentIndex = (currentIndex + 1) %
|
|
1442
|
+
currentIndex = (currentIndex + 1) % customProviders.length;
|
|
1412
1443
|
} else if (key === '\r') {
|
|
1413
1444
|
// Enter - select current provider
|
|
1414
1445
|
break;
|
|
@@ -1418,31 +1449,73 @@ async function addModelToProvider() {
|
|
|
1418
1449
|
}
|
|
1419
1450
|
}
|
|
1420
1451
|
|
|
1421
|
-
const provider =
|
|
1422
|
-
const modelName = await question(colorText('Enter new model name: ', 'cyan'));
|
|
1452
|
+
const provider = customProviders[currentIndex];
|
|
1423
1453
|
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1454
|
+
console.log('');
|
|
1455
|
+
console.log(colorText('Selected provider: ', 'cyan') + colorText(provider.name, 'white'));
|
|
1456
|
+
console.log('');
|
|
1457
|
+
|
|
1458
|
+
// Ask if user wants to add multiple models
|
|
1459
|
+
console.log(colorText('Do you want to add multiple models?', 'cyan'));
|
|
1460
|
+
console.log(colorText('1. Add single model', 'yellow'));
|
|
1461
|
+
console.log(colorText('2. Add multiple models', 'yellow'));
|
|
1462
|
+
|
|
1463
|
+
const modelChoice = await question(colorText('Enter choice (1 or 2): ', 'cyan'));
|
|
1464
|
+
|
|
1465
|
+
let modelsAdded = 0;
|
|
1466
|
+
|
|
1467
|
+
if (modelChoice === '2') {
|
|
1468
|
+
// Multiple models mode
|
|
1469
|
+
console.log('');
|
|
1470
|
+
console.log(colorText('Enter model names (one per line, empty line to finish):', 'cyan'));
|
|
1471
|
+
console.log(colorText('Examples: gpt-4, gpt-4-turbo, gpt-3.5-turbo', 'dim'));
|
|
1472
|
+
console.log('');
|
|
1473
|
+
|
|
1474
|
+
while (true) {
|
|
1475
|
+
const modelName = await question(colorText('Model name: ', 'cyan'));
|
|
1476
|
+
if (!modelName.trim()) break;
|
|
1477
|
+
|
|
1478
|
+
const modelId = modelName.trim().toLowerCase().replace(/[^a-z0-9-]/g, '-') + '_' + Date.now();
|
|
1479
|
+
const modelData = {
|
|
1480
|
+
name: modelName.trim(),
|
|
1481
|
+
id: modelId
|
|
1482
|
+
};
|
|
1483
|
+
|
|
1484
|
+
const success = await addModelToCustomProvider(provider.id, modelData);
|
|
1485
|
+
if (success) {
|
|
1486
|
+
modelsAdded++;
|
|
1487
|
+
console.log(colorText(`✓ Added model: ${modelName.trim()}`, 'green'));
|
|
1488
|
+
} else {
|
|
1489
|
+
console.log(colorText(`✗ Failed to add model: ${modelName.trim()}`, 'red'));
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1431
1492
|
} else {
|
|
1432
|
-
//
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1493
|
+
// Single model mode
|
|
1494
|
+
const modelName = await question(colorText('Enter model name: ', 'cyan'));
|
|
1495
|
+
if (modelName.trim()) {
|
|
1496
|
+
const modelId = modelName.trim().toLowerCase().replace(/[^a-z0-9-]/g, '-') + '_' + Date.now();
|
|
1497
|
+
const modelData = {
|
|
1498
|
+
name: modelName.trim(),
|
|
1499
|
+
id: modelId
|
|
1500
|
+
};
|
|
1501
|
+
|
|
1502
|
+
const success = await addModelToCustomProvider(provider.id, modelData);
|
|
1503
|
+
if (success) {
|
|
1504
|
+
modelsAdded = 1;
|
|
1505
|
+
console.log(colorText(`✓ Added model: ${modelName.trim()}`, 'green'));
|
|
1506
|
+
} else {
|
|
1507
|
+
console.log(colorText(`✗ Failed to add model: ${modelName.trim()}`, 'red'));
|
|
1508
|
+
}
|
|
1440
1509
|
}
|
|
1441
|
-
config.customProviders.push(provider);
|
|
1442
1510
|
}
|
|
1443
1511
|
|
|
1444
|
-
|
|
1445
|
-
|
|
1512
|
+
if (modelsAdded > 0) {
|
|
1513
|
+
console.log('');
|
|
1514
|
+
console.log(colorText(`Successfully added ${modelsAdded} model(s) to ${provider.name}`, 'green'));
|
|
1515
|
+
} else {
|
|
1516
|
+
console.log(colorText('No models were added.', 'yellow'));
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1446
1519
|
await question(colorText('\nPress Enter to continue...', 'yellow'));
|
|
1447
1520
|
}
|
|
1448
1521
|
|
|
@@ -1698,9 +1771,9 @@ async function showMainMenu() {
|
|
|
1698
1771
|
|
|
1699
1772
|
async function showModelMenu() {
|
|
1700
1773
|
const menuOptions = [
|
|
1701
|
-
{ id: 1, text: 'Add Provider', action: () =>
|
|
1702
|
-
{ id: 2, text: '
|
|
1703
|
-
{ id: 3, text: '
|
|
1774
|
+
{ id: 1, text: 'Add Verified Provider', action: () => addVerifiedProvider() },
|
|
1775
|
+
{ id: 2, text: 'Add Custom Models', action: () => addCustomModelsMenu() },
|
|
1776
|
+
{ id: 3, text: 'List Existing Providers', action: () => listProviders() },
|
|
1704
1777
|
{ id: 4, text: 'Debug Info', action: () => showDebugInfo() },
|
|
1705
1778
|
{ id: 5, text: 'Back to Main Menu', action: () => 'back' }
|
|
1706
1779
|
];
|
|
@@ -1770,4 +1843,4 @@ if (import.meta.url === `file://${process.argv[1]}` ||
|
|
|
1770
1843
|
showMainMenu();
|
|
1771
1844
|
}
|
|
1772
1845
|
|
|
1773
|
-
export { showMainMenu,
|
|
1846
|
+
export { showMainMenu, listProviders, selectModelsCircular, runStreamingBenchmark, loadConfig, saveConfig };
|