import fs from 'node:fs'; import path from 'node:path'; export const name = 'dsh-plugin-model-enhancer'; export const inject = ['webServer']; let cachedData = null; let lastFetchedAt = 0; const CACHE_TTL_MS = 3600 * 1000; // 1 hour async function getModelsDevData() { const now = Date.now(); if (cachedData && now - lastFetchedAt < CACHE_TTL_MS) { return cachedData; } try { const res = await fetch('https://models.dev/api.json', { headers: { 'User-Agent': 'DSH-Model-Enhancer/1.0.0' } }); if (!res.ok) { throw new Error(`Failed to fetch models.dev: HTTP ${res.status}`); } const json = await res.json(); cachedData = json; lastFetchedAt = now; return json; } catch (err) { if (cachedData) { // Fallback to stale cache if network fails return cachedData; } throw err; } } export function apply(ctx) { // 1. Models.dev Cache Proxy ctx.effect(() => { return ctx.webServer.register({ kind: 'exact', path: '/api/models-dev/models', handler: async (req, res) => { res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); if (req.method === 'OPTIONS') { res.writeHead(204); res.end(); return; } if (req.method !== 'GET') { res.writeHead(405, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Method Not Allowed' })); return; } try { const data = await getModelsDevData(); res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'public, max-age=3600' }); res.end(JSON.stringify(data)); } catch (err) { ctx.logger?.error?.('[model-enhancer] Error fetching models.dev:', err); res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: err.message || 'Internal Server Error' })); } } }); }, 'dsh-plugin-model-enhancer: webServer proxy route'); // 2. Binary / Large Document File Upload to Workspace Endpoint ctx.effect(() => { return ctx.webServer.register({ kind: 'exact', path: '/api/model-enhancer/upload', handler: async (req, res) => { res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Filename'); if (req.method === 'OPTIONS') { res.writeHead(204); res.end(); return; } if (req.method !== 'POST') { res.writeHead(405, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Method Not Allowed' })); return; } try { const urlObj = new URL(req.url, 'http://127.0.0.1'); const rawName = req.headers['x-filename'] || urlObj.searchParams.get('filename') || `upload_${Date.now()}.bin`; const decodedName = decodeURIComponent(rawName); const safeName = path.basename(decodedName); const uploadDir = path.join(process.cwd(), 'uploads'); fs.mkdirSync(uploadDir, { recursive: true }); const targetPath = path.join(uploadDir, safeName); const writeStream = fs.createWriteStream(targetPath); let totalBytes = 0; req.on('data', (chunk) => { totalBytes += chunk.length; }); req.pipe(writeStream); writeStream.on('finish', () => { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ ok: true, filename: safeName, path: path.join('uploads', safeName), size: totalBytes })); }); writeStream.on('error', (err) => { ctx.logger?.error?.('[model-enhancer] Upload stream error:', err); res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: err.message || 'File write error' })); }); } catch (err) { ctx.logger?.error?.('[model-enhancer] Upload handler error:', err); res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: err.message || 'Upload processing error' })); } } }); }, 'dsh-plugin-model-enhancer: workspace file upload endpoint'); }