llmkit 0.1.3

Production-grade LLM client - 100+ providers, 11,000+ models. Pure Rust.
Documentation
//! Runware video generation API provider.
//!
//! This module provides access to Runware's video generation capabilities.
//! Runware offers an aggregated video generation service with support for multiple models.
//!
//! # Supported Models
//!
//! - runway-gen-4.5 - RunwayML Gen-4.5
//! - kling-2.0 - Kling Video Generation
//! - pika-1.0 - Pika 1.0
//! - hailuo-mini - Hailuo Mini Video
//! - leonardo-ultra - Leonardo Diffusion Ultra

use std::pin::Pin;

use async_trait::async_trait;
use futures::Stream;
use serde::{Deserialize, Serialize};

use crate::error::{Error, Result};
use crate::provider::{Provider, ProviderConfig};
use crate::types::{CompletionRequest, CompletionResponse, StreamChunk};

#[allow(dead_code)]
const RUNWARE_API_URL: &str = "https://api.runware.ai/v1";

/// Video generation models supported by Runware.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VideoModel {
    /// RunwayML Gen-4.5
    RunwayGen45,
    /// Kling Video Generation
    Kling20,
    /// Pika 1.0
    Pika10,
    /// Hailuo Mini Video
    HailuoMini,
    /// Leonardo Diffusion Ultra
    LeonardoUltra,
}

impl VideoModel {
    /// Get the model identifier for the API.
    pub fn model_id(&self) -> &'static str {
        match self {
            Self::RunwayGen45 => "runway-gen-4.5",
            Self::Kling20 => "kling-2.0",
            Self::Pika10 => "pika-1.0",
            Self::HailuoMini => "hailuo-mini",
            Self::LeonardoUltra => "leonardo-ultra",
        }
    }
}

/// Runware video generation provider.
///
/// Provides access to multiple video generation models through a unified aggregator.
#[allow(dead_code)]
pub struct RunwareProvider {
    config: ProviderConfig,
}

impl RunwareProvider {
    /// Create a new Runware provider with the given configuration.
    pub fn new(config: ProviderConfig) -> Result<Self> {
        if config.api_key.is_none() {
            return Err(Error::config("Runware API key is required"));
        }
        Ok(Self { config })
    }

    /// Create a new Runware provider from environment variable.
    ///
    /// Reads the API key from `RUNWARE_API_KEY`.
    pub fn from_env() -> Result<Self> {
        let config = ProviderConfig::from_env("RUNWARE_API_KEY");
        Self::new(config)
    }

    /// Create a new Runware provider with an API key.
    pub fn with_api_key(api_key: impl Into<String>) -> Result<Self> {
        let config = ProviderConfig::new(api_key);
        Self::new(config)
    }

    #[allow(dead_code)]
    fn api_url(&self) -> &str {
        self.config.base_url.as_deref().unwrap_or(RUNWARE_API_URL)
    }

    /// Generate a video from a text prompt.
    ///
    /// # Arguments
    ///
    /// * `prompt` - The text description of the video to generate
    /// * `model` - The video generation model to use
    /// * `duration` - Video duration in seconds (optional)
    /// * `width` - Video width in pixels (optional)
    /// * `height` - Video height in pixels (optional)
    ///
    /// # Example
    ///
    /// ```ignore
    /// let provider = RunwareProvider::with_api_key("your-api-key")?;
    /// let result = provider.generate(
    ///     "A cat playing with a ball",
    ///     VideoModel::RunwayGen45,
    ///     Some(6),
    ///     Some(1280),
    ///     Some(720),
    /// ).await?;
    /// ```
    pub async fn generate(
        &self,
        #[allow(unused_variables)] prompt: &str,
        _model: VideoModel,
        _duration: Option<u32>,
        _width: Option<u32>,
        _height: Option<u32>,
    ) -> Result<VideoGenerationResult> {
        // This is a placeholder for future implementation
        Err(Error::invalid_request(
            "Video generation is coming soon. API integration in progress.",
        ))
    }
}

#[async_trait]
impl Provider for RunwareProvider {
    fn name(&self) -> &str {
        "runware"
    }

    fn supports_streaming(&self) -> bool {
        true
    }

    async fn complete(&self, _request: CompletionRequest) -> Result<CompletionResponse> {
        Err(Error::invalid_request(
            "Video generation is not available yet. Integration in progress.",
        ))
    }

    async fn complete_stream(
        &self,
        _request: CompletionRequest,
    ) -> Result<Pin<Box<dyn Stream<Item = Result<StreamChunk>> + Send>>> {
        Err(Error::invalid_request(
            "Video generation streaming is not available yet. Integration in progress.",
        ))
    }
}

/// Result of a video generation request.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VideoGenerationResult {
    /// Video generation task ID
    pub task_id: String,
    /// Video URL or location
    pub video_url: Option<String>,
    /// Status (pending, processing, completed, failed)
    pub status: String,
    /// Error message if failed
    pub error: Option<String>,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_video_models() {
        assert_eq!(VideoModel::RunwayGen45.model_id(), "runway-gen-4.5");
        assert_eq!(VideoModel::Kling20.model_id(), "kling-2.0");
        assert_eq!(VideoModel::Pika10.model_id(), "pika-1.0");
        assert_eq!(VideoModel::HailuoMini.model_id(), "hailuo-mini");
        assert_eq!(VideoModel::LeonardoUltra.model_id(), "leonardo-ultra");
    }

    #[test]
    fn test_provider_creation() {
        let provider = RunwareProvider::with_api_key("test-key").unwrap();
        assert_eq!(provider.name(), "runware");
        assert!(provider.supports_streaming());
    }

    #[test]
    fn test_provider_requires_api_key() {
        let config = ProviderConfig::default();
        let result = RunwareProvider::new(config);
        assert!(result.is_err());
    }
}