zai-mcp-server
Version:
🚀 REVOLUTIONARY AI-to-AI Collaboration Platform v6.1! NEW: Advanced Debugging Tools with Screenshot Analysis, Console Error Parsing, Automated Fix Generation, 5 Specialized Debugging Agents, Visual UI Analysis, JavaScript Error Intelligence, CSS/HTML Fix
314 lines (275 loc) • 12.4 kB
JavaScript
import { CONFIG } from './config.js';
import { APIKeyManager } from './apiKeyManager.js';
// Use global fetch (available in Node.js 18+)
// If using older Node.js, uncomment the line below:
// import fetch from 'node-fetch';
export class OpenRouterClient {
constructor(apiKeys, model = CONFIG.DEFAULT_MODEL) {
// Initialize API Key Manager
this.apiKeyManager = new APIKeyManager(apiKeys);
this.primaryModel = model;
this.apiUrl = CONFIG.OPENROUTER_API_URL;
this.fallbackModels = CONFIG.FALLBACK_MODELS;
this.currentModelIndex = 0;
this.failedModels = new Set(); // Track models that have failed
// Ensure primary model is first in fallback list
if (!this.fallbackModels.includes(this.primaryModel)) {
this.fallbackModels = [this.primaryModel, ...this.fallbackModels];
}
console.error(`[OPENROUTER] Initialized with primary model: ${this.primaryModel}`);
console.error(`[OPENROUTER] Available fallback models: ${this.fallbackModels.length}`);
console.error(`[OPENROUTER] API Key Manager: ${this.apiKeyManager.apiKeys.length} key(s) available`);
}
/**
* Get next available model to try
* @returns {string|null} - Next model or null if all failed
*/
getNextModel() {
// Filter out failed models and get available ones
const availableModels = this.fallbackModels.filter(model => !this.failedModels.has(model));
if (availableModels.length === 0) {
console.error('[OPENROUTER] All models have failed, using local fallback');
return null;
}
// Get next model in rotation
const model = availableModels[this.currentModelIndex % availableModels.length];
this.currentModelIndex++;
return model;
}
/**
* Mark a model as failed
* @param {string} model - Model that failed
* @param {string} reason - Reason for failure
*/
markModelFailed(model, reason) {
this.failedModels.add(model);
console.error(`[OPENROUTER] Model ${model} marked as failed: ${reason}`);
console.error(`[OPENROUTER] Failed models: ${Array.from(this.failedModels).join(', ')}`);
}
/**
* Reset failed models (call periodically to retry failed models)
*/
resetFailedModels() {
console.error(`[OPENROUTER] Resetting ${this.failedModels.size} failed models`);
this.failedModels.clear();
this.currentModelIndex = 0;
}
/**
* Generate AI improvement using OpenRouter with model fallback
* @param {string} topic - The topic to improve
* @param {string} strategy - The improvement strategy
* @param {number} iteration - Current iteration number
* @param {string} lastAgentResponse - The agent's last response (optional)
* @returns {Promise<string>} - Generated improvement
*/
async generateImprovement(topic, strategy, iteration, lastAgentResponse = null) {
const systemPrompt = CONFIG.PROMPT_TEMPLATES.IMPROVEMENT_SYSTEM;
// Choose prompt template based on whether we have agent response
let userPrompt;
if (lastAgentResponse && lastAgentResponse.length > 10) {
userPrompt = CONFIG.PROMPT_TEMPLATES.IMPROVEMENT_USER_FOLLOWUP
.replace('{topic}', topic)
.replace('{iteration}', iteration)
.replace('{strategy}', strategy)
.replace('{strategy}', strategy) // Replace both occurrences
.replace('{agentResponse}', lastAgentResponse);
} else {
userPrompt = CONFIG.PROMPT_TEMPLATES.IMPROVEMENT_USER_FIRST
.replace('{topic}', topic)
.replace('{iteration}', iteration)
.replace('{strategy}', strategy)
.replace('{strategy}', strategy); // Replace both occurrences
}
// Try up to 3 different model/key combinations
for (let attempt = 0; attempt < 3; attempt++) {
const model = this.getNextModel();
if (!model) {
console.error('[OPENROUTER] No available models, falling back to local generation');
return this.generateFallbackImprovement(topic, strategy, iteration, lastAgentResponse);
}
// Get next available API key
const keyInfo = this.apiKeyManager.getNextAvailableKey();
if (!keyInfo) {
console.error('[OPENROUTER] No available API keys, falling back to local generation');
return this.generateFallbackImprovement(topic, strategy, iteration, lastAgentResponse);
}
try {
console.error(`[OPENROUTER] Attempt ${attempt + 1}: Trying model ${model} with API key ${keyInfo.index + 1}`);
const response = await fetch(this.apiUrl, {
method: 'POST',
headers: {
'Authorization': `Bearer ${keyInfo.key}`,
'Content-Type': 'application/json',
'HTTP-Referer': 'https://github.com/mcp-infinite-loop/mcp-infinite-loop-server',
'X-Title': 'MCP Infinite Loop Server'
},
body: JSON.stringify({
model: model,
messages: [
{
role: 'system',
content: systemPrompt
},
{
role: 'user',
content: userPrompt
}
],
temperature: 0.7,
max_tokens: 200,
top_p: 0.9
})
});
if (!response.ok) {
const errorData = await response.text();
const errorMsg = `${response.status} - ${errorData}`;
// Check if it's a rate limit or model-specific error
if (response.status === 429 || errorData.includes('rate-limited') || errorData.includes('temporarily')) {
console.error(`[OPENROUTER] API key ${keyInfo.index + 1} rate limited for model ${model}`);
this.apiKeyManager.markKeyRateLimited(keyInfo.index, `Rate limited (${response.status})`);
this.markModelFailed(model, 'Rate limited');
continue; // Try next model/key combination
} else if (response.status === 401 || errorData.includes('unauthorized') || errorData.includes('invalid')) {
console.error(`[OPENROUTER] API key ${keyInfo.index + 1} unauthorized or invalid`);
this.apiKeyManager.markKeyFailed(keyInfo.index, `Unauthorized (${response.status})`);
continue; // Try next key
} else if (response.status === 404 || errorData.includes('not found')) {
console.error(`[OPENROUTER] Model ${model} not found with API key ${keyInfo.index + 1}`);
this.markModelFailed(model, 'Model not found');
continue; // Try next model
} else {
console.error(`[OPENROUTER] API error with key ${keyInfo.index + 1}: ${errorMsg}`);
this.apiKeyManager.markKeyFailed(keyInfo.index, errorMsg);
continue; // Try next key
}
}
const data = await response.json();
if (!data.choices || !data.choices[0] || !data.choices[0].message) {
throw new Error('Invalid response format from OpenRouter API');
}
const improvement = data.choices[0].message.content.trim();
// Mark API key as successful
this.apiKeyManager.markKeySuccess(keyInfo.index);
// Log successful usage
console.error(`[OPENROUTER] Success with model: ${model} using API key ${keyInfo.index + 1}`);
if (data.usage) {
console.error(`[OPENROUTER] Tokens used: ${data.usage.total_tokens} (prompt: ${data.usage.prompt_tokens}, completion: ${data.usage.completion_tokens})`);
}
return improvement;
} catch (error) {
console.error(`[OPENROUTER] Error with model ${model} and API key ${keyInfo.index + 1}: ${error.message}`);
// If it's a network error, mark key as failed and try next
if (error.message.includes('fetch') || error.message.includes('network')) {
this.apiKeyManager.markKeyFailed(keyInfo.index, 'Network error');
this.markModelFailed(model, 'Network error');
continue;
}
// For other errors, mark both key and model as problematic
this.apiKeyManager.markKeyFailed(keyInfo.index, error.message);
this.markModelFailed(model, error.message);
}
}
// If all model/key combinations failed, use local fallback
console.error('[OPENROUTER] All model/key combinations failed, using local fallback');
return this.generateFallbackImprovement(topic, strategy, iteration, lastAgentResponse);
}
/**
* Generate fallback improvement when API fails
* @param {string} topic - The topic to improve
* @param {string} strategy - The improvement strategy
* @param {number} iteration - Current iteration number
* @returns {string} - Fallback improvement
*/
generateFallbackImprovement(topic, strategy, iteration) {
const fallbackImprovements = {
optimization: `Optimize ${topic} by implementing caching mechanisms and reducing computational complexity (iteration ${iteration})`,
refactoring: `Refactor ${topic} by breaking down complex components into smaller, more maintainable modules (iteration ${iteration})`,
'performance enhancement': `Enhance ${topic} performance by implementing lazy loading and efficient data structures (iteration ${iteration})`,
'code quality improvement': `Improve ${topic} code quality by adding comprehensive error handling and input validation (iteration ${iteration})`,
'functionality expansion': `Expand ${topic} functionality by adding new features based on user feedback and requirements (iteration ${iteration})`,
'error handling enhancement': `Enhance ${topic} error handling by implementing graceful degradation and detailed logging (iteration ${iteration})`,
'documentation improvement': `Improve ${topic} documentation by adding comprehensive examples and API references (iteration ${iteration})`,
'testing coverage increase': `Increase ${topic} testing coverage by implementing unit tests and integration tests (iteration ${iteration})`
};
return fallbackImprovements[strategy] || `Apply general improvements to ${topic} using ${strategy} approach (iteration ${iteration})`;
}
/**
* Test API connection
* @returns {Promise<boolean>} - Connection status
*/
async testConnection() {
try {
const response = await fetch(this.apiUrl, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
'HTTP-Referer': 'https://github.com/your-repo/mcp-infinite-loop-server',
'X-Title': 'MCP Infinite Loop Server'
},
body: JSON.stringify({
model: this.model,
messages: [
{
role: 'user',
content: 'Test connection'
}
],
max_tokens: 10
})
});
return response.ok;
} catch (error) {
console.error(`[OPENROUTER TEST] Connection failed: ${error.message}`);
return false;
}
}
/**
* Get model information
* @returns {Object} - Model configuration
*/
getModelInfo() {
const availableModels = this.fallbackModels.filter(model => !this.failedModels.has(model));
const keyStatus = this.apiKeyManager.getKeyStatus();
return {
primaryModel: this.primaryModel,
currentModel: this.getNextModel(),
availableModels: availableModels,
failedModels: Array.from(this.failedModels),
totalModels: this.fallbackModels.length,
apiUrl: this.apiUrl,
apiKeys: {
total: keyStatus.totalKeys,
available: keyStatus.availableKeys,
rateLimited: keyStatus.rateLimitedKeys,
failed: keyStatus.failedKeys
}
};
}
/**
* Get status of all models and API keys
* @returns {Object} - Detailed model and key status
*/
getModelStatus() {
const keyStatus = this.apiKeyManager.getKeyStatus();
const keyStats = this.apiKeyManager.getStatistics();
return {
models: {
allModels: this.fallbackModels,
availableModels: this.fallbackModels.filter(model => !this.failedModels.has(model)),
failedModels: Array.from(this.failedModels),
currentIndex: this.currentModelIndex,
primaryModel: this.primaryModel
},
apiKeys: keyStatus,
statistics: keyStats
};
}
/**
* Reset failed API keys
* @returns {number} - Number of keys reset
*/
resetFailedKeys() {
return this.apiKeyManager.resetAllKeys();
}
}