UNPKG

@just-every/ensemble

Version:

LLM provider abstraction layer with unified streaming interface

145 lines 6.73 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.runwayProvider = exports.RunwayProvider = void 0; const base_provider_js_1 = require("./base_provider.cjs"); const cost_tracker_js_1 = require("../utils/cost_tracker.cjs"); const llm_logger_js_1 = require("../utils/llm_logger.cjs"); const fetch_with_timeout_js_1 = require("../utils/fetch_with_timeout.cjs"); const RUNWAY_BASE = process.env.RUNWAY_API_BASE || 'https://api.dev.runwayml.com'; const RUNWAY_VERSION = process.env.RUNWAY_API_VERSION || '2024-11-06'; function mapRatio(size) { const s = String(size || 'square'); if (s === 'landscape' || s === '1792x1024' || s === '1536x1024' || s === '1920x1080' || s === '1280x720') return '1920:1080'; if (s === 'portrait' || s === '1024x1792' || s === '1080x1920' || s === '720x1280') return '1080:1920'; return '1024:1024'; } 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; }; class RunwayProvider extends base_provider_js_1.BaseModelProvider { constructor() { super('runway'); } async *createResponseStream() { throw new Error('Runway provider does not support text streaming'); } async createImage(prompt, model, agent, opts = {}) { const apiKey = process.env.RUNWAY_API_KEY; const requestId = (0, llm_logger_js_1.log_llm_request)(agent.agent_id || 'default', 'runway', model, { prompt, opts }, new Date()); let success = false; try { if (!apiKey) throw new Error('RUNWAY_API_KEY is not set'); const body = { promptText: prompt, ratio: mapRatio(opts.size), model: (model && model.toLowerCase().includes('turbo')) ? 'gen4_image_turbo' : 'gen4_image', }; if (opts?.source_images) { const srcs = Array.isArray(opts.source_images) ? opts.source_images : [opts.source_images]; body.referenceImages = srcs.slice(0, 3).map((v, i) => { const uri = typeof v === 'string' ? v : v?.data || v; return { uri, tag: `ref${i + 1}` }; }); } const createRes = await withRetries(() => (0, fetch_with_timeout_js_1.fetchWithTimeout)(`${RUNWAY_BASE}/v1/text_to_image`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}`, 'X-Runway-Version': RUNWAY_VERSION, }, body: JSON.stringify(body), }, 20000), 3); if (!createRes.ok) throw new Error(`Runway create failed: ${createRes.status} ${await createRes.text()}`); const created = await createRes.json(); const id = created?.id || created?.task?.id; if (!id) throw new Error('Runway: missing task id'); const pollUrl = `${RUNWAY_BASE}/v1/tasks/${encodeURIComponent(id)}`; const started = Date.now(); const timeoutMs = 180000; let urls = []; while (true) { const response = await withRetries(() => (0, fetch_with_timeout_js_1.fetchWithTimeout)(pollUrl, { headers: { Authorization: `Bearer ${apiKey}`, 'X-Runway-Version': RUNWAY_VERSION, }, }, 15000), 2); if (!response.ok) throw new Error(`Runway poll failed: ${response.status} ${await response.text()}`); const info = await response.json(); const status = (info?.status || info?.task?.status || '').toLowerCase(); const extracted = []; const tryPush = (value) => { if (!value) return; if (typeof value === 'string') extracted.push(value); else if (Array.isArray(value)) value.forEach(tryPush); else if (typeof value === 'object') { if (typeof value.url === 'string') extracted.push(value.url); if (typeof value.uri === 'string') extracted.push(value.uri); if (Array.isArray(value.images)) value.images.forEach(tryPush); if (Array.isArray(value.assets)) value.assets.forEach(tryPush); if (value.image) tryPush(value.image); } }; tryPush(info?.output); tryPush(info?.assets); tryPush(info?.task?.output); if (status === 'succeeded' || status === 'completed' || (extracted.length > 0 && (status === 'success' || status === ''))) { urls = extracted; break; } if (status === 'failed' || status === 'canceled' || status === 'error') { throw new Error(`Runway task failed (status=${status || 'unknown'})`); } if (Date.now() - started > timeoutMs) throw new Error('Runway generation timed out'); await new Promise(r => setTimeout(r, 1200)); } if (!urls.length) throw new Error('Runway: no image url in response'); cost_tracker_js_1.costTracker.addUsage({ model, image_count: urls.length, request_id: opts?.request_id, metadata: { source: 'runway' } }); success = true; return urls; } catch (err) { (0, llm_logger_js_1.log_llm_error)(requestId, err); throw err; } finally { (0, llm_logger_js_1.log_llm_response)(requestId, { ok: success }); } } } exports.RunwayProvider = RunwayProvider; exports.runwayProvider = new RunwayProvider(); //# sourceMappingURL=runway.js.map