capsule-ai-cli
Version:
The AI Model Orchestrator - Intelligent multi-model workflows with device-locked licensing
209 lines • 7.97 kB
JavaScript
import axios from 'axios';
import { configManager } from '../core/config.js';
class OpenRouterModelsService {
cache = null;
toolSupportedCache = null;
cacheTimestamp = 0;
CACHE_DURATION = 15 * 60 * 1000;
API_URL = 'https://openrouter.ai/api/v1/models';
async fetchModels(forceRefresh = false, toolsOnly = false) {
const cacheKey = toolsOnly ? 'tools' : 'all';
if (!forceRefresh && this.cache && Date.now() - this.cacheTimestamp < this.CACHE_DURATION) {
if (toolsOnly) {
return this.cache.filter(model => model.supported_parameters?.includes('tools'));
}
return this.cache;
}
try {
const apiKey = configManager.getApiKey('openrouter');
if (!apiKey) {
console.warn('OpenRouter API key not found, using cached/default models');
return this.getDefaultModels();
}
const url = toolsOnly
? `${this.API_URL}?supported_parameters=tools`
: this.API_URL;
const response = await axios.get(url, {
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
timeout: 10000
});
if (response.data && response.data.data) {
if (toolsOnly) {
this.toolSupportedCache = response.data.data;
}
else {
this.cache = response.data.data;
this.cacheTimestamp = Date.now();
}
this.saveToLocalCache(response.data.data, cacheKey);
return response.data.data;
}
throw new Error('Invalid response format from OpenRouter models API');
}
catch (error) {
console.error('Failed to fetch models from OpenRouter:', error);
const localCache = this.loadFromLocalCache(cacheKey);
if (localCache) {
return localCache;
}
return this.getDefaultModels();
}
}
saveToLocalCache(models, cacheKey = 'all') {
try {
const config = configManager.getConfig();
if (!config.cache)
config.cache = {};
const cacheData = {
models,
timestamp: Date.now()
};
if (cacheKey === 'tools') {
config.cache.openrouterToolModels = cacheData;
}
else {
config.cache.openrouterModels = cacheData;
}
configManager.setConfig('cache', config.cache);
}
catch (error) {
console.error('Failed to save models to cache:', error);
}
}
loadFromLocalCache(cacheKey = 'all') {
try {
const config = configManager.getConfig();
const cached = cacheKey === 'tools'
? config.cache?.openrouterToolModels
: config.cache?.openrouterModels;
if (cached && cached.models && Date.now() - cached.timestamp < 24 * 60 * 60 * 1000) {
return cached.models;
}
}
catch (error) {
console.error('Failed to load models from cache:', error);
}
return null;
}
getDefaultModels() {
return [
{
id: 'openai/gpt-4o',
canonical_slug: 'openai/gpt-4o',
name: 'GPT-4o',
created: Date.now(),
description: 'OpenAI GPT-4o model',
context_length: 128000,
architecture: {
input_modalities: ['text'],
output_modalities: ['text'],
tokenizer: 'cl100k_base',
instruct_type: null
},
pricing: {
prompt: '0.0000025',
completion: '0.00001',
request: '0',
image: '0',
web_search: '0',
internal_reasoning: '0',
input_cache_read: '0',
input_cache_write: '0'
},
top_provider: {
context_length: 128000,
max_completion_tokens: 4096,
is_moderated: false
},
per_request_limits: null,
supported_parameters: ['tools', 'tool_choice', 'max_tokens', 'temperature', 'top_p', 'stop', 'frequency_penalty', 'presence_penalty', 'seed']
},
{
id: 'anthropic/claude-opus-4',
canonical_slug: 'anthropic/claude-opus-4',
name: 'Claude Opus 4',
created: Date.now(),
description: 'Anthropic Claude Opus 4 model',
context_length: 200000,
architecture: {
input_modalities: ['text'],
output_modalities: ['text'],
tokenizer: 'claude',
instruct_type: null
},
pricing: {
prompt: '0.000015',
completion: '0.000075',
request: '0',
image: '0',
web_search: '0',
internal_reasoning: '0',
input_cache_read: '0',
input_cache_write: '0'
},
top_provider: {
context_length: 200000,
max_completion_tokens: 4096,
is_moderated: false
},
per_request_limits: null,
supported_parameters: ['max_tokens', 'temperature', 'top_p', 'stop']
}
];
}
getModelsByProvider(provider) {
const models = this.toolSupportedCache || this.cache || this.getDefaultModels();
return models.filter(model => model.id.startsWith(`${provider}/`));
}
getModel(modelId) {
const models = this.toolSupportedCache || this.cache || this.getDefaultModels();
return models.find(model => model.id === modelId);
}
getAvailableProviders() {
const models = this.toolSupportedCache || this.cache || this.getDefaultModels();
const providers = new Set();
models.forEach(model => {
const provider = model.id.split('/')[0];
if (provider) {
providers.add(provider);
}
});
return Array.from(providers).sort();
}
getModelPricing(modelId) {
const model = this.getModel(modelId);
if (!model)
return null;
return {
prompt: parseFloat(model.pricing.prompt) || 0,
completion: parseFloat(model.pricing.completion) || 0
};
}
getModelContextLength(modelId) {
const model = this.getModel(modelId);
return model?.context_length || 128000;
}
modelSupports(modelId, parameter) {
const model = this.getModel(modelId);
return model?.supported_parameters?.includes(parameter) || false;
}
isModelAvailable(modelId, _mode) {
const model = this.getModel(modelId);
if (!model)
return false;
const params = model.supported_parameters || [];
return params.includes('tools') && params.includes('tool_choice');
}
getToolSupportedModels() {
const models = this.cache || this.getDefaultModels();
return models.filter(model => {
const params = model.supported_parameters || [];
return params.includes('tools') && params.includes('tool_choice');
});
}
}
export const openRouterModelsService = new OpenRouterModelsService();
//# sourceMappingURL=openrouter-models.js.map