@hivetechs/hive-ai
Version:
Real-time streaming AI consensus platform with HTTP+SSE MCP integration for Claude Code, VS Code, Cursor, and Windsurf - powered by OpenRouter's unified API
114 lines (113 loc) • 4.43 kB
JavaScript
/**
* OpenRouter API Client - SOURCE_OF_TRUTH Implementation
*
* Simple OpenRouter API wrapper for consensus tool per exact design specifications.
* Only used for consensus pipeline - no other functionality.
* Now includes health monitoring integration for bulletproof error handling.
*/
import { globalHealthMonitor } from './health-monitor.js';
/**
* Call OpenRouter API with model, messages, and API key
* Per SOURCE_OF_TRUTH: Function callOpenRouter(model, messages, apiKey)
*/
export async function callOpenRouter(model, messages, apiKey) {
// Validate inputs
if (!model || !messages || !apiKey) {
throw new Error('callOpenRouter: model, messages, and apiKey are required');
}
if (!apiKey.startsWith('sk-or-')) {
throw new Error('Invalid OpenRouter API key format. Key must start with sk-or-');
}
// Prepare request
const requestBody = {
model: model,
messages: messages,
temperature: 0.7,
max_tokens: 4000
};
const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`,
'HTTP-Referer': 'https://hivetechs.io',
'X-Title': 'Hive.AI Consensus Pipeline'
};
try {
// Add model to health monitoring if not already tracked
const [provider, modelName] = model.includes('/') ? model.split('/') : ['openrouter', model];
globalHealthMonitor.addTestModel(model);
// Make API call to OpenRouter
const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
method: 'POST',
headers: headers,
body: JSON.stringify(requestBody)
});
// Handle non-OK responses
if (!response.ok) {
const errorText = await response.text();
let errorMessage = `OpenRouter API error (${response.status})`;
try {
const errorData = JSON.parse(errorText);
if (errorData.error?.message) {
errorMessage = errorData.error.message;
}
}
catch {
// Use status-based error message if JSON parsing fails
if (response.status === 401) {
errorMessage = 'Invalid OpenRouter API key. Check your configuration.';
}
else if (response.status === 402) {
errorMessage = 'Insufficient credits in OpenRouter account.';
}
else if (response.status === 404) {
errorMessage = `Model "${model}" not found on OpenRouter.`;
}
else if (response.status === 429) {
errorMessage = 'Rate limit exceeded. Please try again later.';
}
}
throw new Error(errorMessage);
}
// Parse successful response
const data = await response.json();
if (!data.choices || !data.choices[0] || !data.choices[0].message) {
throw new Error('Invalid response format from OpenRouter API');
}
return {
content: data.choices[0].message.content,
model: data.model || model,
usage: data.usage
};
}
catch (error) {
// Re-throw with better error messages
if (error.name === 'TypeError' && error.message.includes('fetch')) {
throw new Error('Network error: Unable to connect to OpenRouter API');
}
if (error.message.includes('JSON')) {
throw new Error('OpenRouter API returned invalid response format');
}
// Pass through our custom errors
throw error;
}
}
/**
* Test OpenRouter API connectivity with a simple call
* Uses the first available model from OpenRouter data
*/
export async function testOpenRouterConnection(apiKey, testModel) {
try {
if (!testModel) {
throw new Error('No test model provided. OpenRouter model selection required.');
}
const testMessages = [
{ role: 'user', content: 'Say "test successful"' }
];
const response = await callOpenRouter(testModel, testMessages, apiKey);
return response.content.toLowerCase().includes('test successful');
}
catch (error) {
console.error('OpenRouter connection test failed:', error.message);
return false;
}
}