route-claudecode
Version:
Advanced routing and transformation system for Claude Code outputs to multiple AI providers
509 lines • 20.5 kB
JavaScript
;
/**
* Test command implementation for RCC CLI
* Tests provider configurations and updates config files
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.executeTestCommand = executeTestCommand;
const axios_1 = __importDefault(require("axios"));
const chalk_1 = __importDefault(require("chalk"));
const fs_1 = require("fs");
const path_1 = require("path");
const os_1 = require("os");
const genai_1 = require("@google/genai");
/**
* 测试Gemini API keys
*/
async function testGeminiApiKeys(apiKeys) {
const workingKeys = [];
for (const key of apiKeys) {
try {
const genAI = new genai_1.GoogleGenAI({ apiKey: key });
// Simple health check with gemini-2.5-flash (most stable model)
const testResponse = await genAI.models.generateContent({
model: 'gemini-2.5-flash',
contents: [{
role: 'user',
parts: [{ text: 'Hi' }]
}]
});
const success = !!(testResponse && testResponse.candidates && testResponse.candidates.length > 0);
if (success) {
workingKeys.push(key);
}
}
catch (error) {
// Key不工作,跳过
}
}
return workingKeys;
}
/**
* 测试单个Gemini模型
*/
async function testGeminiModel(apiKey, model) {
const result = {
model,
available: false,
maxTokens: null,
supportsStreaming: false,
responseTime: null,
finishReason: null,
error: null
};
try {
const genAI = new genai_1.GoogleGenAI({ apiKey });
const startTime = Date.now();
const testResponse = await genAI.models.generateContent({
model: model,
contents: [{
role: 'user',
parts: [{ text: 'Hi' }]
}]
});
result.available = true;
result.responseTime = Date.now() - startTime;
result.finishReason = testResponse.candidates?.[0]?.finishReason || null;
result.supportsStreaming = true; // Gemini supports streaming
// Set maxTokens based on known model limits
const geminiTokenLimits = {
'gemini-2.5-pro': 2097152,
'gemini-2.5-flash': 1048576,
'gemini-2.5-flash-lite': 1048576,
'gemini-2.0-flash': 1048576,
'gemini-2.0-flash-exp': 1048576,
'gemini-1.5-pro': 2097152,
'gemini-1.5-flash': 1048576,
'gemini-1.5-flash-8b': 1048576
};
result.maxTokens = geminiTokenLimits[model];
if (!result.maxTokens) {
throw new Error(`Unknown Gemini model token limit for: ${model}. Please add to configuration.`);
}
return result;
}
catch (error) {
result.error = error instanceof Error ? error.message : String(error);
return result;
}
}
/**
* 获取ModelScope支持的所有模型
*/
async function getModelScopeModels(apiKey) {
try {
const response = await axios_1.default.get('https://api-inference.modelscope.cn/v1/models', {
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
timeout: 15000
});
if (response.data && response.data.data) {
return response.data.data.map((model) => model.id);
}
return [];
}
catch (error) {
console.log(chalk_1.default.yellow(`⚠️ Failed to fetch ModelScope models: ${error.response?.data?.error?.message || error.message}`));
return [];
}
}
/**
* 测试单个模型
*/
async function testModel(apiKey, endpoint, model, providerType = 'openai') {
// 对于Gemini provider,使用专门的测试逻辑
if (providerType === 'gemini') {
return await testGeminiModel(apiKey, model);
}
const result = {
model,
available: false,
maxTokens: null,
supportsStreaming: false,
responseTime: null,
finishReason: null,
error: null
};
try {
// 基本可用性测试
const startTime = Date.now();
const basicResponse = await axios_1.default.post(endpoint, {
model,
messages: [{ role: 'user', content: 'Hi' }],
max_tokens: 10,
stream: false
}, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
timeout: 30000
});
result.available = true;
result.responseTime = Date.now() - startTime;
result.finishReason = basicResponse.data.choices?.[0]?.finish_reason;
// 测试max_tokens限制 - 扩展到长上下文模型支持
const tokenLimits = [1000, 10000, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152];
let maxValidTokens = 10;
for (const tokens of tokenLimits) {
try {
await axios_1.default.post(endpoint, {
model,
messages: [{ role: 'user', content: 'Hi' }],
max_tokens: tokens,
stream: false
}, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
timeout: 15000
});
maxValidTokens = tokens;
}
catch (error) {
break;
}
}
result.maxTokens = maxValidTokens;
// 基于模型名称识别已知的长上下文模型并设置合理的maxTokens
const longContextModels = {
'Qwen/Qwen2.5-14B-Instruct-1M': 1048576,
'Qwen/Qwen2.5-7B-Instruct-1M': 1048576,
'Qwen/Qwen2.5-72B-Instruct': 131072,
'Qwen/Qwen3-235B-A22B': 131072,
'Qwen/Qwen3-235B-A22B-Instruct-2507': 131072,
'Qwen/Qwen3-Coder-480B-A35B-Instruct': 65536,
'Qwen/QVQ-72B-Preview': 131072
};
if (longContextModels[model]) {
result.maxTokens = Math.max(result.maxTokens, longContextModels[model]);
}
// 测试流式支持
try {
const streamResponse = await axios_1.default.post(endpoint, {
model,
messages: [{ role: 'user', content: 'Hi' }],
max_tokens: 10,
stream: true
}, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
timeout: 15000,
responseType: 'stream'
});
result.supportsStreaming = streamResponse.status === 200;
}
catch (error) {
// 流式测试失败不影响整体结果
}
}
catch (error) {
result.error = error.response?.data?.error?.message || error.message;
}
return result;
}
/**
* 测试API密钥
*/
async function testApiKeys(apiKeys, endpoint, modelName, providerType = 'openai') {
// 对于Gemini provider,使用专门的测试逻辑
if (providerType === 'gemini') {
return await testGeminiApiKeys(apiKeys);
}
// 对于OpenAI兼容的providers,使用原有逻辑
const workingKeys = [];
for (const key of apiKeys) {
try {
const response = await axios_1.default.post(endpoint, {
model: modelName,
messages: [{ role: 'user', content: 'Hi' }],
max_tokens: 5,
stream: false
}, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${key}`
},
timeout: 10000
});
if (response.status === 200) {
workingKeys.push(key);
}
}
catch (error) {
// Key不工作,跳过
}
}
return workingKeys;
}
/**
* 测试单个provider
*/
async function testProvider(providerId, providerConfig, configPath, specificModel) {
console.log(chalk_1.default.blue(`\n🔧 Testing provider: ${providerId}`));
const result = {
providerId,
providerType: providerConfig.type,
endpoint: providerConfig.endpoint,
workingKeys: 0,
totalKeys: 0,
models: [],
error: null
};
try {
// 提取API密钥
const apiKeys = Array.isArray(providerConfig.authentication?.credentials?.apiKey)
? providerConfig.authentication.credentials.apiKey
: [providerConfig.authentication?.credentials?.apiKey].filter(Boolean);
result.totalKeys = apiKeys.length;
if (apiKeys.length === 0) {
result.error = 'No API keys found';
return result;
}
// 测试API密钥
const modelToTest = specificModel || providerConfig.models?.[0] || 'ZhipuAI/GLM-4.5';
const workingKeys = await testApiKeys(apiKeys, providerConfig.endpoint, modelToTest, providerConfig.type);
result.workingKeys = workingKeys.length;
if (workingKeys.length === 0) {
result.error = 'No working API keys found';
return result;
}
console.log(chalk_1.default.green(` ✅ ${workingKeys.length}/${apiKeys.length} API keys working`));
// 确定要测试的模型列表
let modelsToTest = [];
if (specificModel) {
modelsToTest = [specificModel];
}
else {
// 获取配置中的模型
modelsToTest = providerConfig.models || [];
// 只测试配置文件中指定的模型,不获取所有可用模型
if (providerConfig.endpoint?.includes('modelscope.cn') && modelsToTest.length > 0) {
console.log(chalk_1.default.gray(` 📋 Testing ${modelsToTest.length} configured models from ModelScope...`));
}
}
// 测试每个模型
console.log(chalk_1.default.gray(` 🧪 Testing ${modelsToTest.length} models...`));
for (const model of modelsToTest) {
console.log(chalk_1.default.gray(` Testing ${model}...`));
const modelResult = await testModel(workingKeys[0], providerConfig.endpoint, model, providerConfig.type);
result.models.push(modelResult);
if (modelResult.available) {
console.log(chalk_1.default.green(` ✅ Available (${modelResult.responseTime}ms, max_tokens: ${modelResult.maxTokens})`));
}
else {
console.log(chalk_1.default.red(` ❌ Failed: ${modelResult.error}`));
}
// 实时更新配置文件
if (configPath) {
updateSingleModelConfig(configPath, providerId, modelResult);
}
}
}
catch (error) {
result.error = error.message;
console.log(chalk_1.default.red(` ❌ Provider test failed: ${error.message}`));
}
return result;
}
/**
* 实时更新单个模型的配置
*/
function updateSingleModelConfig(configPath, providerId, modelResult) {
try {
const config = JSON.parse((0, fs_1.readFileSync)(configPath, 'utf8'));
const providerConfig = config.providers?.[providerId];
if (!providerConfig)
return false;
let updated = false;
// 更新maxTokens信息
if (modelResult.available && modelResult.maxTokens) {
if (!providerConfig.maxTokens) {
providerConfig.maxTokens = {};
}
const currentMaxTokens = providerConfig.maxTokens[modelResult.model];
if (currentMaxTokens !== modelResult.maxTokens) {
providerConfig.maxTokens[modelResult.model] = modelResult.maxTokens;
updated = true;
}
}
// 如果模型不可用且在配置中,考虑是否移除(保持原有模型配置)
// 这里我们保持配置不变,只更新已有模型的信息
if (updated) {
// 创建临时备份
const backupPath = configPath + '.tmp.' + Date.now();
(0, fs_1.writeFileSync)(backupPath, (0, fs_1.readFileSync)(configPath));
// 写入更新的配置
(0, fs_1.writeFileSync)(configPath, JSON.stringify(config, null, 2));
console.log(chalk_1.default.green(` 📝 Updated ${modelResult.model} config`));
// 删除临时备份
require('fs').unlinkSync(backupPath);
}
return updated;
}
catch (error) {
console.log(chalk_1.default.red(` ❌ Failed to update config: ${error.message}`));
return false;
}
}
/**
* 更新配置文件 (批量更新,保留作为备用)
*/
function updateConfigFile(configPath, testResults) {
try {
const config = JSON.parse((0, fs_1.readFileSync)(configPath, 'utf8'));
let updated = false;
for (const providerResult of testResults) {
const providerConfig = config.providers?.[providerResult.providerId];
if (!providerConfig)
continue;
// 更新models列表(只包含可用的模型)
const availableModels = providerResult.models
.filter(m => m.available)
.map(m => m.model);
if (availableModels.length > 0) {
const currentModels = providerConfig.models || [];
const newModels = [...new Set([...currentModels, ...availableModels])];
if (JSON.stringify(currentModels.sort()) !== JSON.stringify(newModels.sort())) {
providerConfig.models = newModels;
updated = true;
console.log(chalk_1.default.blue(` 📝 Updated models list for ${providerResult.providerId}`));
}
}
// 更新maxTokens配置
if (!providerConfig.maxTokens) {
providerConfig.maxTokens = {};
}
for (const modelResult of providerResult.models) {
if (modelResult.available && modelResult.maxTokens) {
const currentMaxTokens = providerConfig.maxTokens[modelResult.model];
if (!currentMaxTokens || currentMaxTokens !== modelResult.maxTokens) {
providerConfig.maxTokens[modelResult.model] = modelResult.maxTokens;
updated = true;
console.log(chalk_1.default.blue(` 📝 Updated maxTokens for ${modelResult.model}: ${modelResult.maxTokens}`));
}
}
}
}
if (updated) {
// 创建备份
const backupPath = configPath + '.backup.' + Date.now();
(0, fs_1.writeFileSync)(backupPath, (0, fs_1.readFileSync)(configPath));
console.log(chalk_1.default.gray(` 💾 Backup created: ${backupPath}`));
// 写入更新的配置
(0, fs_1.writeFileSync)(configPath, JSON.stringify(config, null, 2));
console.log(chalk_1.default.green(` ✅ Configuration updated: ${configPath}`));
}
return updated;
}
catch (error) {
console.log(chalk_1.default.red(` ❌ Failed to update config: ${error.message}`));
return false;
}
}
/**
* 执行测试命令
*/
async function executeTestCommand(provider, model, options = {}) {
console.log(chalk_1.default.cyan('🧪 RCC Provider Testing Tool'));
console.log('============================\n');
// 确定配置文件路径
const configPaths = [];
if (options.config) {
configPaths.push((0, path_1.resolve)(options.config));
}
else {
// 默认配置文件
const baseConfigDir = (0, path_1.join)((0, os_1.homedir)(), '.route-claude-code');
const defaultConfigs = [
(0, path_1.join)(baseConfigDir, 'config.json'),
(0, path_1.join)(baseConfigDir, 'config.release.json')
];
for (const configPath of defaultConfigs) {
if ((0, fs_1.existsSync)(configPath)) {
configPaths.push(configPath);
}
}
}
if (configPaths.length === 0) {
console.error(chalk_1.default.red('❌ No configuration files found'));
console.error(chalk_1.default.gray(' Please specify --config or ensure config.json/config.release.json exist'));
process.exit(1);
}
console.log(chalk_1.default.blue(`📁 Testing configurations:`));
configPaths.forEach(path => console.log(chalk_1.default.gray(` ${path}`)));
console.log('');
// 处理每个配置文件
for (const configPath of configPaths) {
console.log(chalk_1.default.cyan(`\n📄 Processing: ${configPath}`));
console.log('='.repeat(50));
try {
const config = JSON.parse((0, fs_1.readFileSync)(configPath, 'utf8'));
const testResults = [];
// 确定要测试的providers
const providersToTest = provider
? [provider]
: Object.keys(config.providers || {});
if (providersToTest.length === 0) {
console.log(chalk_1.default.yellow('⚠️ No providers found to test'));
continue;
}
// 测试每个provider
for (const providerId of providersToTest) {
const providerConfig = config.providers?.[providerId];
if (!providerConfig) {
console.log(chalk_1.default.red(`❌ Provider '${providerId}' not found in configuration`));
continue;
}
const result = await testProvider(providerId, providerConfig, configPath, model);
testResults.push(result);
}
// 更新配置文件
console.log(chalk_1.default.blue('\n📝 Updating configuration...'));
const updated = updateConfigFile(configPath, testResults);
// 输出测试总结
console.log(chalk_1.default.cyan('\n📊 Test Summary:'));
console.log('================');
for (const result of testResults) {
console.log(chalk_1.default.blue(`\n🔧 ${result.providerId} (${result.providerType}):`));
console.log(` Endpoint: ${result.endpoint}`);
console.log(` API Keys: ${result.workingKeys}/${result.totalKeys} working`);
console.log(` Models tested: ${result.models.length}`);
const availableModels = result.models.filter(m => m.available);
console.log(` Available models: ${availableModels.length}`);
if (availableModels.length > 0) {
console.log(chalk_1.default.green(' ✅ Available models:'));
availableModels.forEach(m => {
console.log(chalk_1.default.gray(` • ${m.model} (max_tokens: ${m.maxTokens}, streaming: ${m.supportsStreaming ? 'yes' : 'no'})`));
});
}
const failedModels = result.models.filter(m => !m.available);
if (failedModels.length > 0) {
console.log(chalk_1.default.red(' ❌ Failed models:'));
failedModels.forEach(m => {
console.log(chalk_1.default.gray(` • ${m.model}: ${m.error}`));
});
}
}
if (updated) {
console.log(chalk_1.default.green(`\n✅ Configuration file updated successfully`));
}
else {
console.log(chalk_1.default.gray(`\nℹ️ No configuration changes needed`));
}
}
catch (error) {
console.error(chalk_1.default.red(`❌ Failed to process ${configPath}:`), error.message);
}
}
console.log(chalk_1.default.cyan('\n🎉 Testing completed!'));
}
//# sourceMappingURL=test-command.js.map