@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
145 lines • 5.79 kB
JavaScript
/**
* OpenRouter API Client
* Provides unified access to 200+ models from 50+ providers
*/
export class OpenRouterClient {
baseUrl = 'https://openrouter.ai/api/v1';
userAgent = 'hive-tools/1.0.0';
async fetchAllModels() {
try {
const response = await fetch(`${this.baseUrl}/models`, {
method: 'GET',
headers: {
'User-Agent': this.userAgent,
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`OpenRouter API error: ${response.status} ${response.statusText}`);
}
const data = await response.json();
return this.transformModels(data.data);
}
catch (error) {
throw new Error(`Failed to fetch models from OpenRouter: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
transformModels(rawModels) {
return rawModels.map(model => {
// Extract provider from model ID (format: provider/model-name)
const provider = this.extractProvider(model.id);
// Convert pricing from per-token to per-1k-tokens
const inputCost = parseFloat(model.pricing.prompt) * 1000;
const outputCost = parseFloat(model.pricing.completion) * 1000;
// Determine capabilities based on modalities
const capabilities = this.determineCapabilities(model.architecture);
return {
id: model.id,
name: model.name,
provider,
description: model.description || '',
capabilities,
pricing: {
input: inputCost,
output: outputCost,
image: model.pricing.image ? parseFloat(model.pricing.image) : undefined,
request: model.pricing.request ? parseFloat(model.pricing.request) : undefined,
},
contextWindow: model.context_length,
created: model.created,
modalities: {
input: model.architecture.input_modalities || ['text'],
output: model.architecture.output_modalities || ['text'],
},
};
});
}
extractProvider(modelId) {
// Model IDs follow format: "provider/model-name" or just "model-name"
const parts = modelId.split('/');
if (parts.length > 1) {
return parts[0];
}
// Fallback: try to detect provider from common patterns
if (modelId.includes('gpt'))
return 'openai';
if (modelId.includes('claude'))
return 'anthropic';
if (modelId.includes('gemini'))
return 'google';
if (modelId.includes('grok'))
return 'xai';
if (modelId.includes('llama'))
return 'meta';
return 'unknown';
}
determineCapabilities(architecture) {
const capabilities = [];
// All models support chat by default
capabilities.push('chat');
// Check for image input
if (architecture.input_modalities?.includes('image')) {
capabilities.push('vision');
}
// Check for other capabilities based on modalities
if (architecture.input_modalities?.includes('audio')) {
capabilities.push('audio');
}
return capabilities;
}
async getProviderSummary() {
const models = await this.fetchAllModels();
const providerMap = new Map();
// Group models by provider
models.forEach(model => {
if (!providerMap.has(model.provider)) {
providerMap.set(model.provider, []);
}
providerMap.get(model.provider).push(model);
});
// Create provider summaries
return Array.from(providerMap.entries()).map(([provider, providerModels]) => {
const averageCost = this.calculateAverageCost(providerModels);
const allCapabilities = new Set();
providerModels.forEach(model => {
model.capabilities.forEach(cap => allCapabilities.add(cap));
});
return {
name: provider,
modelCount: providerModels.length,
averageCost,
capabilities: Array.from(allCapabilities),
models: providerModels,
};
}).sort((a, b) => b.modelCount - a.modelCount); // Sort by model count descending
}
calculateAverageCost(models) {
const costs = models.map(model => (model.pricing.input + model.pricing.output) / 2);
return costs.reduce((sum, cost) => sum + cost, 0) / costs.length;
}
searchModels(models, filters) {
return models.filter(model => {
if (filters.capability && !model.capabilities.includes(filters.capability)) {
return false;
}
if (filters.maxCost) {
const avgCost = (model.pricing.input + model.pricing.output) / 2;
if (avgCost > filters.maxCost) {
return false;
}
}
if (filters.provider && model.provider !== filters.provider) {
return false;
}
if (filters.contextWindow && model.contextWindow < filters.contextWindow) {
return false;
}
return true;
});
}
compareModels(models, modelIds) {
return models.filter(model => modelIds.includes(model.id));
}
}
export const openRouterClient = new OpenRouterClient();
//# sourceMappingURL=openrouter-client.js.map