Skip to main content
Glama
luma-polling.ts6.82 kB
import LumaAI from 'lumaai'; import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; // Removed incorrect Exchange import const POLLING_INTERVAL_MS = 5000; // Poll every 5 seconds const MAX_POLLING_ATTEMPTS = 60; // Max attempts (e.g., 60 * 5s = 5 minutes timeout) // Define expected asset types type ExpectedAssetType = 'video' | 'image' | 'upscaled_video'; // Add 'upscaled_video' for future interface LumaPollOptions { lumaClient: LumaAI; generationId: string; mcpExchange: any; // Let TS infer or use Exchange from mcp.js if needed progressToken?: string; // Optional progress token expectedAssetType?: ExpectedAssetType; // Add expected asset type, default to 'video' } /** * Polls the Luma AI API for the status of a generation task. * Sends progress updates via MCP exchange. * Resolves with the asset URL on success, rejects on failure or timeout. */ export async function pollLumaTask({ lumaClient, generationId, mcpExchange, progressToken, expectedAssetType = 'video', // Default to video if not specified }: LumaPollOptions): Promise<string> { let attempts = 0; const sendProgress = (status: string, data?: any) => { // If progressToken exists, assume mcpExchange and sendProgress method exist if (progressToken) { mcpExchange.sendProgress({ // Removed optional chaining '?' token: progressToken, // Ensure data is nested under a key like 'result' or similar if needed by client value: { status, ...(data && { result: data }) }, // Nest data under 'result' }); console.error( `[${new Date().toISOString()}] Progress sent for Luma task ${generationId}: ${status}` ); } }; return new Promise((resolve, reject) => { const intervalId = setInterval(async () => { attempts++; if (attempts > MAX_POLLING_ATTEMPTS) { clearInterval(intervalId); console.error( `[${new Date().toISOString()}] Luma polling timed out for task ${generationId} after ${MAX_POLLING_ATTEMPTS} attempts.` ); sendProgress('TIMEOUT'); return reject( new McpError( ErrorCode.InternalError, // Use InternalError for timeout `Polling timed out for Luma AI task ${generationId}` ) ); } try { console.error( `[${new Date().toISOString()}] Polling Luma task ${generationId} (Attempt ${attempts}/${MAX_POLLING_ATTEMPTS})...` ); // Use the Luma SDK's method to get generation status const generationStatus = await lumaClient.generations.get(generationId); console.error( `[${new Date().toISOString()}] Luma task ${generationId} state: ${generationStatus.state}` ); switch (generationStatus.state) { case 'completed': { // Use block scope for clarity clearInterval(intervalId); let assetUrl: string | undefined | null = null; let assetKey: string = 'assetUrl'; // Default key for progress message // Determine the correct asset URL based on expected type if (expectedAssetType === 'video') { assetUrl = generationStatus.assets?.video; assetKey = 'videoUrl'; } else if (expectedAssetType === 'image') { assetUrl = generationStatus.assets?.image; assetKey = 'imageUrl'; } else if (expectedAssetType === 'upscaled_video') { // Assuming upscale might return in assets.video or a specific key // Adjust this based on actual Luma API response for upscale assetUrl = generationStatus.assets?.video; // Placeholder - VERIFY THIS assetKey = 'upscaledVideoUrl'; } if (assetUrl) { console.log( `[${new Date().toISOString()}] Luma task ${generationId} (${expectedAssetType}) succeeded. Asset URL: ${assetUrl}` ); // Send the specific asset key and URL sendProgress('SUCCEEDED', { [assetKey]: assetUrl }); resolve(assetUrl); } else { const reason = `Task completed but no ${expectedAssetType} URL found.`; console.error( `[${new Date().toISOString()}] Luma task ${generationId} ${reason}` ); sendProgress('FAILED', { reason }); reject( new McpError( ErrorCode.InternalError, `Luma AI task ${generationId} ${reason}` ) ); } break; } // Added missing closing brace for case block case 'failed': { // Added block scope for consistency clearInterval(intervalId); const failureReason = generationStatus.failure_reason || 'Unknown failure reason'; console.error( `[${new Date().toISOString()}] Luma task ${generationId} failed: ${failureReason}` ); sendProgress('FAILED', { reason: failureReason }); reject( new McpError( ErrorCode.InternalError, `Luma AI task ${generationId} failed: ${failureReason}` ) ); break; } // Added missing closing brace for case block // Luma states based on type hints seem to be 'queued', 'dreaming', 'completed', 'failed' case 'queued': case 'dreaming': // Continue polling sendProgress(generationStatus.state.toUpperCase()); // Send status like QUEUED, DREAMING break; default: // Unexpected status console.warn( `[${new Date().toISOString()}] Luma task ${generationId} has unexpected state: ${generationStatus.state}` ); sendProgress('UNKNOWN_STATUS', { status: generationStatus.state }); // Continue polling } } catch (error: any) { // Handle errors during the polling request itself console.error( `[${new Date().toISOString()}] Error polling Luma task ${generationId}:`, error ); if (error instanceof LumaAI.APIError && error.status === 404) { clearInterval(intervalId); sendProgress('FAILED', { reason: `Generation ID ${generationId} not found.` }); reject( new McpError( ErrorCode.InvalidRequest, `Luma AI generation ID ${generationId} not found.` ) ); } else { sendProgress('POLLING_ERROR', { message: error.message }); } // Continue polling unless it's a fatal error like 404 } }, POLLING_INTERVAL_MS); }); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/wheattoast11/mcp-video-gen'

If you have feedback or need assistance with the MCP directory API, please join our Discord server