Summary
Upgrade OpenRouterProvider in media_providers.py to support video generation using OpenRouter's async video API (POST /api/v1/videos → poll → download).
Context
OpenRouter launched a dedicated video generation API in March 2026. It's asynchronous: you submit a job, poll for status, then download the result. This is a different pattern from the synchronous chat completions API used for images.
LiteLLM does NOT route to OpenRouter's video endpoint — LiteLLM's video support covers OpenAI/Azure/Gemini/Vertex/RunwayML only. So this must be direct HTTP.
Available Models (via OpenRouter)
| Model |
Price |
Features |
google/veo-3.1 |
$0.40-0.50/sec |
1080p, native audio, scene extension, 4K upscale |
openai/sora-2-pro |
$0.30/sec |
Physics-accurate, multi-shot, C2PA provenance |
alibaba/wan-2.6 |
$0.04/sec |
1080p, text/image/reference-to-video, lip-sync |
alibaba/wan-2.7 |
~$0.04/sec |
First+last frame control, reference-to-video |
bytedance/seedance-2.0 |
varies |
Text/image/reference-to-video |
bytedance/seedance-1.5-pro |
varies |
4.5B params, multi-lang lip-sync, camera control |
API Reference
Submit: POST /api/v1/videos
{
"model": "google/veo-3.1",
"prompt": "A golden retriever on a beach",
"duration": 8,
"resolution": "1080p",
"aspect_ratio": "16:9",
"generate_audio": true,
"seed": 42,
"frame_images": [
{"type": "image_url", "image_url": {"url": "..."}, "frame_type": "first_frame"}
],
"input_references": [
{"type": "image_url", "image_url": {"url": "..."}}
],
"provider": {"options": {"google-vertex": {"parameters": {"negativePrompt": "blurry"}}}}
}
Response (202): {"id": "abc123", "polling_url": "https://openrouter.ai/api/v1/videos/abc123", "status": "pending"}
Poll: GET /api/v1/videos/{jobId}
Status transitions: pending → in_progress → completed | failed
On completion: {"unsigned_urls": ["https://openrouter.ai/api/v1/videos/abc123/content?index=0"], "usage": {"cost": 0.25}}
Download: GET /api/v1/videos/{jobId}/content?index=0 → binary MP4
Model discovery: GET /api/v1/videos/models
Implementation
Update OpenRouterProvider.generate_video() in media_providers.py:
async def generate_video(
self,
prompt: str,
model: Optional[str] = None,
image_url: Optional[str] = None,
duration: Optional[int] = None,
resolution: Optional[str] = None,
aspect_ratio: Optional[str] = None,
generate_audio: Optional[bool] = None,
seed: Optional[int] = None,
frame_images: Optional[List[Dict]] = None,
input_references: Optional[List[Dict]] = None,
poll_interval: float = 30.0,
timeout: float = 600.0,
**kwargs,
) -> MultimodalResponse:
Key implementation details:
- Strip
openrouter/ prefix before sending to API (OpenRouter models are google/veo-3.1, not openrouter/google/veo-3.1)
- Use
aiohttp or httpx for async HTTP (check what's already available in the project deps)
- Polling loop with configurable interval (default 30s) and timeout (default 10min)
- Download video bytes on completion and return as
MultimodalResponse with FileOutput in files[]
- Error handling: Map HTTP 400/401/402/429/500 to clear error messages
- API key: Use
OPENROUTER_API_KEY env var or explicit api_key param
Update supported_modalities:
@property
def supported_modalities(self) -> List[str]:
return ["image", "video"] # was ["image"]
Developer Experience
# From agent
result = await app.ai_generate_video(
"A golden retriever playing fetch on a beach",
model="openrouter/google/veo-3.1",
resolution="1080p",
aspect_ratio="16:9",
duration=8,
generate_audio=True,
)
result.files[0].save("dog_beach.mp4")
# Image-to-video
result = await app.ai_generate_video(
"Camera slowly pans across the scene",
model="openrouter/alibaba/wan-2.7",
image_url="https://example.com/landscape.jpg",
frame_images=[{"type": "image_url", "image_url": {"url": "https://..."}, "frame_type": "first_frame"}],
)
# Cheap testing
result = await app.ai_generate_video(
"A cat playing with yarn",
model="openrouter/alibaba/wan-2.6", # $0.04/sec
)
Dependencies
Files
| File |
Change |
sdk/python/agentfield/media_providers.py |
Upgrade OpenRouterProvider.generate_video(), update supported_modalities |
sdk/python/agentfield/types.py |
Add openrouter_api_key: Optional[str] to AIConfig, update video_model default |
Acceptance Criteria
Testing
# Manual integration test (requires OPENROUTER_API_KEY)
OPENROUTER_API_KEY=sk-... python -c "
import asyncio
from agentfield.media_providers import OpenRouterProvider
async def main():
p = OpenRouterProvider(api_key='sk-...')
result = await p.generate_video('A cat walking', model='alibaba/wan-2.6')
print(f'Files: {len(result.files)}, URL: {result.files[0].url}')
asyncio.run(main())
"
Notes for Contributors
Severity: HIGH — This is the core new feature.
The async polling pattern is different from everything else in the SDK. Use asyncio.sleep() in the poll loop, not time.sleep(). Add a reasonable default timeout (600s) because video gen can take minutes.
The frame_images and input_references parameters should be passed through as-is to the API — don't try to transform them. Let the user follow OpenRouter's schema directly.
For the HTTP client, prefer httpx (async) if it's already a dependency, otherwise use aiohttp. Check pyproject.toml for what's available. Fallback to aiohttp if neither is present.
Summary
Upgrade
OpenRouterProviderinmedia_providers.pyto support video generation using OpenRouter's async video API (POST /api/v1/videos→ poll → download).Context
OpenRouter launched a dedicated video generation API in March 2026. It's asynchronous: you submit a job, poll for status, then download the result. This is a different pattern from the synchronous chat completions API used for images.
LiteLLM does NOT route to OpenRouter's video endpoint — LiteLLM's video support covers OpenAI/Azure/Gemini/Vertex/RunwayML only. So this must be direct HTTP.
Available Models (via OpenRouter)
google/veo-3.1openai/sora-2-proalibaba/wan-2.6alibaba/wan-2.7bytedance/seedance-2.0bytedance/seedance-1.5-proAPI Reference
Submit:
POST /api/v1/videos{ "model": "google/veo-3.1", "prompt": "A golden retriever on a beach", "duration": 8, "resolution": "1080p", "aspect_ratio": "16:9", "generate_audio": true, "seed": 42, "frame_images": [ {"type": "image_url", "image_url": {"url": "..."}, "frame_type": "first_frame"} ], "input_references": [ {"type": "image_url", "image_url": {"url": "..."}} ], "provider": {"options": {"google-vertex": {"parameters": {"negativePrompt": "blurry"}}}} }Response (202):
{"id": "abc123", "polling_url": "https://openrouter.ai/api/v1/videos/abc123", "status": "pending"}Poll:
GET /api/v1/videos/{jobId}Status transitions:
pending→in_progress→completed|failedOn completion:
{"unsigned_urls": ["https://openrouter.ai/api/v1/videos/abc123/content?index=0"], "usage": {"cost": 0.25}}Download:
GET /api/v1/videos/{jobId}/content?index=0→ binary MP4Model discovery:
GET /api/v1/videos/modelsImplementation
Update
OpenRouterProvider.generate_video()inmedia_providers.py:Key implementation details:
openrouter/prefix before sending to API (OpenRouter models aregoogle/veo-3.1, notopenrouter/google/veo-3.1)aiohttporhttpxfor async HTTP (check what's already available in the project deps)MultimodalResponsewithFileOutputinfiles[]OPENROUTER_API_KEYenv var or explicitapi_keyparamUpdate
supported_modalities:Developer Experience
Dependencies
Files
sdk/python/agentfield/media_providers.pyOpenRouterProvider.generate_video(), updatesupported_modalitiessdk/python/agentfield/types.pyopenrouter_api_key: Optional[str]toAIConfig, updatevideo_modeldefaultAcceptance Criteria
OpenRouterProvider.generate_video()submits to/api/v1/videos, polls, downloadsMultimodalResponsewith video asFileOutputinfiles[]modelprefixopenrouter/is stripped before API callalibaba/wan-2.6(cheapest for testing)pytest sdk/python/passesruff check sdk/python/passesTesting
Notes for Contributors
Severity: HIGH — This is the core new feature.
The async polling pattern is different from everything else in the SDK. Use
asyncio.sleep()in the poll loop, nottime.sleep(). Add a reasonable default timeout (600s) because video gen can take minutes.The
frame_imagesandinput_referencesparameters should be passed through as-is to the API — don't try to transform them. Let the user follow OpenRouter's schema directly.For the HTTP client, prefer
httpx(async) if it's already a dependency, otherwise useaiohttp. Checkpyproject.tomlfor what's available. Fallback toaiohttpif neither is present.