Skip to content

feat(videos): Runway (Gen/Veo) adapter on /v1/videos#817

Merged
moonming merged 2 commits into
mainfrom
feat/videos-runway
Jul 24, 2026
Merged

feat(videos): Runway (Gen/Veo) adapter on /v1/videos#817
moonming merged 2 commits into
mainfrom
feat/videos-runway

Conversation

@moonming

Copy link
Copy Markdown
Collaborator

Adds the Runway Developer API as the fourth provider on the unified /v1/videos surface — the first overseas provider — mirroring the existing Alibaba/Zhipu/Volcengine dispatch (PRs #811, #814) exactly. Refs api7/AISIX-Cloud#1118.

Why Runway (and why not Veo/Sora)

Runway's task output is an array of signed, self-authenticating URLs (long-lived signed JWT blobs, expiring 24–48h) — fetchable with no credentials, a clean fit for the existing 302 content route. Providers whose download URL requires a provider auth header (Google Veo's x-goog-api-key, Sora) are deferred to the content-streaming-proxy work and deliberately not added here.

Mapping table (verified against official docs + SDK)

Concern Runway shape Source
Base URL https://api.dev.runwayml.com (bare host; /v1 is per-path) SDK src/runwayml/_client.py base_url
Version header X-Runway-Version: 2024-11-06, required on every call SDK _client.py default header + docs 2024-11-06
Auth Authorization: Bearer <key> SDK _client.py
Submit POST /v1/text_to_video{ model, promptText, duration?, ratio? } SDK resources/text_to_video.py, types/text_to_video_create_params.py
Submit resp { id } (no status) → queued SDK types/text_to_video_create_response.py
Poll GET /v1/tasks/{id} SDK resources/tasks.py
prompt promptText (camelCase wire alias) text_to_video_create_params.py alias
seconds duration (int) same
size WxH ratio W:H (2024-11-06 expresses output resolution as WIDTH:HEIGHT, a per-model enum; lossless separator swap, mirrors DashScope x*) docs 2024-11-06, text_to_video_create_params.py allowed ratio values
Output URL output[0] (bare array of signed URLs, present only on SUCCEEDED) SDK types/task_retrieve_response.py
Failure failure (human) → error.message, failureCode (camelCase alias) → error.code same

Status normalisation

Runway unified
PENDING / THROTTLED queued
RUNNING in_progress
SUCCEEDED completed
FAILED / CANCELLED / other failed

Per-fact verification

  • Base URL — confirmed https://api.dev.runwayml.com (no /v1 suffix). No root-stripper invented (the documented api_base carries no version suffix, per brief).
  • Version header on both legsX-Runway-Version must ride submit AND poll (unlike DashScope's submit-only async marker). Wired via a new minimal all_request_header() hook applied unconditionally in provider_call; asserted on both legs in unit + e2e.
  • ratio = resolution — the 2024-11-06 version replaced aspect enums (16:9) with pixel-resolution strings (1280:720). sizeratio is therefore a faithful separator swap, not a dropped/invented mapping. ratio is required for several models, so omitting size lets the provider return its own "ratio required" 400 rather than inventing a default.
  • Output fetchable without auth — confirmed signed/self-authenticating URLs (expire 24–48h). No divergence from the brief; the 302 stays valid.
  • Failure fieldsfailure / failureCode (camelCase), surfaced through the unified error object like Ark's error.{code,message}.

Invariants preserved by construction (for Runway too)

3-segment video_id codec, submit quota::enforce(Some(&model_rl)) / GETs enforce(None), guardrail-before-quota on the prompt, uniform 404 video_not_found on GETs (unsupported-provider + ACL-denied folds), bounded metric labels (resolved display_name / UNRESOLVED sentinel), zero-token UsageEvent on submit only, 302 restricted to absolute http/https, task-id charset guard before URL interpolation.

Test evidence

Unit (cargo test -p aisix-proxy --lib videos): 41 passed, 0 failed — 5 new Runway tests (status-map table, ratio separator-swap + malformed rejection, submit-body shape, full submit→poll→content journey asserting X-Runway-Version on both legs, failed-task failure/failureCode), plus the root-stripper and URL-composition tables extended; all existing Alibaba/Zhipu/Ark tests stay green.

E2E:

  • videos family: 6 passed — new Runway journey against a mock upstream serving Runway-shaped responses, asserting the version header reached the upstream on the submit POST and both poll GETs, promptText/duration/ratio wire shape, and the 302 to output[0].
  • passthrough family: 12 passed (no regression).

Gate: cargo fmt --check clean, cargo clippy --workspace --tests 0 warnings, cargo build --bin aisix ok.

Notes

This DP adapter reads the free-form provider string; a matching CP catalog entry (runwayml provider + video model) is control-plane follow-up work under AISIX-Cloud#1118, same as the existing three video providers.

Add the Runway Developer API as the fourth provider on the unified
/v1/videos surface (first overseas provider), mirroring the existing
Alibaba/Zhipu/Volcengine dispatch exactly.

- provider string `runwayml` (models.dev canonical id), alias `runway`
  following the zhipuai/zhipu precedent
- submit POST /v1/text_to_video: {model, promptText, duration?, ratio?};
  `size` WIDTHxHEIGHT maps to `ratio` WIDTH:HEIGHT (the 2024-11-06
  resolution form) by separator swap, mirroring the DashScope x->*
  treatment; unset params omitted
- poll GET /v1/tasks/{id}: status PENDING/THROTTLED->queued,
  RUNNING->in_progress, SUCCEEDED->completed, FAILED/CANCELLED->failed;
  output[0] signed URL for the 302; failure/failureCode -> error object
- mandatory X-Runway-Version header wired on BOTH submit and poll via a
  new minimal all-request-header hook (distinct from the DashScope
  submit-only async marker)
- no api_base suffix stripped (documented base is the bare host; /v1 is
  per-path)

All invariants preserved by construction: 3-segment id codec, submit
quota-enforced / GETs exempt, guardrail-before-quota, uniform 404 folds
on GETs, bounded metric labels, zero-token submit UsageEvent, 302
restricted to http/https, task-id charset guard.

Field shapes verified against the official runwayml SDK (sdk-python
_client.py, resources/text_to_video.py, resources/tasks.py,
types/task_retrieve_response.py) and
https://docs.dev.runwayml.com/api-details/versions/2024-11-06/.

Overseas providers whose download URLs need auth (Veo, Sora) are
deferred to the content-streaming-proxy work.

Refs api7/AISIX-Cloud#1118
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@moonming, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 37 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1b2605f6-966c-45b7-acd7-fe22be3a7983

📥 Commits

Reviewing files that changed from the base of the PR and between 0f90a98 and e69a6e7.

📒 Files selected for processing (2)
  • crates/aisix-proxy/src/videos.rs
  • tests/e2e/src/cases/videos-providers-e2e.test.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/videos-runway

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

… pin version-header isolation

Audit follow-ups (all LOW/NOTE):
- fixtures used gen4_5 (underscore); Runway's canonical id is gen4.5
  (dot) — the pass-through code is model-agnostic but a CP catalog or
  docs entry copying gen4_5 would 400 upstream.
- clarify in comments that this is Runway-hosted Veo (veo3/veo3.1),
  distinct from Google-direct Veo (deferred, needs x-goog-api-key).
- assert the X-Runway-Version header does not bleed onto other
  providers' submit requests.
@moonming

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@moonming
moonming merged commit 85446b9 into main Jul 24, 2026
12 checks passed
@moonming
moonming deleted the feat/videos-runway branch July 24, 2026 09:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant