Skip to main content
This guide walks through building a complete Opperator agent that generates videos using OpenAI’s Sora API. You’ll learn how to structure an agent, manage API credentials securely, and use it from the Opperator TUI.

What You’ll Build

By the end of this guide, you’ll have a working Sora agent that:
  • Accepts natural language prompts for video generation
  • Manages OpenAI API credentials securely
  • Tracks video generation status in real-time
  • Displays generated videos in your working directory
  • Handles errors gracefully with helpful feedback

Prerequisites

Before starting, make sure you have:
  • Opperator installed (see Installation)
  • Python 3.8 or higher
  • OpenAI API key with Sora access (Get one here)
  • Basic familiarity with Python

Creating the Sora Agent

1

Bootstrap the agent

Use the Opperator CLI to quickly create the agent structure:
op agent bootstrap sora_agent -d "Generate videos using OpenAI Sora API" --no-start
This command:
  • Creates the agent directory at ~/.config/opperator/agents/sora_agent/
  • Sets up a Python virtual environment
  • Installs the Opperator SDK
  • Registers the agent in agents.yaml
  • Uses --no-start so we can add our code before running it
Navigate to the agent directory:
cd ~/.config/opperator/agents/sora_agent
2

Configure API Key

Set up your OpenAI API key using the CLI:
op secret create openai_api_key
This will prompt you to enter your API key securely. The key is stored in Opperator’s secret manager, where your agent can access it via get_secret("openai_api_key").Alternatively, you can provide the key inline:
op secret create openai_api_key sk-proj-...
3

Implement the agent logic

Replace the contents of main.py with the following implementation:
from opperator import OpperatorAgent
import openai
import os
import time
from pathlib import Path

class SoraAgent(OpperatorAgent):
    """Agent for generating videos using OpenAI's Sora API"""

    def __init__(self):
        super().__init__()
        self.current_generation = None

    async def on_start(self):
        """Initialize the agent when it starts"""
        self.log("Sora video generation agent started")

        # Get API key from secret manager
        api_key = self.get_secret("openai_api_key")
        if not api_key:
            self.log("⚠️  No OpenAI API key found. Set it with: op secret create openai_api_key")
        else:
            self.client = openai.OpenAI(api_key=api_key)
            self.log("✓ OpenAI API key configured")

    async def generate_video(
        self,
        prompt: str,
        duration: int = 5,
        aspect_ratio: str = "16:9"
    ) -> str:
        """
        Generate a video using Sora API

        Args:
            prompt: Description of the video to generate
            duration: Video duration in seconds (default: 5)
            aspect_ratio: Video aspect ratio (default: 16:9)

        Returns:
            Path to the generated video file
        """
        try:
            # Check if API key is configured
            if not hasattr(self, 'client'):
                return "❌ OpenAI API key not configured. Set it with: op secret create openai_api_key"

            self.log(f"🎬 Generating video: {prompt[:50]}...")

            # Update status section
            self.set_status_section("generation", {
                "title": "Current Generation",
                "items": [
                    {"label": "Status", "value": "Generating..."},
                    {"label": "Prompt", "value": prompt[:50] + "..."},
                    {"label": "Duration", "value": f"{duration}s"}
                ]
            })

            # Call Sora API
            # Note: This is a high-level example. Actual Sora API may differ.
            response = self.client.videos.generate(
                model="sora-1.0",
                prompt=prompt,
                duration=duration,
                aspect_ratio=aspect_ratio
            )

            # Wait for generation to complete
            video_id = response.id
            self.log(f"⏳ Video generation started (ID: {video_id})")

            while True:
                status = self.client.videos.retrieve(video_id)

                if status.status == "completed":
                    break
                elif status.status == "failed":
                    error_msg = f"❌ Video generation failed: {status.error}"
                    self.log(error_msg)
                    self.clear_status_section("generation")
                    return error_msg

                time.sleep(2)

            # Download the video
            video_url = status.url
            output_dir = Path.home() / "Videos" / "sora_generated"
            output_dir.mkdir(parents=True, exist_ok=True)

            timestamp = int(time.time())
            output_path = output_dir / f"sora_{timestamp}.mp4"

            # Download video content
            video_data = self.client.videos.download(video_id)
            with open(output_path, 'wb') as f:
                f.write(video_data)

            self.log(f"✅ Video saved to: {output_path}")

            # Update status to show completion
            self.set_status_section("generation", {
                "title": "Last Generation",
                "items": [
                    {"label": "Status", "value": "✅ Complete"},
                    {"label": "Prompt", "value": prompt[:50] + "..."},
                    {"label": "File", "value": str(output_path)}
                ]
            })

            return f"✅ Video generated successfully!\n\nSaved to: {output_path}\n\nPrompt: {prompt}"

        except Exception as e:
            error_msg = f"❌ Error generating video: {str(e)}"
            self.log(error_msg)
            self.clear_status_section("generation")
            return error_msg

    async def list_generations(self) -> str:
        """
        List all generated videos

        Returns:
            List of generated video files
        """
        output_dir = Path.home() / "Videos" / "sora_generated"

        if not output_dir.exists():
            return "No videos generated yet."

        videos = list(output_dir.glob("sora_*.mp4"))

        if not videos:
            return "No videos found in the output directory."

        result = f"📹 Found {len(videos)} generated video(s):\n\n"
        for video in sorted(videos, reverse=True):
            size_mb = video.stat().st_size / (1024 * 1024)
            result += f"- {video.name} ({size_mb:.1f} MB)\n"

        return result

if __name__ == "__main__":
    agent = SoraAgent()
    agent.run()
This implementation provides two main commands:
  • generate_video: Creates videos from text prompts
  • list_generations: Shows all generated videos
The agent retrieves the OpenAI API key from Opperator’s secret manager using get_secret("openai_api_key").
4

Start the agent

Start your Sora agent:
op agent start sora_agent
Or launch the Opperator TUI and switch to your Sora agent:
opperator
In the TUI:
  • Press Shift+Tab to open the agent picker
  • Select sora_agent
  • You’re ready to start generating videos!
Note: The agent was already registered in agents.yaml when you ran the bootstrap command.

Using Your Sora Agent

Generate Your First Video

Simply describe the video you want in natural language:
Generate a video of a golden retriever playing in a park during sunset
The LLM will call the generate_video command with your prompt. You’ll see:
  • Real-time status updates in the sidebar
  • Logs showing generation progress
  • The final video path when complete

Use Slash Commands

For direct control, you can call commands explicitly:
/generate_video A serene ocean wave at sunset, duration 10s and aspect_ratio 16:9

List Your Videos

Check all generated videos:
/list_generations

Next Steps

Continue Learning

Deepen your understanding of the Opperator SDK features used in this guide:

Reference Documentation