UNPKG

@juspay/neurolink

Version:

Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio

172 lines (171 loc) 6.95 kB
/** * Avatar / Lip-sync Generation Processing Utility * * Central registry + dispatch for avatar handlers across providers * (D-ID, HeyGen, Replicate-hosted MuseTalk / SadTalker / Wav2Lip). * * Mirrors the static-handler-registry pattern established by * `TTSProcessor` / `STTProcessor` / `VideoProcessor` / `MusicProcessor`. * * @module utils/avatarProcessor */ import { ErrorCategory, ErrorSeverity } from "../constants/enums.js"; import { SpanSerializer, SpanStatus, SpanType, getMetricsAggregator, } from "../observability/index.js"; import { NeuroLinkError } from "./errorHandling.js"; import { logger } from "./logger.js"; /** * Avatar-specific error codes. */ export const AVATAR_ERROR_CODES = { PROVIDER_NOT_SUPPORTED: "AVATAR_PROVIDER_NOT_SUPPORTED", PROVIDER_NOT_CONFIGURED: "AVATAR_PROVIDER_NOT_CONFIGURED", GENERATION_FAILED: "AVATAR_GENERATION_FAILED", POLL_TIMEOUT: "AVATAR_POLL_TIMEOUT", INVALID_INPUT: "AVATAR_INVALID_INPUT", AUDIO_REQUIRED: "AVATAR_AUDIO_REQUIRED", IMAGE_REQUIRED: "AVATAR_IMAGE_REQUIRED", AUDIO_TOO_LONG: "AVATAR_AUDIO_TOO_LONG", }; /** * Typed error class for avatar-generation failures. */ export class AvatarError extends NeuroLinkError { constructor(options) { super({ code: options.code, message: options.message, category: options.category ?? ErrorCategory.EXECUTION, severity: options.severity ?? ErrorSeverity.HIGH, retriable: options.retriable ?? false, context: options.context, originalError: options.originalError, }); this.name = "AvatarError"; } } /** * Static processor managing the avatar handler registry. */ export class AvatarProcessor { static handlers = new Map(); /** * Register an avatar handler for a specific provider. */ static registerHandler(providerName, handler) { if (!providerName) { throw new Error("Provider name is required"); } if (!handler) { throw new Error("Handler is required"); } const key = providerName.toLowerCase(); if (this.handlers.has(key)) { logger.warn(`[AvatarProcessor] Overwriting existing handler for provider: ${key}`); } this.handlers.set(key, handler); logger.debug(`[AvatarProcessor] Registered avatar handler: ${key}`); } /** * Check if a provider has a registered avatar handler. */ static supports(providerName) { if (!providerName) { return false; } return this.handlers.has(providerName.toLowerCase()); } /** * List the names of all registered providers. */ static listProviders() { return Array.from(this.handlers.keys()); } static getHandler(providerName) { return this.handlers.get(providerName.toLowerCase()); } static buildSpanAttributes(provider, options) { return { "avatar.operation": "generate", "avatar.provider": provider, "avatar.quality": options.quality, "avatar.format": options.format, "avatar.has_text": options.text !== undefined, "avatar.has_audio": options.audio !== undefined, }; } /** * Generate an avatar video via the registered handler. * * @throws AvatarError on registry miss, handler-not-configured, or * generation failure. */ static async generate(provider, options) { const span = SpanSerializer.createSpan(SpanType.MEDIA_GENERATION, "avatar.generate", this.buildSpanAttributes(provider, options)); try { if (!options.image) { throw new AvatarError({ code: AVATAR_ERROR_CODES.IMAGE_REQUIRED, message: "Avatar generation requires an `image` (Buffer, path, or URL)", category: ErrorCategory.VALIDATION, severity: ErrorSeverity.MEDIUM, retriable: false, context: { provider }, }); } if (!options.audio && !options.text) { throw new AvatarError({ code: AVATAR_ERROR_CODES.INVALID_INPUT, message: "Avatar generation requires either `audio` (a Buffer/URL) or `text` (to be TTS'd by the provider).", category: ErrorCategory.VALIDATION, severity: ErrorSeverity.HIGH, retriable: false, context: { provider }, }); } const handler = this.getHandler(provider); if (!handler) { throw new AvatarError({ code: AVATAR_ERROR_CODES.PROVIDER_NOT_SUPPORTED, message: `Avatar provider "${provider}" is not registered. Available: ${this.listProviders().join(", ")}`, category: ErrorCategory.CONFIGURATION, severity: ErrorSeverity.HIGH, retriable: false, context: { provider, available: this.listProviders() }, }); } if (!handler.isConfigured()) { throw new AvatarError({ code: AVATAR_ERROR_CODES.PROVIDER_NOT_CONFIGURED, message: `Avatar provider "${provider}" is not configured. Set the required credentials.`, category: ErrorCategory.CONFIGURATION, severity: ErrorSeverity.HIGH, retriable: false, context: { provider }, }); } logger.debug(`[AvatarProcessor] Starting avatar generation with provider: ${provider}`); const result = await handler.generate(options); const ended = SpanSerializer.endSpan(span, SpanStatus.OK); getMetricsAggregator().recordSpan(ended); logger.info(`[AvatarProcessor] Generated ${result.size} bytes (${provider})`); return result; } catch (err) { const ended = SpanSerializer.endSpan(span, SpanStatus.ERROR, err instanceof Error ? err.message : String(err)); getMetricsAggregator().recordSpan(ended); if (err instanceof AvatarError) { throw err; } const message = err instanceof Error ? err.message : String(err); throw new AvatarError({ code: AVATAR_ERROR_CODES.GENERATION_FAILED, message: `Avatar generation failed for provider "${provider}": ${message}`, category: ErrorCategory.EXECUTION, severity: ErrorSeverity.HIGH, retriable: true, context: { provider }, originalError: err instanceof Error ? err : undefined, }); } } }