UNPKG

@just-every/ensemble

Version:

LLM provider abstraction layer with unified streaming interface

146 lines 6.63 kB
import { BaseModelProvider } from './base_provider.js'; import { costTracker } from '../utils/cost_tracker.js'; import { fetchWithTimeout } from '../utils/fetch_with_timeout.js'; import { log_llm_error, log_llm_request, log_llm_response } from '../utils/llm_logger.js'; const LUMA_BASE = 'https://api.lumalabs.ai/dream-machine/v1'; function mapAspect(size) { if (!size) return undefined; const s = String(size); if (s === 'square' || s === '1024x1024' || s === '512x512' || s === '256x256') return '1:1'; if (s === 'landscape' || s === '1792x1024' || s === '1536x1024') return '16:9'; if (s === 'portrait' || s === '1024x1792' || s === '1024x1536') return '9:16'; return undefined; } export class LumaProvider extends BaseModelProvider { constructor() { super('luma'); } async *createResponseStream() { throw new Error('Luma provider does not support text streaming'); } async createImage(prompt, model, agent, opts) { const apiKey = process.env.LUMA_API_KEY; if (!apiKey) throw new Error('Luma provider: LUMA_API_KEY is not set'); const requestId = log_llm_request(agent.agent_id || 'default', 'luma', model, { prompt, opts }, new Date()); let success = false; try { const aspect = mapAspect(opts?.size); const body = { prompt, model: model.replace('luma-', ''), format: 'png' }; if (aspect) body.aspect_ratio = aspect; const srcs = []; if (opts?.source_images) { const arr = Array.isArray(opts.source_images) ? opts.source_images : [opts.source_images]; for (const s of arr) { const v = typeof s === 'string' ? s : s?.data || ''; if (typeof v === 'string' && /^https?:\/\//i.test(v)) srcs.push(v); } } if (srcs.length) body.image_ref = [{ image_url: srcs[0] }]; const shouldRetry = (error) => { const message = String(error?.message || '').toLowerCase(); return error?.name === 'AbortError' || message.includes('timed out'); }; const withRetries = async (fn, attempts = 3) => { let lastError; for (let attempt = 1; attempt <= attempts; attempt++) { try { return await fn(); } catch (error) { lastError = error; if (!shouldRetry(error) || attempt === attempts) throw error; await new Promise(r => setTimeout(r, attempt * 500)); } } throw lastError; }; const createTimeout = 20000; let res = await withRetries(() => fetchWithTimeout(`${LUMA_BASE}/generations/image`, { method: 'POST', headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify(body), }, createTimeout), 3); if (!res.ok) { const text = await res.text(); if (srcs.length && (res.status === 400 || res.status === 422)) { delete body.image_ref; res = await withRetries(() => fetchWithTimeout(`${LUMA_BASE}/generations/image`, { method: 'POST', headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify(body), }, 20000), 3); } if (!res.ok) throw new Error(`Luma create failed: ${res.status} ${text}`); } const job = await res.json(); if (job?.state === 'completed' && (job?.assets?.image || job?.assets?.image_url)) { const url = job.assets?.image || job.assets?.image_url; if (url) { costTracker.addUsage({ model, image_count: 1, request_id: opts?.request_id, metadata: { source: 'luma', mode: 'sync' } }); success = true; return [url]; } } const id = job.id || job.generation_id || job.data?.id; if (!id) throw new Error('Luma: missing generation id in response'); const started = Date.now(); const timeoutMs = 120000; const intervalMs = 1500; while (true) { let r; try { r = await withRetries(() => fetchWithTimeout(`${LUMA_BASE}/generations/${id}`, { headers: { Authorization: `Bearer ${apiKey}` }, }, 15000), 2); } catch (error) { throw new Error(`Luma poll failed for ${id}: ${error?.message || error}`); } if (!r.ok) throw new Error(`Luma poll failed: ${r.status} ${await r.text()}`); const data = await r.json(); const state = data.state || data.status; if (state === 'completed' || state === 'succeeded' || state === 'success') { const url = data.assets?.image || data.output?.image_url || data.url; if (!url) throw new Error('Luma: completed without image url'); costTracker.addUsage({ model, image_count: 1, request_id: opts?.request_id, metadata: { source: 'luma' } }); success = true; return [url]; } if (state === 'failed' || state === 'error' || state === 'canceled') { throw new Error(`Luma generation failed: ${state}`); } if (Date.now() - started > timeoutMs) throw new Error('Luma generation timed out'); await new Promise(r => setTimeout(r, intervalMs)); } } catch (err) { log_llm_error(requestId, err); throw err; } finally { log_llm_response(requestId, { ok: success }); } } } export const lumaProvider = new LumaProvider(); //# sourceMappingURL=luma.js.map