import express from 'express'; import fetch from 'node-fetch'; import mime from 'mime-types'; import { readSecret, SECRET_KEYS } from './secrets.js'; export const router = express.Router(); const API_OPENROUTER = 'https://openrouter.ai/api/v1'; router.post('/models/providers', async (req, res) => { try { const { model } = req.body; const response = await fetch(`${API_OPENROUTER}/models/${model}/endpoints`, { method: 'GET', headers: { 'Accept': 'application/json', }, }); if (!response.ok) { return res.json([]); } /** @type {any} */ const data = await response.json(); const endpoints = data?.data?.endpoints || []; const providerNames = endpoints.map(e => e.provider_name); return res.json(providerNames); } catch (error) { console.error(error); return res.sendStatus(500); } }); /** * Fetches and filters models from OpenRouter API based on modality criteria. * @param {string} endpoint - The API endpoint to fetch from * @param {string} inputModality - Required input modality * @param {string} outputModality - Required output modality * @param {boolean} [idsOnly=false] - Whether to return only model IDs * @returns {Promise} Filtered models or model IDs */ async function fetchModelsByModality(endpoint, inputModality, outputModality, idsOnly = false) { const response = await fetch(`${API_OPENROUTER}${endpoint}`, { method: 'GET', headers: { 'Accept': 'application/json' }, }); if (!response.ok) { console.warn('OpenRouter API request failed', response.statusText); return []; } /** @type {any} */ const data = await response.json(); if (!Array.isArray(data?.data)) { console.warn('OpenRouter API response was not an array'); return []; } const filtered = data.data .filter(m => Array.isArray(m?.architecture?.input_modalities)) .filter(m => m.architecture.input_modalities.includes(inputModality)) .filter(m => Array.isArray(m?.architecture?.output_modalities)) .filter(m => m.architecture.output_modalities.includes(outputModality)) .sort((a, b) => a?.id && b?.id ? a.id.localeCompare(b.id) : 0); return idsOnly ? filtered.map(m => m.id) : filtered; } router.post('/models/multimodal', async (_req, res) => { try { const models = await fetchModelsByModality('/models', 'image', 'text', true); return res.json(models); } catch (error) { console.error(error); return res.sendStatus(500); } }); router.post('/models/embedding', async (_req, res) => { try { const models = await fetchModelsByModality('/embeddings/models', 'text', 'embeddings'); return res.json(models); } catch (error) { console.error(error); return res.sendStatus(500); } }); router.post('/models/image', async (_req, res) => { try { const models = await fetchModelsByModality('/models', 'text', 'image'); return res.json(models.map(m => ({ value: m.id, text: m.name || m.id }))); } catch (error) { console.error(error); return res.sendStatus(500); } }); router.post('/image/generate', async (req, res) => { try { const key = readSecret(req.user.directories, SECRET_KEYS.OPENROUTER); if (!key) { console.warn('OpenRouter API key not found'); return res.status(400).json({ error: 'OpenRouter API key not found' }); } console.debug('OpenRouter image generation request', req.body); const { model, prompt } = req.body; if (!model || !prompt) { return res.status(400).json({ error: 'Model and prompt are required' }); } const response = await fetch(`${API_OPENROUTER}/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${key}`, }, body: JSON.stringify({ model: model, messages: [ { role: 'user', content: prompt, }, ], modalities: ['image', 'text'], image_config: { aspect_ratio: req.body.aspect_ratio || '1:1', }, }), }); if (!response.ok) { console.warn('OpenRouter image generation failed', await response.text()); return res.sendStatus(500); } /** @type {any} */ const data = await response.json(); const imageUrl = data?.choices?.[0]?.message?.images?.[0]?.image_url?.url; if (!imageUrl) { console.warn('No image URL found in OpenRouter response', data); return res.sendStatus(500); } const [mimeType, base64Data] = /^data:(.*);base64,(.*)$/.exec(imageUrl)?.slice(1) || []; if (!mimeType || !base64Data) { console.warn('Invalid image data format', imageUrl); return res.sendStatus(500); } const result = { format: mime.extension(mimeType) || 'png', image: base64Data, }; return res.json(result); } catch (error) { console.error(error); return res.sendStatus(500); } });