From d83d1895c3ef26cb6c803db36dacab1b43350fd7 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 17 Jul 2026 17:23:54 +0000
Subject: [PATCH 1/9] feat(stlc): configurable CI runner and
private-production-repo support in workflow templates
---
.github/workflows/ci.yml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 3b9852c..5c973cf 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -18,7 +18,7 @@ jobs:
lint:
timeout-minutes: 10
name: lint
- runs-on: ${{ github.repository == 'stainless-sdks/runwayml-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
+ runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
@@ -44,7 +44,7 @@ jobs:
permissions:
contents: read
id-token: write
- runs-on: ${{ github.repository == 'stainless-sdks/runwayml-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
+ runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
@@ -84,7 +84,7 @@ jobs:
test:
timeout-minutes: 10
name: test
- runs-on: ${{ github.repository == 'stainless-sdks/runwayml-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
+ runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
From 05df606fe356a89e7cfa2765f77a1cddbdf06d71 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 21 Jul 2026 18:08:54 +0000
Subject: [PATCH 2/9] feat(api): add Model Router CRUD and routed video
generation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Expose Model Router management and routed generate so SDKs can create configs and start video jobs without picking a model.
- POST /v1/generate/video — route a video request via a saved config
- GET/POST /v1/routers and GET/PATCH/DELETE /v1/routers/{id}
---
.stats.yml | 8 +-
README.md | 26 +-
api.md | 35 +
src/runwayml/_client.py | 76 +++
src/runwayml/resources/__init__.py | 28 +
src/runwayml/resources/generate/__init__.py | 33 +
src/runwayml/resources/generate/generate.py | 102 +++
src/runwayml/resources/generate/video.py | 213 ++++++
src/runwayml/resources/routers.py | 605 ++++++++++++++++++
src/runwayml/resources/text_to_image.py | 12 +
src/runwayml/types/__init__.py | 7 +
src/runwayml/types/generate/__init__.py | 6 +
.../types/generate/video_create_params.py | 182 ++++++
.../types/generate/video_create_response.py | 179 ++++++
.../organization_retrieve_usage_response.py | 2 +
src/runwayml/types/router_create_params.py | 90 +++
src/runwayml/types/router_create_response.py | 97 +++
src/runwayml/types/router_list_params.py | 15 +
src/runwayml/types/router_list_response.py | 99 +++
.../types/router_retrieve_response.py | 97 +++
src/runwayml/types/router_update_params.py | 80 +++
src/runwayml/types/router_update_response.py | 97 +++
.../types/text_to_image_create_params.py | 6 +
tests/api_resources/generate/__init__.py | 1 +
tests/api_resources/generate/test_video.py | 174 +++++
tests/api_resources/test_routers.py | 486 ++++++++++++++
tests/api_resources/test_text_to_image.py | 2 +
uv.lock | 2 +-
28 files changed, 2742 insertions(+), 18 deletions(-)
create mode 100644 src/runwayml/resources/generate/__init__.py
create mode 100644 src/runwayml/resources/generate/generate.py
create mode 100644 src/runwayml/resources/generate/video.py
create mode 100644 src/runwayml/resources/routers.py
create mode 100644 src/runwayml/types/generate/__init__.py
create mode 100644 src/runwayml/types/generate/video_create_params.py
create mode 100644 src/runwayml/types/generate/video_create_response.py
create mode 100644 src/runwayml/types/router_create_params.py
create mode 100644 src/runwayml/types/router_create_response.py
create mode 100644 src/runwayml/types/router_list_params.py
create mode 100644 src/runwayml/types/router_list_response.py
create mode 100644 src/runwayml/types/router_retrieve_response.py
create mode 100644 src/runwayml/types/router_update_params.py
create mode 100644 src/runwayml/types/router_update_response.py
create mode 100644 tests/api_resources/generate/__init__.py
create mode 100644 tests/api_resources/generate/test_video.py
create mode 100644 tests/api_resources/test_routers.py
diff --git a/.stats.yml b/.stats.yml
index 8c6b27f..0cfcc15 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 51
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runwayml/runwayml-391d899ffd246491c8d2a02adec62eb5fc1b00044e6704a7886d8e8e09d72484.yml
-openapi_spec_hash: 475451c27bcc5721a02ee92c1898000e
-config_hash: 7caf94f26186e1ee432efcaa0b590bab
+configured_endpoints: 57
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runwayml/runwayml-264e9b264bae2f906e06f33a5af492e19580bcd192f6094cfdb534fcbf70f5bc.yml
+openapi_spec_hash: 30ac92dee6f88c19ea1cdc174b05a260
+config_hash: 6bdea3c3109ee75952f779d566c229ea
diff --git a/README.md b/README.md
index 00f3a2a..974bdac 100644
--- a/README.md
+++ b/README.md
@@ -132,14 +132,14 @@ from runwayml import RunwayML
client = RunwayML()
-all_avatars = []
+all_routers = []
# Automatically fetches more pages as needed.
-for avatar in client.avatars.list(
+for router in client.routers.list(
limit=1,
):
- # Do something with avatar here
- all_avatars.append(avatar)
-print(all_avatars)
+ # Do something with router here
+ all_routers.append(router)
+print(all_routers)
```
Or, asynchronously:
@@ -152,13 +152,13 @@ client = AsyncRunwayML()
async def main() -> None:
- all_avatars = []
+ all_routers = []
# Iterate through items across all pages, issuing requests as needed.
- async for avatar in client.avatars.list(
+ async for router in client.routers.list(
limit=1,
):
- all_avatars.append(avatar)
- print(all_avatars)
+ all_routers.append(router)
+ print(all_routers)
asyncio.run(main())
@@ -167,7 +167,7 @@ asyncio.run(main())
Alternatively, you can use the `.has_next_page()`, `.next_page_info()`, or `.get_next_page()` methods for more granular control working with pages:
```python
-first_page = await client.avatars.list(
+first_page = await client.routers.list(
limit=1,
)
if first_page.has_next_page():
@@ -181,13 +181,13 @@ if first_page.has_next_page():
Or just work directly with the returned data:
```python
-first_page = await client.avatars.list(
+first_page = await client.routers.list(
limit=1,
)
print(f"next page cursor: {first_page.next_cursor}") # => "next page cursor: ..."
-for avatar in first_page.data:
- print(avatar)
+for router in first_page.data:
+ print(router.id)
# Remove `await` for non-async usage.
```
diff --git a/api.md b/api.md
index 5b8a55f..6bb94e6 100644
--- a/api.md
+++ b/api.md
@@ -155,6 +155,41 @@ Methods:
- client.video_upscale.create(\*\*params) -> VideoUpscaleCreateResponse
+# Generate
+
+## Video
+
+Types:
+
+```python
+from runwayml.types.generate import VideoCreateResponse
+```
+
+Methods:
+
+- client.generate.video.create(\*\*params) -> VideoCreateResponse
+
+# Routers
+
+Types:
+
+```python
+from runwayml.types import (
+ RouterCreateResponse,
+ RouterRetrieveResponse,
+ RouterUpdateResponse,
+ RouterListResponse,
+)
+```
+
+Methods:
+
+- client.routers.create(\*\*params) -> RouterCreateResponse
+- client.routers.retrieve(id) -> RouterRetrieveResponse
+- client.routers.update(id, \*\*params) -> RouterUpdateResponse
+- client.routers.list(\*\*params) -> SyncCursorPage[RouterListResponse]
+- client.routers.delete(id) -> None
+
# Organization
Types:
diff --git a/src/runwayml/_client.py b/src/runwayml/_client.py
index f5df830..b048cd3 100644
--- a/src/runwayml/_client.py
+++ b/src/runwayml/_client.py
@@ -40,7 +40,9 @@
voices,
avatars,
recipes,
+ routers,
uploads,
+ generate,
documents,
workflows,
organization,
@@ -65,6 +67,7 @@
from .resources.voices import VoicesResource, AsyncVoicesResource
from .resources.avatars import AvatarsResource, AsyncAvatarsResource
from .resources.recipes import RecipesResource, AsyncRecipesResource
+ from .resources.routers import RoutersResource, AsyncRoutersResource
from .resources.uploads import UploadsResource, AsyncUploadsResource
from .resources.documents import DocumentsResource, AsyncDocumentsResource
from .resources.workflows import WorkflowsResource, AsyncWorkflowsResource
@@ -81,6 +84,7 @@
from .resources.video_to_video import VideoToVideoResource, AsyncVideoToVideoResource
from .resources.voice_isolation import VoiceIsolationResource, AsyncVoiceIsolationResource
from .resources.speech_to_speech import SpeechToSpeechResource, AsyncSpeechToSpeechResource
+ from .resources.generate.generate import GenerateResource, AsyncGenerateResource
from .resources.realtime_sessions import RealtimeSessionsResource, AsyncRealtimeSessionsResource
from .resources.avatar_conversations import AvatarConversationsResource, AsyncAvatarConversationsResource
from .resources.workflow_invocations import WorkflowInvocationsResource, AsyncWorkflowInvocationsResource
@@ -265,6 +269,18 @@ def video_upscale(self) -> VideoUpscaleResource:
return VideoUpscaleResource(self)
+ @cached_property
+ def generate(self) -> GenerateResource:
+ from .resources.generate import GenerateResource
+
+ return GenerateResource(self)
+
+ @cached_property
+ def routers(self) -> RoutersResource:
+ from .resources.routers import RoutersResource
+
+ return RoutersResource(self)
+
@cached_property
def organization(self) -> OrganizationResource:
from .resources.organization import OrganizationResource
@@ -608,6 +624,18 @@ def video_upscale(self) -> AsyncVideoUpscaleResource:
return AsyncVideoUpscaleResource(self)
+ @cached_property
+ def generate(self) -> AsyncGenerateResource:
+ from .resources.generate import AsyncGenerateResource
+
+ return AsyncGenerateResource(self)
+
+ @cached_property
+ def routers(self) -> AsyncRoutersResource:
+ from .resources.routers import AsyncRoutersResource
+
+ return AsyncRoutersResource(self)
+
@cached_property
def organization(self) -> AsyncOrganizationResource:
from .resources.organization import AsyncOrganizationResource
@@ -887,6 +915,18 @@ def video_upscale(self) -> video_upscale.VideoUpscaleResourceWithRawResponse:
return VideoUpscaleResourceWithRawResponse(self._client.video_upscale)
+ @cached_property
+ def generate(self) -> generate.GenerateResourceWithRawResponse:
+ from .resources.generate import GenerateResourceWithRawResponse
+
+ return GenerateResourceWithRawResponse(self._client.generate)
+
+ @cached_property
+ def routers(self) -> routers.RoutersResourceWithRawResponse:
+ from .resources.routers import RoutersResourceWithRawResponse
+
+ return RoutersResourceWithRawResponse(self._client.routers)
+
@cached_property
def organization(self) -> organization.OrganizationResourceWithRawResponse:
from .resources.organization import OrganizationResourceWithRawResponse
@@ -1051,6 +1091,18 @@ def video_upscale(self) -> video_upscale.AsyncVideoUpscaleResourceWithRawRespons
return AsyncVideoUpscaleResourceWithRawResponse(self._client.video_upscale)
+ @cached_property
+ def generate(self) -> generate.AsyncGenerateResourceWithRawResponse:
+ from .resources.generate import AsyncGenerateResourceWithRawResponse
+
+ return AsyncGenerateResourceWithRawResponse(self._client.generate)
+
+ @cached_property
+ def routers(self) -> routers.AsyncRoutersResourceWithRawResponse:
+ from .resources.routers import AsyncRoutersResourceWithRawResponse
+
+ return AsyncRoutersResourceWithRawResponse(self._client.routers)
+
@cached_property
def organization(self) -> organization.AsyncOrganizationResourceWithRawResponse:
from .resources.organization import AsyncOrganizationResourceWithRawResponse
@@ -1215,6 +1267,18 @@ def video_upscale(self) -> video_upscale.VideoUpscaleResourceWithStreamingRespon
return VideoUpscaleResourceWithStreamingResponse(self._client.video_upscale)
+ @cached_property
+ def generate(self) -> generate.GenerateResourceWithStreamingResponse:
+ from .resources.generate import GenerateResourceWithStreamingResponse
+
+ return GenerateResourceWithStreamingResponse(self._client.generate)
+
+ @cached_property
+ def routers(self) -> routers.RoutersResourceWithStreamingResponse:
+ from .resources.routers import RoutersResourceWithStreamingResponse
+
+ return RoutersResourceWithStreamingResponse(self._client.routers)
+
@cached_property
def organization(self) -> organization.OrganizationResourceWithStreamingResponse:
from .resources.organization import OrganizationResourceWithStreamingResponse
@@ -1379,6 +1443,18 @@ def video_upscale(self) -> video_upscale.AsyncVideoUpscaleResourceWithStreamingR
return AsyncVideoUpscaleResourceWithStreamingResponse(self._client.video_upscale)
+ @cached_property
+ def generate(self) -> generate.AsyncGenerateResourceWithStreamingResponse:
+ from .resources.generate import AsyncGenerateResourceWithStreamingResponse
+
+ return AsyncGenerateResourceWithStreamingResponse(self._client.generate)
+
+ @cached_property
+ def routers(self) -> routers.AsyncRoutersResourceWithStreamingResponse:
+ from .resources.routers import AsyncRoutersResourceWithStreamingResponse
+
+ return AsyncRoutersResourceWithStreamingResponse(self._client.routers)
+
@cached_property
def organization(self) -> organization.AsyncOrganizationResourceWithStreamingResponse:
from .resources.organization import AsyncOrganizationResourceWithStreamingResponse
diff --git a/src/runwayml/resources/__init__.py b/src/runwayml/resources/__init__.py
index 336dc03..3454fde 100644
--- a/src/runwayml/resources/__init__.py
+++ b/src/runwayml/resources/__init__.py
@@ -32,6 +32,14 @@
RecipesResourceWithStreamingResponse,
AsyncRecipesResourceWithStreamingResponse,
)
+from .routers import (
+ RoutersResource,
+ AsyncRoutersResource,
+ RoutersResourceWithRawResponse,
+ AsyncRoutersResourceWithRawResponse,
+ RoutersResourceWithStreamingResponse,
+ AsyncRoutersResourceWithStreamingResponse,
+)
from .uploads import (
UploadsResource,
AsyncUploadsResource,
@@ -40,6 +48,14 @@
UploadsResourceWithStreamingResponse,
AsyncUploadsResourceWithStreamingResponse,
)
+from .generate import (
+ GenerateResource,
+ AsyncGenerateResource,
+ GenerateResourceWithRawResponse,
+ AsyncGenerateResourceWithRawResponse,
+ GenerateResourceWithStreamingResponse,
+ AsyncGenerateResourceWithStreamingResponse,
+)
from .documents import (
DocumentsResource,
AsyncDocumentsResource,
@@ -272,6 +288,18 @@
"AsyncVideoUpscaleResourceWithRawResponse",
"VideoUpscaleResourceWithStreamingResponse",
"AsyncVideoUpscaleResourceWithStreamingResponse",
+ "GenerateResource",
+ "AsyncGenerateResource",
+ "GenerateResourceWithRawResponse",
+ "AsyncGenerateResourceWithRawResponse",
+ "GenerateResourceWithStreamingResponse",
+ "AsyncGenerateResourceWithStreamingResponse",
+ "RoutersResource",
+ "AsyncRoutersResource",
+ "RoutersResourceWithRawResponse",
+ "AsyncRoutersResourceWithRawResponse",
+ "RoutersResourceWithStreamingResponse",
+ "AsyncRoutersResourceWithStreamingResponse",
"OrganizationResource",
"AsyncOrganizationResource",
"OrganizationResourceWithRawResponse",
diff --git a/src/runwayml/resources/generate/__init__.py b/src/runwayml/resources/generate/__init__.py
new file mode 100644
index 0000000..a52fcf1
--- /dev/null
+++ b/src/runwayml/resources/generate/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .video import (
+ VideoResource,
+ AsyncVideoResource,
+ VideoResourceWithRawResponse,
+ AsyncVideoResourceWithRawResponse,
+ VideoResourceWithStreamingResponse,
+ AsyncVideoResourceWithStreamingResponse,
+)
+from .generate import (
+ GenerateResource,
+ AsyncGenerateResource,
+ GenerateResourceWithRawResponse,
+ AsyncGenerateResourceWithRawResponse,
+ GenerateResourceWithStreamingResponse,
+ AsyncGenerateResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "VideoResource",
+ "AsyncVideoResource",
+ "VideoResourceWithRawResponse",
+ "AsyncVideoResourceWithRawResponse",
+ "VideoResourceWithStreamingResponse",
+ "AsyncVideoResourceWithStreamingResponse",
+ "GenerateResource",
+ "AsyncGenerateResource",
+ "GenerateResourceWithRawResponse",
+ "AsyncGenerateResourceWithRawResponse",
+ "GenerateResourceWithStreamingResponse",
+ "AsyncGenerateResourceWithStreamingResponse",
+]
diff --git a/src/runwayml/resources/generate/generate.py b/src/runwayml/resources/generate/generate.py
new file mode 100644
index 0000000..02d0cbc
--- /dev/null
+++ b/src/runwayml/resources/generate/generate.py
@@ -0,0 +1,102 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .video import (
+ VideoResource,
+ AsyncVideoResource,
+ VideoResourceWithRawResponse,
+ AsyncVideoResourceWithRawResponse,
+ VideoResourceWithStreamingResponse,
+ AsyncVideoResourceWithStreamingResponse,
+)
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+
+__all__ = ["GenerateResource", "AsyncGenerateResource"]
+
+
+class GenerateResource(SyncAPIResource):
+ @cached_property
+ def video(self) -> VideoResource:
+ return VideoResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> GenerateResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/runwayml/sdk-python#accessing-raw-response-data-eg-headers
+ """
+ return GenerateResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> GenerateResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/runwayml/sdk-python#with_streaming_response
+ """
+ return GenerateResourceWithStreamingResponse(self)
+
+
+class AsyncGenerateResource(AsyncAPIResource):
+ @cached_property
+ def video(self) -> AsyncVideoResource:
+ return AsyncVideoResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncGenerateResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/runwayml/sdk-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncGenerateResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncGenerateResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/runwayml/sdk-python#with_streaming_response
+ """
+ return AsyncGenerateResourceWithStreamingResponse(self)
+
+
+class GenerateResourceWithRawResponse:
+ def __init__(self, generate: GenerateResource) -> None:
+ self._generate = generate
+
+ @cached_property
+ def video(self) -> VideoResourceWithRawResponse:
+ return VideoResourceWithRawResponse(self._generate.video)
+
+
+class AsyncGenerateResourceWithRawResponse:
+ def __init__(self, generate: AsyncGenerateResource) -> None:
+ self._generate = generate
+
+ @cached_property
+ def video(self) -> AsyncVideoResourceWithRawResponse:
+ return AsyncVideoResourceWithRawResponse(self._generate.video)
+
+
+class GenerateResourceWithStreamingResponse:
+ def __init__(self, generate: GenerateResource) -> None:
+ self._generate = generate
+
+ @cached_property
+ def video(self) -> VideoResourceWithStreamingResponse:
+ return VideoResourceWithStreamingResponse(self._generate.video)
+
+
+class AsyncGenerateResourceWithStreamingResponse:
+ def __init__(self, generate: AsyncGenerateResource) -> None:
+ self._generate = generate
+
+ @cached_property
+ def video(self) -> AsyncVideoResourceWithStreamingResponse:
+ return AsyncVideoResourceWithStreamingResponse(self._generate.video)
diff --git a/src/runwayml/resources/generate/video.py b/src/runwayml/resources/generate/video.py
new file mode 100644
index 0000000..7c05ac3
--- /dev/null
+++ b/src/runwayml/resources/generate/video.py
@@ -0,0 +1,213 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Any, cast
+
+import httpx
+
+from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from ..._utils import maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.generate import video_create_params
+from ...types.generate.video_create_response import VideoCreateResponse
+
+__all__ = ["VideoResource", "AsyncVideoResource"]
+
+
+class VideoResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> VideoResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/runwayml/sdk-python#accessing-raw-response-data-eg-headers
+ """
+ return VideoResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> VideoResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/runwayml/sdk-python#with_streaming_response
+ """
+ return VideoResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ *,
+ config_id: str,
+ input: video_create_params.Input,
+ dry_run: bool | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VideoCreateResponse:
+ """
+ Start a video generation task using a saved Model Router config instead of
+ naming a model.
+
+ Args:
+ config_id: The slug of a saved Model Router config to route this request with.
+
+ input: Model-agnostic video generation input. Fields are optional; the router selects a
+ model and maps these options to it.
+
+ dry_run: When true, run the full routing pipeline and return the decision and estimated
+ cost without generating. No task is created, nothing is billed, and no asset is
+ produced.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return cast(
+ VideoCreateResponse,
+ self._post(
+ "/v1/generate/video",
+ body=maybe_transform(
+ {
+ "config_id": config_id,
+ "input": input,
+ "dry_run": dry_run,
+ },
+ video_create_params.VideoCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=cast(
+ Any, VideoCreateResponse
+ ), # Union types cannot be passed in as arguments in the type system
+ ),
+ )
+
+
+class AsyncVideoResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncVideoResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/runwayml/sdk-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncVideoResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncVideoResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/runwayml/sdk-python#with_streaming_response
+ """
+ return AsyncVideoResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ *,
+ config_id: str,
+ input: video_create_params.Input,
+ dry_run: bool | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VideoCreateResponse:
+ """
+ Start a video generation task using a saved Model Router config instead of
+ naming a model.
+
+ Args:
+ config_id: The slug of a saved Model Router config to route this request with.
+
+ input: Model-agnostic video generation input. Fields are optional; the router selects a
+ model and maps these options to it.
+
+ dry_run: When true, run the full routing pipeline and return the decision and estimated
+ cost without generating. No task is created, nothing is billed, and no asset is
+ produced.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return cast(
+ VideoCreateResponse,
+ await self._post(
+ "/v1/generate/video",
+ body=await async_maybe_transform(
+ {
+ "config_id": config_id,
+ "input": input,
+ "dry_run": dry_run,
+ },
+ video_create_params.VideoCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=cast(
+ Any, VideoCreateResponse
+ ), # Union types cannot be passed in as arguments in the type system
+ ),
+ )
+
+
+class VideoResourceWithRawResponse:
+ def __init__(self, video: VideoResource) -> None:
+ self._video = video
+
+ self.create = to_raw_response_wrapper(
+ video.create,
+ )
+
+
+class AsyncVideoResourceWithRawResponse:
+ def __init__(self, video: AsyncVideoResource) -> None:
+ self._video = video
+
+ self.create = async_to_raw_response_wrapper(
+ video.create,
+ )
+
+
+class VideoResourceWithStreamingResponse:
+ def __init__(self, video: VideoResource) -> None:
+ self._video = video
+
+ self.create = to_streamed_response_wrapper(
+ video.create,
+ )
+
+
+class AsyncVideoResourceWithStreamingResponse:
+ def __init__(self, video: AsyncVideoResource) -> None:
+ self._video = video
+
+ self.create = async_to_streamed_response_wrapper(
+ video.create,
+ )
diff --git a/src/runwayml/resources/routers.py b/src/runwayml/resources/routers.py
new file mode 100644
index 0000000..58282b3
--- /dev/null
+++ b/src/runwayml/resources/routers.py
@@ -0,0 +1,605 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+
+import httpx
+
+from ..types import router_list_params, router_create_params, router_update_params
+from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
+from .._utils import path_template, maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
+from .._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..pagination import SyncCursorPage, AsyncCursorPage
+from .._base_client import AsyncPaginator, make_request_options
+from ..types.router_list_response import RouterListResponse
+from ..types.router_create_response import RouterCreateResponse
+from ..types.router_update_response import RouterUpdateResponse
+from ..types.router_retrieve_response import RouterRetrieveResponse
+
+__all__ = ["RoutersResource", "AsyncRoutersResource"]
+
+
+class RoutersResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> RoutersResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/runwayml/sdk-python#accessing-raw-response-data-eg-headers
+ """
+ return RoutersResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> RoutersResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/runwayml/sdk-python#with_streaming_response
+ """
+ return RoutersResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ *,
+ slug: str,
+ description: str | Omit = omit,
+ name: str | Omit = omit,
+ settings: router_create_params.Settings | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> RouterCreateResponse:
+ """
+ Create a Model Router configuration.
+
+ Args:
+ slug: Immutable slug used to reference this Model Router in generation requests (for
+ example, production-video). Unique within the API project. The UUID id remains
+ the canonical management identifier.
+
+ description: An optional Model Router description.
+
+ name: Optional human-readable display name for this router. Defaults to the slug when
+ omitted.
+
+ settings: Model Router routing preferences. Defaults to cost-optimized allow-all when
+ omitted. Modality is implied by the generate endpoint used with this Model
+ Router.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._post(
+ "/v1/routers",
+ body=maybe_transform(
+ {
+ "slug": slug,
+ "description": description,
+ "name": name,
+ "settings": settings,
+ },
+ router_create_params.RouterCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=RouterCreateResponse,
+ )
+
+ def retrieve(
+ self,
+ id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> RouterRetrieveResponse:
+ """
+ Retrieve a Model Router configuration by ID.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return self._get(
+ path_template("/v1/routers/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=RouterRetrieveResponse,
+ )
+
+ def update(
+ self,
+ id: str,
+ *,
+ description: Optional[str] | Omit = omit,
+ name: str | Omit = omit,
+ settings: router_update_params.Settings | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> RouterUpdateResponse:
+ """Update a Model Router configuration.
+
+ Settings changes append a new version; name
+ and description updates do not. Settings are merged with the current snapshot —
+ omitted fields keep their existing values.
+
+ Args:
+ name: Display name. The slug is immutable and cannot be changed after creation.
+
+ settings: Nested merge: omitted settings fields keep their current values. When models is
+ present, omitted models.mode or models.ids are preserved (sending only
+ optimizeFor does not clear the model allowlist or credit ceiling).
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return self._patch(
+ path_template("/v1/routers/{id}", id=id),
+ body=maybe_transform(
+ {
+ "description": description,
+ "name": name,
+ "settings": settings,
+ },
+ router_update_params.RouterUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=RouterUpdateResponse,
+ )
+
+ def list(
+ self,
+ *,
+ limit: int,
+ cursor: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SyncCursorPage[RouterListResponse]:
+ """
+ List Model Router configurations for the authenticated organization with
+ cursor-based pagination.
+
+ Args:
+ limit: The maximum number of items to return per page.
+
+ cursor: Cursor from a previous response for fetching the next page of results.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get_api_list(
+ "/v1/routers",
+ page=SyncCursorPage[RouterListResponse],
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "limit": limit,
+ "cursor": cursor,
+ },
+ router_list_params.RouterListParams,
+ ),
+ ),
+ model=RouterListResponse,
+ )
+
+ def delete(
+ self,
+ id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """Delete a Model Router configuration.
+
+ Deleted Model Routers cannot be used for
+ generation.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._delete(
+ path_template("/v1/routers/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+
+class AsyncRoutersResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncRoutersResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/runwayml/sdk-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncRoutersResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncRoutersResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/runwayml/sdk-python#with_streaming_response
+ """
+ return AsyncRoutersResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ *,
+ slug: str,
+ description: str | Omit = omit,
+ name: str | Omit = omit,
+ settings: router_create_params.Settings | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> RouterCreateResponse:
+ """
+ Create a Model Router configuration.
+
+ Args:
+ slug: Immutable slug used to reference this Model Router in generation requests (for
+ example, production-video). Unique within the API project. The UUID id remains
+ the canonical management identifier.
+
+ description: An optional Model Router description.
+
+ name: Optional human-readable display name for this router. Defaults to the slug when
+ omitted.
+
+ settings: Model Router routing preferences. Defaults to cost-optimized allow-all when
+ omitted. Modality is implied by the generate endpoint used with this Model
+ Router.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._post(
+ "/v1/routers",
+ body=await async_maybe_transform(
+ {
+ "slug": slug,
+ "description": description,
+ "name": name,
+ "settings": settings,
+ },
+ router_create_params.RouterCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=RouterCreateResponse,
+ )
+
+ async def retrieve(
+ self,
+ id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> RouterRetrieveResponse:
+ """
+ Retrieve a Model Router configuration by ID.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return await self._get(
+ path_template("/v1/routers/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=RouterRetrieveResponse,
+ )
+
+ async def update(
+ self,
+ id: str,
+ *,
+ description: Optional[str] | Omit = omit,
+ name: str | Omit = omit,
+ settings: router_update_params.Settings | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> RouterUpdateResponse:
+ """Update a Model Router configuration.
+
+ Settings changes append a new version; name
+ and description updates do not. Settings are merged with the current snapshot —
+ omitted fields keep their existing values.
+
+ Args:
+ name: Display name. The slug is immutable and cannot be changed after creation.
+
+ settings: Nested merge: omitted settings fields keep their current values. When models is
+ present, omitted models.mode or models.ids are preserved (sending only
+ optimizeFor does not clear the model allowlist or credit ceiling).
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return await self._patch(
+ path_template("/v1/routers/{id}", id=id),
+ body=await async_maybe_transform(
+ {
+ "description": description,
+ "name": name,
+ "settings": settings,
+ },
+ router_update_params.RouterUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=RouterUpdateResponse,
+ )
+
+ def list(
+ self,
+ *,
+ limit: int,
+ cursor: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> AsyncPaginator[RouterListResponse, AsyncCursorPage[RouterListResponse]]:
+ """
+ List Model Router configurations for the authenticated organization with
+ cursor-based pagination.
+
+ Args:
+ limit: The maximum number of items to return per page.
+
+ cursor: Cursor from a previous response for fetching the next page of results.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get_api_list(
+ "/v1/routers",
+ page=AsyncCursorPage[RouterListResponse],
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "limit": limit,
+ "cursor": cursor,
+ },
+ router_list_params.RouterListParams,
+ ),
+ ),
+ model=RouterListResponse,
+ )
+
+ async def delete(
+ self,
+ id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """Delete a Model Router configuration.
+
+ Deleted Model Routers cannot be used for
+ generation.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._delete(
+ path_template("/v1/routers/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+
+class RoutersResourceWithRawResponse:
+ def __init__(self, routers: RoutersResource) -> None:
+ self._routers = routers
+
+ self.create = to_raw_response_wrapper(
+ routers.create,
+ )
+ self.retrieve = to_raw_response_wrapper(
+ routers.retrieve,
+ )
+ self.update = to_raw_response_wrapper(
+ routers.update,
+ )
+ self.list = to_raw_response_wrapper(
+ routers.list,
+ )
+ self.delete = to_raw_response_wrapper(
+ routers.delete,
+ )
+
+
+class AsyncRoutersResourceWithRawResponse:
+ def __init__(self, routers: AsyncRoutersResource) -> None:
+ self._routers = routers
+
+ self.create = async_to_raw_response_wrapper(
+ routers.create,
+ )
+ self.retrieve = async_to_raw_response_wrapper(
+ routers.retrieve,
+ )
+ self.update = async_to_raw_response_wrapper(
+ routers.update,
+ )
+ self.list = async_to_raw_response_wrapper(
+ routers.list,
+ )
+ self.delete = async_to_raw_response_wrapper(
+ routers.delete,
+ )
+
+
+class RoutersResourceWithStreamingResponse:
+ def __init__(self, routers: RoutersResource) -> None:
+ self._routers = routers
+
+ self.create = to_streamed_response_wrapper(
+ routers.create,
+ )
+ self.retrieve = to_streamed_response_wrapper(
+ routers.retrieve,
+ )
+ self.update = to_streamed_response_wrapper(
+ routers.update,
+ )
+ self.list = to_streamed_response_wrapper(
+ routers.list,
+ )
+ self.delete = to_streamed_response_wrapper(
+ routers.delete,
+ )
+
+
+class AsyncRoutersResourceWithStreamingResponse:
+ def __init__(self, routers: AsyncRoutersResource) -> None:
+ self._routers = routers
+
+ self.create = async_to_streamed_response_wrapper(
+ routers.create,
+ )
+ self.retrieve = async_to_streamed_response_wrapper(
+ routers.retrieve,
+ )
+ self.update = async_to_streamed_response_wrapper(
+ routers.update,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ routers.list,
+ )
+ self.delete = async_to_streamed_response_wrapper(
+ routers.delete,
+ )
diff --git a/src/runwayml/resources/text_to_image.py b/src/runwayml/resources/text_to_image.py
index 3fa9199..e76d9be 100644
--- a/src/runwayml/resources/text_to_image.py
+++ b/src/runwayml/resources/text_to_image.py
@@ -456,6 +456,7 @@ def create(
"auto_1k",
"auto_2k",
],
+ grounding: bool | Omit = omit,
output_count: int | Omit = omit,
output_format: Literal["png", "jpeg"] | Omit = omit,
reference_images: Iterable[text_to_image_create_params.Seedream5ProReferenceImage] | Omit = omit,
@@ -476,6 +477,9 @@ def create(
`auto_1k` or `auto_2k` to let the model pick aspect ratio at a fixed resolution
tier.
+ grounding: When true, enable live web search so the model can use current brand, trend, or
+ event context. Default false for deterministic output.
+
output_count: The number of images to generate. Increasing this number will affect the number
of credits consumed by the generation.
@@ -819,6 +823,7 @@ def create(
background: Literal["opaque", "auto"] | Omit = omit,
output_count: int | Literal[1, 4] | Omit = omit,
quality: Literal["low", "medium", "high", "auto"] | Omit = omit,
+ grounding: bool | Omit = omit,
output_format: Literal["png", "jpeg"] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
@@ -840,6 +845,7 @@ def create(
"background": background,
"output_count": output_count,
"quality": quality,
+ "grounding": grounding,
"output_format": output_format,
},
text_to_image_create_params.TextToImageCreateParams,
@@ -1277,6 +1283,7 @@ async def create(
"auto_1k",
"auto_2k",
],
+ grounding: bool | Omit = omit,
output_count: int | Omit = omit,
output_format: Literal["png", "jpeg"] | Omit = omit,
reference_images: Iterable[text_to_image_create_params.Seedream5ProReferenceImage] | Omit = omit,
@@ -1297,6 +1304,9 @@ async def create(
`auto_1k` or `auto_2k` to let the model pick aspect ratio at a fixed resolution
tier.
+ grounding: When true, enable live web search so the model can use current brand, trend, or
+ event context. Default false for deterministic output.
+
output_count: The number of images to generate. Increasing this number will affect the number
of credits consumed by the generation.
@@ -1640,6 +1650,7 @@ async def create(
background: Literal["opaque", "auto"] | Omit = omit,
output_count: int | Literal[1, 4] | Omit = omit,
quality: Literal["low", "medium", "high", "auto"] | Omit = omit,
+ grounding: bool | Omit = omit,
output_format: Literal["png", "jpeg"] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
@@ -1661,6 +1672,7 @@ async def create(
"background": background,
"output_count": output_count,
"quality": quality,
+ "grounding": grounding,
"output_format": output_format,
},
text_to_image_create_params.TextToImageCreateParams,
diff --git a/src/runwayml/types/__init__.py b/src/runwayml/types/__init__.py
index 05d793a..b9b2414 100644
--- a/src/runwayml/types/__init__.py
+++ b/src/runwayml/types/__init__.py
@@ -4,6 +4,7 @@
from .voice_list_params import VoiceListParams as VoiceListParams
from .avatar_list_params import AvatarListParams as AvatarListParams
+from .router_list_params import RouterListParams as RouterListParams
from .voice_create_params import VoiceCreateParams as VoiceCreateParams
from .voice_list_response import VoiceListResponse as VoiceListResponse
from .voice_update_params import VoiceUpdateParams as VoiceUpdateParams
@@ -12,6 +13,9 @@
from .avatar_list_response import AvatarListResponse as AvatarListResponse
from .avatar_update_params import AvatarUpdateParams as AvatarUpdateParams
from .document_list_params import DocumentListParams as DocumentListParams
+from .router_create_params import RouterCreateParams as RouterCreateParams
+from .router_list_response import RouterListResponse as RouterListResponse
+from .router_update_params import RouterUpdateParams as RouterUpdateParams
from .voice_preview_params import VoicePreviewParams as VoicePreviewParams
from .voice_create_response import VoiceCreateResponse as VoiceCreateResponse
from .voice_update_response import VoiceUpdateResponse as VoiceUpdateResponse
@@ -21,6 +25,8 @@
from .document_create_params import DocumentCreateParams as DocumentCreateParams
from .document_list_response import DocumentListResponse as DocumentListResponse
from .document_update_params import DocumentUpdateParams as DocumentUpdateParams
+from .router_create_response import RouterCreateResponse as RouterCreateResponse
+from .router_update_response import RouterUpdateResponse as RouterUpdateResponse
from .task_retrieve_response import TaskRetrieveResponse as TaskRetrieveResponse
from .voice_preview_response import VoicePreviewResponse as VoicePreviewResponse
from .workflow_list_response import WorkflowListResponse as WorkflowListResponse
@@ -29,6 +35,7 @@
from .avatar_retrieve_response import AvatarRetrieveResponse as AvatarRetrieveResponse
from .document_create_response import DocumentCreateResponse as DocumentCreateResponse
from .recipe_product_ad_params import RecipeProductAdParams as RecipeProductAdParams
+from .router_retrieve_response import RouterRetrieveResponse as RouterRetrieveResponse
from .avatar_get_usage_response import AvatarGetUsageResponse as AvatarGetUsageResponse
from .recipe_product_ugc_params import RecipeProductUgcParams as RecipeProductUgcParams
from .avatar_video_create_params import AvatarVideoCreateParams as AvatarVideoCreateParams
diff --git a/src/runwayml/types/generate/__init__.py b/src/runwayml/types/generate/__init__.py
new file mode 100644
index 0000000..809aa0d
--- /dev/null
+++ b/src/runwayml/types/generate/__init__.py
@@ -0,0 +1,6 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .video_create_params import VideoCreateParams as VideoCreateParams
+from .video_create_response import VideoCreateResponse as VideoCreateResponse
diff --git a/src/runwayml/types/generate/video_create_params.py b/src/runwayml/types/generate/video_create_params.py
new file mode 100644
index 0000000..e2b8ef2
--- /dev/null
+++ b/src/runwayml/types/generate/video_create_params.py
@@ -0,0 +1,182 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union, Iterable
+from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = [
+ "VideoCreateParams",
+ "Input",
+ "InputContentModeration",
+ "InputKeyframe",
+ "InputKeyframeUnionMember0",
+ "InputKeyframeUnionMember0Range",
+ "InputKeyframeUnionMember1",
+ "InputKeyframeUnionMember1Range",
+ "InputReferenceAudio",
+ "InputReferenceImage",
+ "InputReferenceVideo",
+]
+
+
+class VideoCreateParams(TypedDict, total=False):
+ config_id: Required[Annotated[str, PropertyInfo(alias="configId")]]
+ """The slug of a saved Model Router config to route this request with."""
+
+ input: Required[Input]
+ """Model-agnostic video generation input.
+
+ Fields are optional; the router selects a model and maps these options to it.
+ """
+
+ dry_run: Annotated[bool, PropertyInfo(alias="dryRun")]
+ """
+ When true, run the full routing pipeline and return the decision and estimated
+ cost without generating. No task is created, nothing is billed, and no asset is
+ produced.
+ """
+
+
+class InputContentModeration(TypedDict, total=False):
+ """Settings that affect the behavior of the content moderation system."""
+
+ public_figure_threshold: Annotated[Literal["auto", "low"], PropertyInfo(alias="publicFigureThreshold")]
+ """
+ When set to `low`, the content moderation system will be less strict about
+ preventing generations that include recognizable public figures.
+ """
+
+
+class InputKeyframeUnionMember0Range(TypedDict, total=False):
+ end_seconds: Required[int]
+
+ start_seconds: Required[int]
+
+
+class InputKeyframeUnionMember0(TypedDict, total=False):
+ seconds: Required[float]
+
+ uri: Required[str]
+ """A HTTPS URL."""
+
+ range: InputKeyframeUnionMember0Range
+
+
+class InputKeyframeUnionMember1Range(TypedDict, total=False):
+ end_seconds: Required[int]
+
+ start_seconds: Required[int]
+
+
+class InputKeyframeUnionMember1(TypedDict, total=False):
+ at: Required[float]
+
+ uri: Required[str]
+ """A HTTPS URL."""
+
+ range: InputKeyframeUnionMember1Range
+
+
+InputKeyframe: TypeAlias = Union[InputKeyframeUnionMember0, InputKeyframeUnionMember1]
+
+
+class InputReferenceAudio(TypedDict, total=False):
+ uri: Required[str]
+ """A HTTPS URL."""
+
+
+class InputReferenceImage(TypedDict, total=False):
+ role: Required[Literal["first", "last", "reference"]]
+ """How the image is used.
+
+ `first` is the starting frame; `last` is an end frame; `reference` is additional
+ image context.
+ """
+
+ uri: Required[str]
+ """A HTTPS URL."""
+
+
+class InputReferenceVideo(TypedDict, total=False):
+ role: Required[Literal["source", "reference"]]
+ """How the video is used.
+
+ `source` is the primary video-to-video input; `reference` is additional video
+ context.
+ """
+
+ uri: Required[str]
+ """A HTTPS URL."""
+
+
+class Input(TypedDict, total=False):
+ """Model-agnostic video generation input.
+
+ Fields are optional; the router selects a model and maps these options to it.
+ """
+
+ aspect_ratio: Annotated[Literal["16:9", "9:16", "1:1", "4:3", "3:4", "21:9"], PropertyInfo(alias="aspectRatio")]
+ """Desired aspect ratio.
+
+ Models that do not support the requested aspect are excluded.
+ """
+
+ audio: bool
+ """Whether to generate native audio with the video.
+
+ When true, only models that output audio remain eligible; when false, silent
+ models and models with an audio toggle remain eligible (always-on native-audio
+ models are excluded). When omitted, the selected model’s default applies.
+ """
+
+ content_moderation: Annotated[InputContentModeration, PropertyInfo(alias="contentModeration")]
+ """Settings that affect the behavior of the content moderation system."""
+
+ duration: int
+ """Desired duration of the output video, in seconds.
+
+ Unsupported values exclude models; with a source video, V2V duration support
+ applies.
+ """
+
+ keyframes: Iterable[InputKeyframe]
+ """Timed guidance images for video restyle.
+
+ Requires a source video; unsupported models are excluded.
+ """
+
+ negative_prompt: Annotated[str, PropertyInfo(alias="negativePrompt")]
+ """A text description of what to avoid in the output."""
+
+ prompt_text: Annotated[str, PropertyInfo(alias="promptText")]
+ """A text prompt describing the desired video."""
+
+ reference_audio: Annotated[Iterable[InputReferenceAudio], PropertyInfo(alias="referenceAudio")]
+ """Optional audio inputs for the generation."""
+
+ reference_images: Annotated[Iterable[InputReferenceImage], PropertyInfo(alias="referenceImages")]
+ """Optional image inputs.
+
+ Each entry requires a `role`. At most one `first` and one `last` are allowed;
+ multiple `reference` images are allowed.
+ """
+
+ reference_videos: Annotated[Iterable[InputReferenceVideo], PropertyInfo(alias="referenceVideos")]
+ """Optional video inputs.
+
+ Each entry requires a `role`. Use `source` for video-to-video; use `reference`
+ for additional context videos (only models that support them remain eligible).
+ At most one `source` is allowed.
+ """
+
+ resolution: Literal["480p", "720p", "1080p", "4k"]
+ """Desired output resolution tier.
+
+ Models that do not support the requested tier are excluded.
+ """
+
+ seed: int
+ """A seed for reproducible generation. Random if omitted."""
diff --git a/src/runwayml/types/generate/video_create_response.py b/src/runwayml/types/generate/video_create_response.py
new file mode 100644
index 0000000..24fd33b
--- /dev/null
+++ b/src/runwayml/types/generate/video_create_response.py
@@ -0,0 +1,179 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Union, Optional
+from typing_extensions import Literal, Annotated, TypeAlias
+
+from pydantic import Field as FieldInfo
+
+from ..._utils import PropertyInfo
+from ..._models import BaseModel
+
+__all__ = [
+ "VideoCreateResponse",
+ "RoutedVideoTaskCreated",
+ "RoutedVideoTaskCreatedRouting",
+ "RoutedVideoTaskCreatedRoutingEstimatedCost",
+ "RoutedVideoTaskCreatedRoutingResolvedInput",
+ "RoutedVideoTaskCreatedRoutingResolvedSettings",
+ "RoutedVideoDryRun",
+ "RoutedVideoDryRunRouting",
+ "RoutedVideoDryRunRoutingEstimatedCost",
+ "RoutedVideoDryRunRoutingResolvedInput",
+ "RoutedVideoDryRunRoutingResolvedSettings",
+]
+
+
+class RoutedVideoTaskCreatedRoutingEstimatedCost(BaseModel):
+ """Estimated cost, computed against current pricing."""
+
+ credits: float
+ """Estimated cost of the generation in credits."""
+
+
+class RoutedVideoTaskCreatedRoutingResolvedInput(BaseModel):
+ """Request-side defaults resolved for the routing response.
+
+ Not necessarily identical to prepared model options.
+ """
+
+ duration: float
+ """Duration in seconds used for routing display (request value or router default)."""
+
+ ratio: str
+ """Concrete output ratio derived from aspectRatio (e.g.
+
+ "1280:720"), or the router default.
+ """
+
+ resolution: str
+ """Resolution tier from the request, or the router default when omitted."""
+
+
+class RoutedVideoTaskCreatedRoutingResolvedSettings(BaseModel):
+ """The resolved config settings the router used for this request."""
+
+ optimize_for: Literal["cost", "latency", "quality"] = FieldInfo(alias="optimizeFor")
+ """
+ The single optimization preference the config selected, used as the soft
+ weighting when scoring eligible models.
+ """
+
+ price_ceiling: Optional[float] = FieldInfo(alias="priceCeiling", default=None)
+ """
+ The applied maximum credits per generation for this request’s modality, or null
+ if the config sets no ceiling.
+ """
+
+
+class RoutedVideoTaskCreatedRouting(BaseModel):
+ """Metadata describing which model the router selected and why."""
+
+ config_id: str = FieldInfo(alias="configId")
+ """The slug of the router config that was applied to this request."""
+
+ estimated_cost: RoutedVideoTaskCreatedRoutingEstimatedCost = FieldInfo(alias="estimatedCost")
+ """Estimated cost, computed against current pricing."""
+
+ model: str
+ """The public name of the model the router selected."""
+
+ provider: str
+ """The provider of the selected model."""
+
+ resolved_input: RoutedVideoTaskCreatedRoutingResolvedInput = FieldInfo(alias="resolvedInput")
+ """Request-side defaults resolved for the routing response.
+
+ Not necessarily identical to prepared model options.
+ """
+
+ resolved_settings: RoutedVideoTaskCreatedRoutingResolvedSettings = FieldInfo(alias="resolvedSettings")
+ """The resolved config settings the router used for this request."""
+
+
+class RoutedVideoTaskCreated(BaseModel):
+ id: str
+ """The ID of the created task. Poll GET /v1/tasks/:id for the result."""
+
+ dry_run: Literal[False] = FieldInfo(alias="dryRun")
+
+ routing: RoutedVideoTaskCreatedRouting
+ """Metadata describing which model the router selected and why."""
+
+
+class RoutedVideoDryRunRoutingEstimatedCost(BaseModel):
+ """Estimated cost, computed against current pricing."""
+
+ credits: float
+ """Estimated cost of the generation in credits."""
+
+
+class RoutedVideoDryRunRoutingResolvedInput(BaseModel):
+ """Request-side defaults resolved for the routing response.
+
+ Not necessarily identical to prepared model options.
+ """
+
+ duration: float
+ """Duration in seconds used for routing display (request value or router default)."""
+
+ ratio: str
+ """Concrete output ratio derived from aspectRatio (e.g.
+
+ "1280:720"), or the router default.
+ """
+
+ resolution: str
+ """Resolution tier from the request, or the router default when omitted."""
+
+
+class RoutedVideoDryRunRoutingResolvedSettings(BaseModel):
+ """The resolved config settings the router used for this request."""
+
+ optimize_for: Literal["cost", "latency", "quality"] = FieldInfo(alias="optimizeFor")
+ """
+ The single optimization preference the config selected, used as the soft
+ weighting when scoring eligible models.
+ """
+
+ price_ceiling: Optional[float] = FieldInfo(alias="priceCeiling", default=None)
+ """
+ The applied maximum credits per generation for this request’s modality, or null
+ if the config sets no ceiling.
+ """
+
+
+class RoutedVideoDryRunRouting(BaseModel):
+ """Metadata describing which model the router selected and why."""
+
+ config_id: str = FieldInfo(alias="configId")
+ """The slug of the router config that was applied to this request."""
+
+ estimated_cost: RoutedVideoDryRunRoutingEstimatedCost = FieldInfo(alias="estimatedCost")
+ """Estimated cost, computed against current pricing."""
+
+ model: str
+ """The public name of the model the router selected."""
+
+ provider: str
+ """The provider of the selected model."""
+
+ resolved_input: RoutedVideoDryRunRoutingResolvedInput = FieldInfo(alias="resolvedInput")
+ """Request-side defaults resolved for the routing response.
+
+ Not necessarily identical to prepared model options.
+ """
+
+ resolved_settings: RoutedVideoDryRunRoutingResolvedSettings = FieldInfo(alias="resolvedSettings")
+ """The resolved config settings the router used for this request."""
+
+
+class RoutedVideoDryRun(BaseModel):
+ dry_run: Literal[True] = FieldInfo(alias="dryRun")
+
+ routing: RoutedVideoDryRunRouting
+ """Metadata describing which model the router selected and why."""
+
+
+VideoCreateResponse: TypeAlias = Annotated[
+ Union[RoutedVideoTaskCreated, RoutedVideoDryRun], PropertyInfo(discriminator="dry_run")
+]
diff --git a/src/runwayml/types/organization_retrieve_usage_response.py b/src/runwayml/types/organization_retrieve_usage_response.py
index fc617da..53971c4 100644
--- a/src/runwayml/types/organization_retrieve_usage_response.py
+++ b/src/runwayml/types/organization_retrieve_usage_response.py
@@ -34,6 +34,7 @@ class ResultUsedCredit(BaseModel):
"gemini_image3_pro",
"gemini_image3.1_flash",
"seedream5_pro",
+ "seedream5_lite",
"gemini_omni_flash",
"eleven_multilingual_v2",
"seed_audio",
@@ -101,6 +102,7 @@ class OrganizationRetrieveUsageResponse(BaseModel):
"gemini_image3_pro",
"gemini_image3.1_flash",
"seedream5_pro",
+ "seedream5_lite",
"gemini_omni_flash",
"eleven_multilingual_v2",
"seed_audio",
diff --git a/src/runwayml/types/router_create_params.py b/src/runwayml/types/router_create_params.py
new file mode 100644
index 0000000..cc393ae
--- /dev/null
+++ b/src/runwayml/types/router_create_params.py
@@ -0,0 +1,90 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, Annotated, TypedDict
+
+from .._types import SequenceNotStr
+from .._utils import PropertyInfo
+
+__all__ = ["RouterCreateParams", "Settings", "SettingsMaxCreditsPerGeneration", "SettingsModels"]
+
+
+class RouterCreateParams(TypedDict, total=False):
+ slug: Required[str]
+ """
+ Immutable slug used to reference this Model Router in generation requests (for
+ example, production-video). Unique within the API project. The UUID id remains
+ the canonical management identifier.
+ """
+
+ description: str
+ """An optional Model Router description."""
+
+ name: str
+ """Optional human-readable display name for this router.
+
+ Defaults to the slug when omitted.
+ """
+
+ settings: Settings
+ """Model Router routing preferences.
+
+ Defaults to cost-optimized allow-all when omitted. Modality is implied by the
+ generate endpoint used with this Model Router.
+ """
+
+
+class SettingsMaxCreditsPerGeneration(TypedDict, total=False):
+ """Optional per-modality hard caps on credits for one generation.
+
+ Models whose estimated cost for that modality exceeds the cap are excluded.
+ """
+
+ audio: int
+
+ image: int
+
+ video: int
+
+
+class SettingsModels(TypedDict, total=False):
+ """
+ When mode is allow_new_except, ids are excluded; when allowlist_only, ids are the only allowed values. Each id must be a known public video model name (unknown ids are rejected on create/update).
+ """
+
+ ids: Required[SequenceNotStr[str]]
+
+ mode: Required[Literal["allow_new_except", "allowlist_only"]]
+
+
+class Settings(TypedDict, total=False):
+ """Model Router routing preferences.
+
+ Defaults to cost-optimized allow-all when omitted. Modality is implied by the generate endpoint used with this Model Router.
+ """
+
+ max_credits_per_generation: Annotated[
+ SettingsMaxCreditsPerGeneration, PropertyInfo(alias="maxCreditsPerGeneration")
+ ]
+ """Optional per-modality hard caps on credits for one generation.
+
+ Models whose estimated cost for that modality exceeds the cap are excluded.
+ """
+
+ models: SettingsModels
+ """
+ When mode is allow_new_except, ids are excluded; when allowlist_only, ids are
+ the only allowed values. Each id must be a known public video model name
+ (unknown ids are rejected on create/update).
+ """
+
+ optimize_for: Annotated[Literal["cost", "latency", "quality"], PropertyInfo(alias="optimizeFor")]
+ """Soft preference among eligible models: cost, latency, or quality."""
+
+ schema_version: Annotated[Literal[1], PropertyInfo(alias="schemaVersion")]
+ """Settings JSON schema version.
+
+ Omit on write to use the current version; responses and stored snapshots always
+ include it.
+ """
diff --git a/src/runwayml/types/router_create_response.py b/src/runwayml/types/router_create_response.py
new file mode 100644
index 0000000..75c7a94
--- /dev/null
+++ b/src/runwayml/types/router_create_response.py
@@ -0,0 +1,97 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+
+__all__ = ["RouterCreateResponse", "Settings", "SettingsMaxCreditsPerGeneration", "SettingsModels"]
+
+
+class SettingsMaxCreditsPerGeneration(BaseModel):
+ """Optional per-modality hard caps on credits for one generation.
+
+ Models whose estimated cost for that modality exceeds the cap are excluded.
+ """
+
+ audio: Optional[int] = None
+
+ image: Optional[int] = None
+
+ video: Optional[int] = None
+
+
+class SettingsModels(BaseModel):
+ """
+ When mode is allow_new_except, ids are excluded; when allowlist_only, ids are the only allowed values. Each id must be a known public video model name (unknown ids are rejected on create/update).
+ """
+
+ ids: List[str]
+
+ mode: Literal["allow_new_except", "allowlist_only"]
+
+
+class Settings(BaseModel):
+ schema_version: Literal[1] = FieldInfo(alias="schemaVersion")
+ """Settings JSON schema version used when this snapshot was written."""
+
+ max_credits_per_generation: Optional[SettingsMaxCreditsPerGeneration] = FieldInfo(
+ alias="maxCreditsPerGeneration", default=None
+ )
+ """Optional per-modality hard caps on credits for one generation.
+
+ Models whose estimated cost for that modality exceeds the cap are excluded.
+ """
+
+ models: Optional[SettingsModels] = None
+ """
+ When mode is allow_new_except, ids are excluded; when allowlist_only, ids are
+ the only allowed values. Each id must be a known public video model name
+ (unknown ids are rejected on create/update).
+ """
+
+ optimize_for: Optional[Literal["cost", "latency", "quality"]] = FieldInfo(alias="optimizeFor", default=None)
+ """Soft preference among eligible models: cost, latency, or quality."""
+
+
+class RouterCreateResponse(BaseModel):
+ id: str
+ """The Model Router's primary key ID (UUID).
+
+ Use it to manage this router via the API; use the slug to reference the router
+ in generation requests.
+ """
+
+ created_at: datetime = FieldInfo(alias="createdAt")
+ """When the Model Router was created."""
+
+ description: Optional[str] = None
+ """An optional Model Router description."""
+
+ name: str
+ """Human-friendly Model Router display name shown in the dev portal.
+
+ Mutable, and not used to reference the router in requests.
+ """
+
+ settings: Settings
+
+ slug: str
+ """
+ Immutable slug used to reference this Model Router in generation requests (for
+ example, production-video). Unique within the API project. The UUID id remains
+ the canonical management identifier.
+ """
+
+ updated_at: datetime = FieldInfo(alias="updatedAt")
+ """When the Model Router was last updated."""
+
+ version: int
+ """Current settings version.
+
+ Increments when settings change; name and description updates do not create a
+ new version.
+ """
diff --git a/src/runwayml/types/router_list_params.py b/src/runwayml/types/router_list_params.py
new file mode 100644
index 0000000..872adbc
--- /dev/null
+++ b/src/runwayml/types/router_list_params.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["RouterListParams"]
+
+
+class RouterListParams(TypedDict, total=False):
+ limit: Required[int]
+ """The maximum number of items to return per page."""
+
+ cursor: str
+ """Cursor from a previous response for fetching the next page of results."""
diff --git a/src/runwayml/types/router_list_response.py b/src/runwayml/types/router_list_response.py
new file mode 100644
index 0000000..c465ef1
--- /dev/null
+++ b/src/runwayml/types/router_list_response.py
@@ -0,0 +1,99 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+
+__all__ = ["RouterListResponse", "Settings", "SettingsMaxCreditsPerGeneration", "SettingsModels"]
+
+
+class SettingsMaxCreditsPerGeneration(BaseModel):
+ """Optional per-modality hard caps on credits for one generation.
+
+ Models whose estimated cost for that modality exceeds the cap are excluded.
+ """
+
+ audio: Optional[int] = None
+
+ image: Optional[int] = None
+
+ video: Optional[int] = None
+
+
+class SettingsModels(BaseModel):
+ """
+ When mode is allow_new_except, ids are excluded; when allowlist_only, ids are the only allowed values. Each id must be a known public video model name (unknown ids are rejected on create/update).
+ """
+
+ ids: List[str]
+
+ mode: Literal["allow_new_except", "allowlist_only"]
+
+
+class Settings(BaseModel):
+ schema_version: Literal[1] = FieldInfo(alias="schemaVersion")
+ """Settings JSON schema version used when this snapshot was written."""
+
+ max_credits_per_generation: Optional[SettingsMaxCreditsPerGeneration] = FieldInfo(
+ alias="maxCreditsPerGeneration", default=None
+ )
+ """Optional per-modality hard caps on credits for one generation.
+
+ Models whose estimated cost for that modality exceeds the cap are excluded.
+ """
+
+ models: Optional[SettingsModels] = None
+ """
+ When mode is allow_new_except, ids are excluded; when allowlist_only, ids are
+ the only allowed values. Each id must be a known public video model name
+ (unknown ids are rejected on create/update).
+ """
+
+ optimize_for: Optional[Literal["cost", "latency", "quality"]] = FieldInfo(alias="optimizeFor", default=None)
+ """Soft preference among eligible models: cost, latency, or quality."""
+
+
+class RouterListResponse(BaseModel):
+ """A named Model Router configuration."""
+
+ id: str
+ """The Model Router's primary key ID (UUID).
+
+ Use it to manage this router via the API; use the slug to reference the router
+ in generation requests.
+ """
+
+ created_at: datetime = FieldInfo(alias="createdAt")
+ """When the Model Router was created."""
+
+ description: Optional[str] = None
+ """An optional Model Router description."""
+
+ name: str
+ """Human-friendly Model Router display name shown in the dev portal.
+
+ Mutable, and not used to reference the router in requests.
+ """
+
+ settings: Settings
+
+ slug: str
+ """
+ Immutable slug used to reference this Model Router in generation requests (for
+ example, production-video). Unique within the API project. The UUID id remains
+ the canonical management identifier.
+ """
+
+ updated_at: datetime = FieldInfo(alias="updatedAt")
+ """When the Model Router was last updated."""
+
+ version: int
+ """Current settings version.
+
+ Increments when settings change; name and description updates do not create a
+ new version.
+ """
diff --git a/src/runwayml/types/router_retrieve_response.py b/src/runwayml/types/router_retrieve_response.py
new file mode 100644
index 0000000..84882b0
--- /dev/null
+++ b/src/runwayml/types/router_retrieve_response.py
@@ -0,0 +1,97 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+
+__all__ = ["RouterRetrieveResponse", "Settings", "SettingsMaxCreditsPerGeneration", "SettingsModels"]
+
+
+class SettingsMaxCreditsPerGeneration(BaseModel):
+ """Optional per-modality hard caps on credits for one generation.
+
+ Models whose estimated cost for that modality exceeds the cap are excluded.
+ """
+
+ audio: Optional[int] = None
+
+ image: Optional[int] = None
+
+ video: Optional[int] = None
+
+
+class SettingsModels(BaseModel):
+ """
+ When mode is allow_new_except, ids are excluded; when allowlist_only, ids are the only allowed values. Each id must be a known public video model name (unknown ids are rejected on create/update).
+ """
+
+ ids: List[str]
+
+ mode: Literal["allow_new_except", "allowlist_only"]
+
+
+class Settings(BaseModel):
+ schema_version: Literal[1] = FieldInfo(alias="schemaVersion")
+ """Settings JSON schema version used when this snapshot was written."""
+
+ max_credits_per_generation: Optional[SettingsMaxCreditsPerGeneration] = FieldInfo(
+ alias="maxCreditsPerGeneration", default=None
+ )
+ """Optional per-modality hard caps on credits for one generation.
+
+ Models whose estimated cost for that modality exceeds the cap are excluded.
+ """
+
+ models: Optional[SettingsModels] = None
+ """
+ When mode is allow_new_except, ids are excluded; when allowlist_only, ids are
+ the only allowed values. Each id must be a known public video model name
+ (unknown ids are rejected on create/update).
+ """
+
+ optimize_for: Optional[Literal["cost", "latency", "quality"]] = FieldInfo(alias="optimizeFor", default=None)
+ """Soft preference among eligible models: cost, latency, or quality."""
+
+
+class RouterRetrieveResponse(BaseModel):
+ id: str
+ """The Model Router's primary key ID (UUID).
+
+ Use it to manage this router via the API; use the slug to reference the router
+ in generation requests.
+ """
+
+ created_at: datetime = FieldInfo(alias="createdAt")
+ """When the Model Router was created."""
+
+ description: Optional[str] = None
+ """An optional Model Router description."""
+
+ name: str
+ """Human-friendly Model Router display name shown in the dev portal.
+
+ Mutable, and not used to reference the router in requests.
+ """
+
+ settings: Settings
+
+ slug: str
+ """
+ Immutable slug used to reference this Model Router in generation requests (for
+ example, production-video). Unique within the API project. The UUID id remains
+ the canonical management identifier.
+ """
+
+ updated_at: datetime = FieldInfo(alias="updatedAt")
+ """When the Model Router was last updated."""
+
+ version: int
+ """Current settings version.
+
+ Increments when settings change; name and description updates do not create a
+ new version.
+ """
diff --git a/src/runwayml/types/router_update_params.py b/src/runwayml/types/router_update_params.py
new file mode 100644
index 0000000..c658374
--- /dev/null
+++ b/src/runwayml/types/router_update_params.py
@@ -0,0 +1,80 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Literal, Required, Annotated, TypedDict
+
+from .._types import SequenceNotStr
+from .._utils import PropertyInfo
+
+__all__ = ["RouterUpdateParams", "Settings", "SettingsMaxCreditsPerGeneration", "SettingsModels"]
+
+
+class RouterUpdateParams(TypedDict, total=False):
+ description: Optional[str]
+
+ name: str
+ """Display name. The slug is immutable and cannot be changed after creation."""
+
+ settings: Settings
+ """Nested merge: omitted settings fields keep their current values.
+
+ When models is present, omitted models.mode or models.ids are preserved (sending
+ only optimizeFor does not clear the model allowlist or credit ceiling).
+ """
+
+
+class SettingsMaxCreditsPerGeneration(TypedDict, total=False):
+ """Optional per-modality hard caps on credits for one generation.
+
+ Models whose estimated cost for that modality exceeds the cap are excluded.
+ """
+
+ audio: int
+
+ image: int
+
+ video: int
+
+
+class SettingsModels(TypedDict, total=False):
+ """
+ When mode is allow_new_except, ids are excluded; when allowlist_only, ids are the only allowed values. Each id must be a known public video model name (unknown ids are rejected on create/update).
+ """
+
+ ids: Required[SequenceNotStr[str]]
+
+ mode: Required[Literal["allow_new_except", "allowlist_only"]]
+
+
+class Settings(TypedDict, total=False):
+ """Nested merge: omitted settings fields keep their current values.
+
+ When models is present, omitted models.mode or models.ids are preserved (sending only optimizeFor does not clear the model allowlist or credit ceiling).
+ """
+
+ max_credits_per_generation: Annotated[
+ SettingsMaxCreditsPerGeneration, PropertyInfo(alias="maxCreditsPerGeneration")
+ ]
+ """Optional per-modality hard caps on credits for one generation.
+
+ Models whose estimated cost for that modality exceeds the cap are excluded.
+ """
+
+ models: SettingsModels
+ """
+ When mode is allow_new_except, ids are excluded; when allowlist_only, ids are
+ the only allowed values. Each id must be a known public video model name
+ (unknown ids are rejected on create/update).
+ """
+
+ optimize_for: Annotated[Literal["cost", "latency", "quality"], PropertyInfo(alias="optimizeFor")]
+ """Soft preference among eligible models: cost, latency, or quality."""
+
+ schema_version: Annotated[Literal[1], PropertyInfo(alias="schemaVersion")]
+ """Settings JSON schema version.
+
+ Omit on write to use the current version; responses and stored snapshots always
+ include it.
+ """
diff --git a/src/runwayml/types/router_update_response.py b/src/runwayml/types/router_update_response.py
new file mode 100644
index 0000000..01c607f
--- /dev/null
+++ b/src/runwayml/types/router_update_response.py
@@ -0,0 +1,97 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+
+__all__ = ["RouterUpdateResponse", "Settings", "SettingsMaxCreditsPerGeneration", "SettingsModels"]
+
+
+class SettingsMaxCreditsPerGeneration(BaseModel):
+ """Optional per-modality hard caps on credits for one generation.
+
+ Models whose estimated cost for that modality exceeds the cap are excluded.
+ """
+
+ audio: Optional[int] = None
+
+ image: Optional[int] = None
+
+ video: Optional[int] = None
+
+
+class SettingsModels(BaseModel):
+ """
+ When mode is allow_new_except, ids are excluded; when allowlist_only, ids are the only allowed values. Each id must be a known public video model name (unknown ids are rejected on create/update).
+ """
+
+ ids: List[str]
+
+ mode: Literal["allow_new_except", "allowlist_only"]
+
+
+class Settings(BaseModel):
+ schema_version: Literal[1] = FieldInfo(alias="schemaVersion")
+ """Settings JSON schema version used when this snapshot was written."""
+
+ max_credits_per_generation: Optional[SettingsMaxCreditsPerGeneration] = FieldInfo(
+ alias="maxCreditsPerGeneration", default=None
+ )
+ """Optional per-modality hard caps on credits for one generation.
+
+ Models whose estimated cost for that modality exceeds the cap are excluded.
+ """
+
+ models: Optional[SettingsModels] = None
+ """
+ When mode is allow_new_except, ids are excluded; when allowlist_only, ids are
+ the only allowed values. Each id must be a known public video model name
+ (unknown ids are rejected on create/update).
+ """
+
+ optimize_for: Optional[Literal["cost", "latency", "quality"]] = FieldInfo(alias="optimizeFor", default=None)
+ """Soft preference among eligible models: cost, latency, or quality."""
+
+
+class RouterUpdateResponse(BaseModel):
+ id: str
+ """The Model Router's primary key ID (UUID).
+
+ Use it to manage this router via the API; use the slug to reference the router
+ in generation requests.
+ """
+
+ created_at: datetime = FieldInfo(alias="createdAt")
+ """When the Model Router was created."""
+
+ description: Optional[str] = None
+ """An optional Model Router description."""
+
+ name: str
+ """Human-friendly Model Router display name shown in the dev portal.
+
+ Mutable, and not used to reference the router in requests.
+ """
+
+ settings: Settings
+
+ slug: str
+ """
+ Immutable slug used to reference this Model Router in generation requests (for
+ example, production-video). Unique within the API project. The UUID id remains
+ the canonical management identifier.
+ """
+
+ updated_at: datetime = FieldInfo(alias="updatedAt")
+ """When the Model Router was last updated."""
+
+ version: int
+ """Current settings version.
+
+ Increments when settings change; name and description updates do not create a
+ new version.
+ """
diff --git a/src/runwayml/types/text_to_image_create_params.py b/src/runwayml/types/text_to_image_create_params.py
index 39db1a3..f30167b 100644
--- a/src/runwayml/types/text_to_image_create_params.py
+++ b/src/runwayml/types/text_to_image_create_params.py
@@ -461,6 +461,12 @@ class Seedream5Pro(TypedDict, total=False):
resolution tier.
"""
+ grounding: bool
+ """
+ When true, enable live web search so the model can use current brand, trend, or
+ event context. Default false for deterministic output.
+ """
+
output_count: Annotated[int, PropertyInfo(alias="outputCount")]
"""The number of images to generate.
diff --git a/tests/api_resources/generate/__init__.py b/tests/api_resources/generate/__init__.py
new file mode 100644
index 0000000..fd8019a
--- /dev/null
+++ b/tests/api_resources/generate/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/generate/test_video.py b/tests/api_resources/generate/test_video.py
new file mode 100644
index 0000000..e9ec88d
--- /dev/null
+++ b/tests/api_resources/generate/test_video.py
@@ -0,0 +1,174 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from runwayml import RunwayML, AsyncRunwayML
+from tests.utils import assert_matches_type
+from runwayml.types.generate import VideoCreateResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestVideo:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_create(self, client: RunwayML) -> None:
+ video = client.generate.video.create(
+ config_id="n6_",
+ input={},
+ )
+ assert_matches_type(VideoCreateResponse, video, path=["response"])
+
+ @parametrize
+ def test_method_create_with_all_params(self, client: RunwayML) -> None:
+ video = client.generate.video.create(
+ config_id="n6_",
+ input={
+ "aspect_ratio": "16:9",
+ "audio": True,
+ "content_moderation": {"public_figure_threshold": "auto"},
+ "duration": 2,
+ "keyframes": [
+ {
+ "seconds": 0,
+ "uri": "https://example.com/file",
+ "range": {
+ "end_seconds": 1,
+ "start_seconds": 0,
+ },
+ }
+ ],
+ "negative_prompt": "negativePrompt",
+ "prompt_text": "x",
+ "reference_audio": [{"uri": "https://example.com/file"}],
+ "reference_images": [
+ {
+ "role": "first",
+ "uri": "https://example.com/file",
+ }
+ ],
+ "reference_videos": [
+ {
+ "role": "source",
+ "uri": "https://example.com/file",
+ }
+ ],
+ "resolution": "480p",
+ "seed": 0,
+ },
+ dry_run=True,
+ )
+ assert_matches_type(VideoCreateResponse, video, path=["response"])
+
+ @parametrize
+ def test_raw_response_create(self, client: RunwayML) -> None:
+ response = client.generate.video.with_raw_response.create(
+ config_id="n6_",
+ input={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ video = response.parse()
+ assert_matches_type(VideoCreateResponse, video, path=["response"])
+
+ @parametrize
+ def test_streaming_response_create(self, client: RunwayML) -> None:
+ with client.generate.video.with_streaming_response.create(
+ config_id="n6_",
+ input={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ video = response.parse()
+ assert_matches_type(VideoCreateResponse, video, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncVideo:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_create(self, async_client: AsyncRunwayML) -> None:
+ video = await async_client.generate.video.create(
+ config_id="n6_",
+ input={},
+ )
+ assert_matches_type(VideoCreateResponse, video, path=["response"])
+
+ @parametrize
+ async def test_method_create_with_all_params(self, async_client: AsyncRunwayML) -> None:
+ video = await async_client.generate.video.create(
+ config_id="n6_",
+ input={
+ "aspect_ratio": "16:9",
+ "audio": True,
+ "content_moderation": {"public_figure_threshold": "auto"},
+ "duration": 2,
+ "keyframes": [
+ {
+ "seconds": 0,
+ "uri": "https://example.com/file",
+ "range": {
+ "end_seconds": 1,
+ "start_seconds": 0,
+ },
+ }
+ ],
+ "negative_prompt": "negativePrompt",
+ "prompt_text": "x",
+ "reference_audio": [{"uri": "https://example.com/file"}],
+ "reference_images": [
+ {
+ "role": "first",
+ "uri": "https://example.com/file",
+ }
+ ],
+ "reference_videos": [
+ {
+ "role": "source",
+ "uri": "https://example.com/file",
+ }
+ ],
+ "resolution": "480p",
+ "seed": 0,
+ },
+ dry_run=True,
+ )
+ assert_matches_type(VideoCreateResponse, video, path=["response"])
+
+ @parametrize
+ async def test_raw_response_create(self, async_client: AsyncRunwayML) -> None:
+ response = await async_client.generate.video.with_raw_response.create(
+ config_id="n6_",
+ input={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ video = await response.parse()
+ assert_matches_type(VideoCreateResponse, video, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_create(self, async_client: AsyncRunwayML) -> None:
+ async with async_client.generate.video.with_streaming_response.create(
+ config_id="n6_",
+ input={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ video = await response.parse()
+ assert_matches_type(VideoCreateResponse, video, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_routers.py b/tests/api_resources/test_routers.py
new file mode 100644
index 0000000..961f0ef
--- /dev/null
+++ b/tests/api_resources/test_routers.py
@@ -0,0 +1,486 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from runwayml import RunwayML, AsyncRunwayML
+from tests.utils import assert_matches_type
+from runwayml.types import (
+ RouterListResponse,
+ RouterCreateResponse,
+ RouterUpdateResponse,
+ RouterRetrieveResponse,
+)
+from runwayml.pagination import SyncCursorPage, AsyncCursorPage
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestRouters:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_create(self, client: RunwayML) -> None:
+ router = client.routers.create(
+ slug="slug",
+ )
+ assert_matches_type(RouterCreateResponse, router, path=["response"])
+
+ @parametrize
+ def test_method_create_with_all_params(self, client: RunwayML) -> None:
+ router = client.routers.create(
+ slug="slug",
+ description="description",
+ name="x",
+ settings={
+ "max_credits_per_generation": {
+ "audio": 1,
+ "image": 1,
+ "video": 1,
+ },
+ "models": {
+ "ids": ["string"],
+ "mode": "allow_new_except",
+ },
+ "optimize_for": "cost",
+ "schema_version": 1,
+ },
+ )
+ assert_matches_type(RouterCreateResponse, router, path=["response"])
+
+ @parametrize
+ def test_raw_response_create(self, client: RunwayML) -> None:
+ response = client.routers.with_raw_response.create(
+ slug="slug",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ router = response.parse()
+ assert_matches_type(RouterCreateResponse, router, path=["response"])
+
+ @parametrize
+ def test_streaming_response_create(self, client: RunwayML) -> None:
+ with client.routers.with_streaming_response.create(
+ slug="slug",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ router = response.parse()
+ assert_matches_type(RouterCreateResponse, router, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_retrieve(self, client: RunwayML) -> None:
+ router = client.routers.retrieve(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+ assert_matches_type(RouterRetrieveResponse, router, path=["response"])
+
+ @parametrize
+ def test_raw_response_retrieve(self, client: RunwayML) -> None:
+ response = client.routers.with_raw_response.retrieve(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ router = response.parse()
+ assert_matches_type(RouterRetrieveResponse, router, path=["response"])
+
+ @parametrize
+ def test_streaming_response_retrieve(self, client: RunwayML) -> None:
+ with client.routers.with_streaming_response.retrieve(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ router = response.parse()
+ assert_matches_type(RouterRetrieveResponse, router, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_retrieve(self, client: RunwayML) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.routers.with_raw_response.retrieve(
+ "",
+ )
+
+ @parametrize
+ def test_method_update(self, client: RunwayML) -> None:
+ router = client.routers.update(
+ id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+ assert_matches_type(RouterUpdateResponse, router, path=["response"])
+
+ @parametrize
+ def test_method_update_with_all_params(self, client: RunwayML) -> None:
+ router = client.routers.update(
+ id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ description="description",
+ name="x",
+ settings={
+ "max_credits_per_generation": {
+ "audio": 1,
+ "image": 1,
+ "video": 1,
+ },
+ "models": {
+ "ids": ["string"],
+ "mode": "allow_new_except",
+ },
+ "optimize_for": "cost",
+ "schema_version": 1,
+ },
+ )
+ assert_matches_type(RouterUpdateResponse, router, path=["response"])
+
+ @parametrize
+ def test_raw_response_update(self, client: RunwayML) -> None:
+ response = client.routers.with_raw_response.update(
+ id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ router = response.parse()
+ assert_matches_type(RouterUpdateResponse, router, path=["response"])
+
+ @parametrize
+ def test_streaming_response_update(self, client: RunwayML) -> None:
+ with client.routers.with_streaming_response.update(
+ id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ router = response.parse()
+ assert_matches_type(RouterUpdateResponse, router, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_update(self, client: RunwayML) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.routers.with_raw_response.update(
+ id="",
+ )
+
+ @parametrize
+ def test_method_list(self, client: RunwayML) -> None:
+ router = client.routers.list(
+ limit=1,
+ )
+ assert_matches_type(SyncCursorPage[RouterListResponse], router, path=["response"])
+
+ @parametrize
+ def test_method_list_with_all_params(self, client: RunwayML) -> None:
+ router = client.routers.list(
+ limit=1,
+ cursor="x",
+ )
+ assert_matches_type(SyncCursorPage[RouterListResponse], router, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: RunwayML) -> None:
+ response = client.routers.with_raw_response.list(
+ limit=1,
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ router = response.parse()
+ assert_matches_type(SyncCursorPage[RouterListResponse], router, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: RunwayML) -> None:
+ with client.routers.with_streaming_response.list(
+ limit=1,
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ router = response.parse()
+ assert_matches_type(SyncCursorPage[RouterListResponse], router, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_delete(self, client: RunwayML) -> None:
+ router = client.routers.delete(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+ assert router is None
+
+ @parametrize
+ def test_raw_response_delete(self, client: RunwayML) -> None:
+ response = client.routers.with_raw_response.delete(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ router = response.parse()
+ assert router is None
+
+ @parametrize
+ def test_streaming_response_delete(self, client: RunwayML) -> None:
+ with client.routers.with_streaming_response.delete(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ router = response.parse()
+ assert router is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_delete(self, client: RunwayML) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.routers.with_raw_response.delete(
+ "",
+ )
+
+
+class TestAsyncRouters:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_create(self, async_client: AsyncRunwayML) -> None:
+ router = await async_client.routers.create(
+ slug="slug",
+ )
+ assert_matches_type(RouterCreateResponse, router, path=["response"])
+
+ @parametrize
+ async def test_method_create_with_all_params(self, async_client: AsyncRunwayML) -> None:
+ router = await async_client.routers.create(
+ slug="slug",
+ description="description",
+ name="x",
+ settings={
+ "max_credits_per_generation": {
+ "audio": 1,
+ "image": 1,
+ "video": 1,
+ },
+ "models": {
+ "ids": ["string"],
+ "mode": "allow_new_except",
+ },
+ "optimize_for": "cost",
+ "schema_version": 1,
+ },
+ )
+ assert_matches_type(RouterCreateResponse, router, path=["response"])
+
+ @parametrize
+ async def test_raw_response_create(self, async_client: AsyncRunwayML) -> None:
+ response = await async_client.routers.with_raw_response.create(
+ slug="slug",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ router = await response.parse()
+ assert_matches_type(RouterCreateResponse, router, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_create(self, async_client: AsyncRunwayML) -> None:
+ async with async_client.routers.with_streaming_response.create(
+ slug="slug",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ router = await response.parse()
+ assert_matches_type(RouterCreateResponse, router, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_retrieve(self, async_client: AsyncRunwayML) -> None:
+ router = await async_client.routers.retrieve(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+ assert_matches_type(RouterRetrieveResponse, router, path=["response"])
+
+ @parametrize
+ async def test_raw_response_retrieve(self, async_client: AsyncRunwayML) -> None:
+ response = await async_client.routers.with_raw_response.retrieve(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ router = await response.parse()
+ assert_matches_type(RouterRetrieveResponse, router, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_retrieve(self, async_client: AsyncRunwayML) -> None:
+ async with async_client.routers.with_streaming_response.retrieve(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ router = await response.parse()
+ assert_matches_type(RouterRetrieveResponse, router, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_retrieve(self, async_client: AsyncRunwayML) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.routers.with_raw_response.retrieve(
+ "",
+ )
+
+ @parametrize
+ async def test_method_update(self, async_client: AsyncRunwayML) -> None:
+ router = await async_client.routers.update(
+ id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+ assert_matches_type(RouterUpdateResponse, router, path=["response"])
+
+ @parametrize
+ async def test_method_update_with_all_params(self, async_client: AsyncRunwayML) -> None:
+ router = await async_client.routers.update(
+ id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ description="description",
+ name="x",
+ settings={
+ "max_credits_per_generation": {
+ "audio": 1,
+ "image": 1,
+ "video": 1,
+ },
+ "models": {
+ "ids": ["string"],
+ "mode": "allow_new_except",
+ },
+ "optimize_for": "cost",
+ "schema_version": 1,
+ },
+ )
+ assert_matches_type(RouterUpdateResponse, router, path=["response"])
+
+ @parametrize
+ async def test_raw_response_update(self, async_client: AsyncRunwayML) -> None:
+ response = await async_client.routers.with_raw_response.update(
+ id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ router = await response.parse()
+ assert_matches_type(RouterUpdateResponse, router, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_update(self, async_client: AsyncRunwayML) -> None:
+ async with async_client.routers.with_streaming_response.update(
+ id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ router = await response.parse()
+ assert_matches_type(RouterUpdateResponse, router, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_update(self, async_client: AsyncRunwayML) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.routers.with_raw_response.update(
+ id="",
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncRunwayML) -> None:
+ router = await async_client.routers.list(
+ limit=1,
+ )
+ assert_matches_type(AsyncCursorPage[RouterListResponse], router, path=["response"])
+
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncRunwayML) -> None:
+ router = await async_client.routers.list(
+ limit=1,
+ cursor="x",
+ )
+ assert_matches_type(AsyncCursorPage[RouterListResponse], router, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncRunwayML) -> None:
+ response = await async_client.routers.with_raw_response.list(
+ limit=1,
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ router = await response.parse()
+ assert_matches_type(AsyncCursorPage[RouterListResponse], router, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncRunwayML) -> None:
+ async with async_client.routers.with_streaming_response.list(
+ limit=1,
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ router = await response.parse()
+ assert_matches_type(AsyncCursorPage[RouterListResponse], router, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_delete(self, async_client: AsyncRunwayML) -> None:
+ router = await async_client.routers.delete(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+ assert router is None
+
+ @parametrize
+ async def test_raw_response_delete(self, async_client: AsyncRunwayML) -> None:
+ response = await async_client.routers.with_raw_response.delete(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ router = await response.parse()
+ assert router is None
+
+ @parametrize
+ async def test_streaming_response_delete(self, async_client: AsyncRunwayML) -> None:
+ async with async_client.routers.with_streaming_response.delete(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ router = await response.parse()
+ assert router is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_delete(self, async_client: AsyncRunwayML) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.routers.with_raw_response.delete(
+ "",
+ )
diff --git a/tests/api_resources/test_text_to_image.py b/tests/api_resources/test_text_to_image.py
index a241fbd..213a6b9 100644
--- a/tests/api_resources/test_text_to_image.py
+++ b/tests/api_resources/test_text_to_image.py
@@ -306,6 +306,7 @@ def test_method_create_with_all_params_overload_6(self, client: RunwayML) -> Non
model="seedream5_pro",
prompt_text="x",
ratio="1024:1024",
+ grounding=True,
output_count=1,
output_format="png",
reference_images=[{"uri": "https://example.com/file"}],
@@ -736,6 +737,7 @@ async def test_method_create_with_all_params_overload_6(self, async_client: Asyn
model="seedream5_pro",
prompt_text="x",
ratio="1024:1024",
+ grounding=True,
output_count=1,
output_format="png",
reference_images=[{"uri": "https://example.com/file"}],
diff --git a/uv.lock b/uv.lock
index ee1b946..7b1cbbf 100644
--- a/uv.lock
+++ b/uv.lock
@@ -886,7 +886,7 @@ wheels = [
[[package]]
name = "runwayml"
-version = "5.1.0"
+version = "5.10.0"
source = { editable = "." }
dependencies = [
{ name = "anyio", version = "4.12.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
From 9c4e62a8c13e96ec733f6efe7cfa3fd7f7589357 Mon Sep 17 00:00:00 2001
From: Robin Andeer
Date: Tue, 21 Jul 2026 14:29:28 -0400
Subject: [PATCH 3/9] fix(client): make generate.video responses awaitable
---
src/runwayml/resources/generate/video.py | 77 +++++++++++-------------
1 file changed, 36 insertions(+), 41 deletions(-)
diff --git a/src/runwayml/resources/generate/video.py b/src/runwayml/resources/generate/video.py
index 7c05ac3..c4b5d7e 100644
--- a/src/runwayml/resources/generate/video.py
+++ b/src/runwayml/resources/generate/video.py
@@ -2,10 +2,15 @@
from __future__ import annotations
-from typing import Any, cast
-
import httpx
+from runwayml.lib.polling import (
+ NewTaskCreatedResponse,
+ AsyncNewTaskCreatedResponse,
+ create_waitable_resource,
+ create_async_waitable_resource,
+)
+
from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ..._utils import maybe_transform, async_maybe_transform
from ..._compat import cached_property
@@ -18,7 +23,7 @@
)
from ..._base_client import make_request_options
from ...types.generate import video_create_params
-from ...types.generate.video_create_response import VideoCreateResponse
+from ...types.generate.video_create_response import RoutedVideoTaskCreated
__all__ = ["VideoResource", "AsyncVideoResource"]
@@ -55,7 +60,7 @@ def create(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> VideoCreateResponse:
+ ) -> NewTaskCreatedResponse:
"""
Start a video generation task using a saved Model Router config instead of
naming a model.
@@ -78,25 +83,20 @@ def create(
timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- VideoCreateResponse,
- self._post(
- "/v1/generate/video",
- body=maybe_transform(
- {
- "config_id": config_id,
- "input": input,
- "dry_run": dry_run,
- },
- video_create_params.VideoCreateParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=cast(
- Any, VideoCreateResponse
- ), # Union types cannot be passed in as arguments in the type system
+ return self._post(
+ "/v1/generate/video",
+ body=maybe_transform(
+ {
+ "config_id": config_id,
+ "input": input,
+ "dry_run": dry_run,
+ },
+ video_create_params.VideoCreateParams,
),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=create_waitable_resource(RoutedVideoTaskCreated, self._client),
)
@@ -132,7 +132,7 @@ async def create(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> VideoCreateResponse:
+ ) -> AsyncNewTaskCreatedResponse:
"""
Start a video generation task using a saved Model Router config instead of
naming a model.
@@ -155,25 +155,20 @@ async def create(
timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- VideoCreateResponse,
- await self._post(
- "/v1/generate/video",
- body=await async_maybe_transform(
- {
- "config_id": config_id,
- "input": input,
- "dry_run": dry_run,
- },
- video_create_params.VideoCreateParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=cast(
- Any, VideoCreateResponse
- ), # Union types cannot be passed in as arguments in the type system
+ return await self._post(
+ "/v1/generate/video",
+ body=await async_maybe_transform(
+ {
+ "config_id": config_id,
+ "input": input,
+ "dry_run": dry_run,
+ },
+ video_create_params.VideoCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
+ cast_to=create_async_waitable_resource(RoutedVideoTaskCreated, self._client),
)
From 9bc89e19db9aced896ebc5f4e5452bc3f187ab07 Mon Sep 17 00:00:00 2001
From: Robin Andeer
Date: Tue, 21 Jul 2026 14:54:55 -0400
Subject: [PATCH 4/9] fix(client): type dry_run separately from waitable
generate.video
---
src/runwayml/__init__.py | 2 +
src/runwayml/lib/polling.py | 20 +++
src/runwayml/resources/generate/video.py | 155 +++++++++++++++++++----
3 files changed, 150 insertions(+), 27 deletions(-)
diff --git a/src/runwayml/__init__.py b/src/runwayml/__init__.py
index 80eec83..f783012 100644
--- a/src/runwayml/__init__.py
+++ b/src/runwayml/__init__.py
@@ -39,6 +39,7 @@
from .lib.polling import (
TaskFailedError,
TaskTimeoutError,
+ DryRunHasNoTaskError,
WorkflowInvocationFailedError,
WorkflowInvocationTimeoutError,
)
@@ -89,6 +90,7 @@
"DefaultAioHttpClient",
"TaskFailedError",
"TaskTimeoutError",
+ "DryRunHasNoTaskError",
"WorkflowInvocationFailedError",
"WorkflowInvocationTimeoutError",
]
diff --git a/src/runwayml/lib/polling.py b/src/runwayml/lib/polling.py
index 4c1f737..1228c47 100644
--- a/src/runwayml/lib/polling.py
+++ b/src/runwayml/lib/polling.py
@@ -87,6 +87,7 @@ class WithClient(base_class): # type: ignore[valid-type,misc]
id: str
def wait_for_task_output(self, timeout: Union[float, None] = 60 * 10) -> TaskRetrieveResponse:
+ _ensure_waitable_task_id(self)
start_time = time.time()
while True:
time.sleep(POLL_TIME + random.random() * POLL_JITTER - POLL_JITTER / 2)
@@ -109,6 +110,7 @@ class WithClient(base_class): # type: ignore[valid-type,misc]
id: str
async def wait_for_task_output(self, timeout: Union[float, None] = 60 * 10) -> TaskRetrieveResponse:
+ _ensure_waitable_task_id(self)
start_time = anyio.current_time()
while True:
await anyio.sleep(POLL_TIME + random.random() * POLL_JITTER - POLL_JITTER / 2)
@@ -138,6 +140,24 @@ def __init__(self, task_details: TaskRetrieveResponse):
super().__init__(f"Task timed out")
+class DryRunHasNoTaskError(Exception):
+ """Raised when wait_for_task_output is called on a dry-run (or otherwise id-less) response."""
+
+ def __init__(self, response: object) -> None:
+ self.response = response
+ super().__init__(
+ "Cannot wait for task output: response has no task id "
+ "(for example dry_run=True creates no task)."
+ )
+
+
+def _ensure_waitable_task_id(response: object) -> None:
+ task_id = getattr(response, "id", None)
+ dry_run = getattr(response, "dry_run", False)
+ if dry_run is True or not isinstance(task_id, str) or not task_id:
+ raise DryRunHasNoTaskError(response)
+
+
class AwaitablePending(AwaitableTaskResponseMixin, Pending): ...
diff --git a/src/runwayml/resources/generate/video.py b/src/runwayml/resources/generate/video.py
index c4b5d7e..7e0da66 100644
--- a/src/runwayml/resources/generate/video.py
+++ b/src/runwayml/resources/generate/video.py
@@ -2,6 +2,9 @@
from __future__ import annotations
+from typing import overload
+from typing_extensions import Literal
+
import httpx
from runwayml.lib.polling import (
@@ -23,7 +26,7 @@
)
from ..._base_client import make_request_options
from ...types.generate import video_create_params
-from ...types.generate.video_create_response import RoutedVideoTaskCreated
+from ...types.generate.video_create_response import RoutedVideoDryRun, RoutedVideoTaskCreated
__all__ = ["VideoResource", "AsyncVideoResource"]
@@ -48,6 +51,45 @@ def with_streaming_response(self) -> VideoResourceWithStreamingResponse:
"""
return VideoResourceWithStreamingResponse(self)
+ @overload
+ def create(
+ self,
+ *,
+ config_id: str,
+ input: video_create_params.Input,
+ dry_run: Literal[True],
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> RoutedVideoDryRun: ...
+
+ @overload
+ def create(
+ self,
+ *,
+ config_id: str,
+ input: video_create_params.Input,
+ dry_run: Literal[False] | Omit = omit,
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> NewTaskCreatedResponse: ...
+
+ @overload
+ def create(
+ self,
+ *,
+ config_id: str,
+ input: video_create_params.Input,
+ dry_run: bool,
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> RoutedVideoDryRun | NewTaskCreatedResponse: ...
+
def create(
self,
*,
@@ -60,7 +102,7 @@ def create(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> NewTaskCreatedResponse:
+ ) -> RoutedVideoDryRun | NewTaskCreatedResponse:
"""
Start a video generation task using a saved Model Router config instead of
naming a model.
@@ -73,7 +115,8 @@ def create(
dry_run: When true, run the full routing pipeline and return the decision and estimated
cost without generating. No task is created, nothing is billed, and no asset is
- produced.
+ produced. Dry-run responses have no task id — do not call
+ `wait_for_task_output`.
extra_headers: Send extra headers
@@ -83,19 +126,28 @@ def create(
timeout: Override the client-level default timeout for this request, in seconds
"""
+ body = maybe_transform(
+ {
+ "config_id": config_id,
+ "input": input,
+ "dry_run": dry_run,
+ },
+ video_create_params.VideoCreateParams,
+ )
+ options = make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ )
+ if dry_run is True:
+ return self._post(
+ "/v1/generate/video",
+ body=body,
+ options=options,
+ cast_to=RoutedVideoDryRun,
+ )
return self._post(
"/v1/generate/video",
- body=maybe_transform(
- {
- "config_id": config_id,
- "input": input,
- "dry_run": dry_run,
- },
- video_create_params.VideoCreateParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
+ body=body,
+ options=options,
cast_to=create_waitable_resource(RoutedVideoTaskCreated, self._client),
)
@@ -120,6 +172,45 @@ def with_streaming_response(self) -> AsyncVideoResourceWithStreamingResponse:
"""
return AsyncVideoResourceWithStreamingResponse(self)
+ @overload
+ async def create(
+ self,
+ *,
+ config_id: str,
+ input: video_create_params.Input,
+ dry_run: Literal[True],
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> RoutedVideoDryRun: ...
+
+ @overload
+ async def create(
+ self,
+ *,
+ config_id: str,
+ input: video_create_params.Input,
+ dry_run: Literal[False] | Omit = omit,
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> AsyncNewTaskCreatedResponse: ...
+
+ @overload
+ async def create(
+ self,
+ *,
+ config_id: str,
+ input: video_create_params.Input,
+ dry_run: bool,
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> RoutedVideoDryRun | AsyncNewTaskCreatedResponse: ...
+
async def create(
self,
*,
@@ -132,7 +223,7 @@ async def create(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> AsyncNewTaskCreatedResponse:
+ ) -> RoutedVideoDryRun | AsyncNewTaskCreatedResponse:
"""
Start a video generation task using a saved Model Router config instead of
naming a model.
@@ -145,7 +236,8 @@ async def create(
dry_run: When true, run the full routing pipeline and return the decision and estimated
cost without generating. No task is created, nothing is billed, and no asset is
- produced.
+ produced. Dry-run responses have no task id — do not call
+ `wait_for_task_output`.
extra_headers: Send extra headers
@@ -155,19 +247,28 @@ async def create(
timeout: Override the client-level default timeout for this request, in seconds
"""
+ body = await async_maybe_transform(
+ {
+ "config_id": config_id,
+ "input": input,
+ "dry_run": dry_run,
+ },
+ video_create_params.VideoCreateParams,
+ )
+ options = make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ )
+ if dry_run is True:
+ return await self._post(
+ "/v1/generate/video",
+ body=body,
+ options=options,
+ cast_to=RoutedVideoDryRun,
+ )
return await self._post(
"/v1/generate/video",
- body=await async_maybe_transform(
- {
- "config_id": config_id,
- "input": input,
- "dry_run": dry_run,
- },
- video_create_params.VideoCreateParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
+ body=body,
+ options=options,
cast_to=create_async_waitable_resource(RoutedVideoTaskCreated, self._client),
)
From 3de6be47f1e49e3d462f077e76e21fc3ef4415a2 Mon Sep 17 00:00:00 2001
From: Robin Andeer
Date: Tue, 21 Jul 2026 15:14:15 -0400
Subject: [PATCH 5/9] feat(client): add generate.video.preview for dry-run
routing
---
src/runwayml/resources/generate/video.py | 241 ++++++++++++-----------
1 file changed, 121 insertions(+), 120 deletions(-)
diff --git a/src/runwayml/resources/generate/video.py b/src/runwayml/resources/generate/video.py
index 7e0da66..93a0d68 100644
--- a/src/runwayml/resources/generate/video.py
+++ b/src/runwayml/resources/generate/video.py
@@ -2,9 +2,6 @@
from __future__ import annotations
-from typing import overload
-from typing_extensions import Literal
-
import httpx
from runwayml.lib.polling import (
@@ -14,7 +11,7 @@
create_async_waitable_resource,
)
-from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from ..._types import Body, Query, Headers, NotGiven, not_given
from ..._utils import maybe_transform, async_maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
@@ -51,61 +48,71 @@ def with_streaming_response(self) -> VideoResourceWithStreamingResponse:
"""
return VideoResourceWithStreamingResponse(self)
- @overload
def create(
self,
*,
config_id: str,
input: video_create_params.Input,
- dry_run: Literal[True],
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> RoutedVideoDryRun: ...
+ ) -> NewTaskCreatedResponse:
+ """
+ Start a video generation task using a saved Model Router config instead of
+ naming a model.
- @overload
- def create(
- self,
- *,
- config_id: str,
- input: video_create_params.Input,
- dry_run: Literal[False] | Omit = omit,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> NewTaskCreatedResponse: ...
+ For a routing decision without creating a task, use `preview` instead.
- @overload
- def create(
- self,
- *,
- config_id: str,
- input: video_create_params.Input,
- dry_run: bool,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> RoutedVideoDryRun | NewTaskCreatedResponse: ...
+ Args:
+ config_id: The slug of a saved Model Router config to route this request with.
- def create(
+ input: Model-agnostic video generation input. Fields are optional; the router selects a
+ model and maps these options to it.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._post(
+ "/v1/generate/video",
+ body=maybe_transform(
+ {
+ "config_id": config_id,
+ "input": input,
+ },
+ video_create_params.VideoCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=create_waitable_resource(RoutedVideoTaskCreated, self._client),
+ )
+
+ def preview(
self,
*,
config_id: str,
input: video_create_params.Input,
- dry_run: bool | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> RoutedVideoDryRun | NewTaskCreatedResponse:
+ ) -> RoutedVideoDryRun:
"""
- Start a video generation task using a saved Model Router config instead of
- naming a model.
+ Run Model Router selection for a video request without creating a task or
+ billing a generation.
+
+ Returns the same routing metadata as `create`, including the selected model and
+ estimated cost. Prefer this over passing `dry_run` to `create`.
Args:
config_id: The slug of a saved Model Router config to route this request with.
@@ -113,11 +120,6 @@ def create(
input: Model-agnostic video generation input. Fields are optional; the router selects a
model and maps these options to it.
- dry_run: When true, run the full routing pipeline and return the decision and estimated
- cost without generating. No task is created, nothing is billed, and no asset is
- produced. Dry-run responses have no task id — do not call
- `wait_for_task_output`.
-
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -126,29 +128,20 @@ def create(
timeout: Override the client-level default timeout for this request, in seconds
"""
- body = maybe_transform(
- {
- "config_id": config_id,
- "input": input,
- "dry_run": dry_run,
- },
- video_create_params.VideoCreateParams,
- )
- options = make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- )
- if dry_run is True:
- return self._post(
- "/v1/generate/video",
- body=body,
- options=options,
- cast_to=RoutedVideoDryRun,
- )
return self._post(
"/v1/generate/video",
- body=body,
- options=options,
- cast_to=create_waitable_resource(RoutedVideoTaskCreated, self._client),
+ body=maybe_transform(
+ {
+ "config_id": config_id,
+ "input": input,
+ "dry_run": True,
+ },
+ video_create_params.VideoCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=RoutedVideoDryRun,
)
@@ -172,61 +165,71 @@ def with_streaming_response(self) -> AsyncVideoResourceWithStreamingResponse:
"""
return AsyncVideoResourceWithStreamingResponse(self)
- @overload
async def create(
self,
*,
config_id: str,
input: video_create_params.Input,
- dry_run: Literal[True],
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> RoutedVideoDryRun: ...
+ ) -> AsyncNewTaskCreatedResponse:
+ """
+ Start a video generation task using a saved Model Router config instead of
+ naming a model.
- @overload
- async def create(
- self,
- *,
- config_id: str,
- input: video_create_params.Input,
- dry_run: Literal[False] | Omit = omit,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> AsyncNewTaskCreatedResponse: ...
+ For a routing decision without creating a task, use `preview` instead.
- @overload
- async def create(
- self,
- *,
- config_id: str,
- input: video_create_params.Input,
- dry_run: bool,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> RoutedVideoDryRun | AsyncNewTaskCreatedResponse: ...
+ Args:
+ config_id: The slug of a saved Model Router config to route this request with.
- async def create(
+ input: Model-agnostic video generation input. Fields are optional; the router selects a
+ model and maps these options to it.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._post(
+ "/v1/generate/video",
+ body=await async_maybe_transform(
+ {
+ "config_id": config_id,
+ "input": input,
+ },
+ video_create_params.VideoCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=create_async_waitable_resource(RoutedVideoTaskCreated, self._client),
+ )
+
+ async def preview(
self,
*,
config_id: str,
input: video_create_params.Input,
- dry_run: bool | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> RoutedVideoDryRun | AsyncNewTaskCreatedResponse:
+ ) -> RoutedVideoDryRun:
"""
- Start a video generation task using a saved Model Router config instead of
- naming a model.
+ Run Model Router selection for a video request without creating a task or
+ billing a generation.
+
+ Returns the same routing metadata as `create`, including the selected model and
+ estimated cost. Prefer this over passing `dry_run` to `create`.
Args:
config_id: The slug of a saved Model Router config to route this request with.
@@ -234,11 +237,6 @@ async def create(
input: Model-agnostic video generation input. Fields are optional; the router selects a
model and maps these options to it.
- dry_run: When true, run the full routing pipeline and return the decision and estimated
- cost without generating. No task is created, nothing is billed, and no asset is
- produced. Dry-run responses have no task id — do not call
- `wait_for_task_output`.
-
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -247,29 +245,20 @@ async def create(
timeout: Override the client-level default timeout for this request, in seconds
"""
- body = await async_maybe_transform(
- {
- "config_id": config_id,
- "input": input,
- "dry_run": dry_run,
- },
- video_create_params.VideoCreateParams,
- )
- options = make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- )
- if dry_run is True:
- return await self._post(
- "/v1/generate/video",
- body=body,
- options=options,
- cast_to=RoutedVideoDryRun,
- )
return await self._post(
"/v1/generate/video",
- body=body,
- options=options,
- cast_to=create_async_waitable_resource(RoutedVideoTaskCreated, self._client),
+ body=await async_maybe_transform(
+ {
+ "config_id": config_id,
+ "input": input,
+ "dry_run": True,
+ },
+ video_create_params.VideoCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=RoutedVideoDryRun,
)
@@ -280,6 +269,9 @@ def __init__(self, video: VideoResource) -> None:
self.create = to_raw_response_wrapper(
video.create,
)
+ self.preview = to_raw_response_wrapper(
+ video.preview,
+ )
class AsyncVideoResourceWithRawResponse:
@@ -289,6 +281,9 @@ def __init__(self, video: AsyncVideoResource) -> None:
self.create = async_to_raw_response_wrapper(
video.create,
)
+ self.preview = async_to_raw_response_wrapper(
+ video.preview,
+ )
class VideoResourceWithStreamingResponse:
@@ -298,6 +293,9 @@ def __init__(self, video: VideoResource) -> None:
self.create = to_streamed_response_wrapper(
video.create,
)
+ self.preview = to_streamed_response_wrapper(
+ video.preview,
+ )
class AsyncVideoResourceWithStreamingResponse:
@@ -307,3 +305,6 @@ def __init__(self, video: AsyncVideoResource) -> None:
self.create = async_to_streamed_response_wrapper(
video.create,
)
+ self.preview = async_to_streamed_response_wrapper(
+ video.preview,
+ )
From 266a47f8f5ad5b29aa2147e2152d86b4d5669f95 Mon Sep 17 00:00:00 2001
From: Robin Andeer
Date: Tue, 21 Jul 2026 15:41:16 -0400
Subject: [PATCH 6/9] fix(client): drop generate.video dry-run/preview from SDK
surface
Keep waitable create only; dry-run stays HTTP-only until a cleaner schema/SDK approach.
---
src/runwayml/__init__.py | 2 -
src/runwayml/lib/polling.py | 20 ----
src/runwayml/resources/generate/video.py | 118 +----------------------
3 files changed, 5 insertions(+), 135 deletions(-)
diff --git a/src/runwayml/__init__.py b/src/runwayml/__init__.py
index f783012..80eec83 100644
--- a/src/runwayml/__init__.py
+++ b/src/runwayml/__init__.py
@@ -39,7 +39,6 @@
from .lib.polling import (
TaskFailedError,
TaskTimeoutError,
- DryRunHasNoTaskError,
WorkflowInvocationFailedError,
WorkflowInvocationTimeoutError,
)
@@ -90,7 +89,6 @@
"DefaultAioHttpClient",
"TaskFailedError",
"TaskTimeoutError",
- "DryRunHasNoTaskError",
"WorkflowInvocationFailedError",
"WorkflowInvocationTimeoutError",
]
diff --git a/src/runwayml/lib/polling.py b/src/runwayml/lib/polling.py
index 1228c47..4c1f737 100644
--- a/src/runwayml/lib/polling.py
+++ b/src/runwayml/lib/polling.py
@@ -87,7 +87,6 @@ class WithClient(base_class): # type: ignore[valid-type,misc]
id: str
def wait_for_task_output(self, timeout: Union[float, None] = 60 * 10) -> TaskRetrieveResponse:
- _ensure_waitable_task_id(self)
start_time = time.time()
while True:
time.sleep(POLL_TIME + random.random() * POLL_JITTER - POLL_JITTER / 2)
@@ -110,7 +109,6 @@ class WithClient(base_class): # type: ignore[valid-type,misc]
id: str
async def wait_for_task_output(self, timeout: Union[float, None] = 60 * 10) -> TaskRetrieveResponse:
- _ensure_waitable_task_id(self)
start_time = anyio.current_time()
while True:
await anyio.sleep(POLL_TIME + random.random() * POLL_JITTER - POLL_JITTER / 2)
@@ -140,24 +138,6 @@ def __init__(self, task_details: TaskRetrieveResponse):
super().__init__(f"Task timed out")
-class DryRunHasNoTaskError(Exception):
- """Raised when wait_for_task_output is called on a dry-run (or otherwise id-less) response."""
-
- def __init__(self, response: object) -> None:
- self.response = response
- super().__init__(
- "Cannot wait for task output: response has no task id "
- "(for example dry_run=True creates no task)."
- )
-
-
-def _ensure_waitable_task_id(response: object) -> None:
- task_id = getattr(response, "id", None)
- dry_run = getattr(response, "dry_run", False)
- if dry_run is True or not isinstance(task_id, str) or not task_id:
- raise DryRunHasNoTaskError(response)
-
-
class AwaitablePending(AwaitableTaskResponseMixin, Pending): ...
diff --git a/src/runwayml/resources/generate/video.py b/src/runwayml/resources/generate/video.py
index 93a0d68..53ceedc 100644
--- a/src/runwayml/resources/generate/video.py
+++ b/src/runwayml/resources/generate/video.py
@@ -23,7 +23,7 @@
)
from ..._base_client import make_request_options
from ...types.generate import video_create_params
-from ...types.generate.video_create_response import RoutedVideoDryRun, RoutedVideoTaskCreated
+from ...types.generate.video_create_response import RoutedVideoTaskCreated
__all__ = ["VideoResource", "AsyncVideoResource"]
@@ -64,7 +64,8 @@ def create(
Start a video generation task using a saved Model Router config instead of
naming a model.
- For a routing decision without creating a task, use `preview` instead.
+ The SDK does not expose dry-run for this endpoint yet — use the HTTP API with
+ ``dry_run=True`` if you need a routing preview without creating a task.
Args:
config_id: The slug of a saved Model Router config to route this request with.
@@ -95,55 +96,6 @@ def create(
cast_to=create_waitable_resource(RoutedVideoTaskCreated, self._client),
)
- def preview(
- self,
- *,
- config_id: str,
- input: video_create_params.Input,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> RoutedVideoDryRun:
- """
- Run Model Router selection for a video request without creating a task or
- billing a generation.
-
- Returns the same routing metadata as `create`, including the selected model and
- estimated cost. Prefer this over passing `dry_run` to `create`.
-
- Args:
- config_id: The slug of a saved Model Router config to route this request with.
-
- input: Model-agnostic video generation input. Fields are optional; the router selects a
- model and maps these options to it.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- return self._post(
- "/v1/generate/video",
- body=maybe_transform(
- {
- "config_id": config_id,
- "input": input,
- "dry_run": True,
- },
- video_create_params.VideoCreateParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=RoutedVideoDryRun,
- )
-
class AsyncVideoResource(AsyncAPIResource):
@cached_property
@@ -181,7 +133,8 @@ async def create(
Start a video generation task using a saved Model Router config instead of
naming a model.
- For a routing decision without creating a task, use `preview` instead.
+ The SDK does not expose dry-run for this endpoint yet — use the HTTP API with
+ ``dry_run=True`` if you need a routing preview without creating a task.
Args:
config_id: The slug of a saved Model Router config to route this request with.
@@ -212,55 +165,6 @@ async def create(
cast_to=create_async_waitable_resource(RoutedVideoTaskCreated, self._client),
)
- async def preview(
- self,
- *,
- config_id: str,
- input: video_create_params.Input,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> RoutedVideoDryRun:
- """
- Run Model Router selection for a video request without creating a task or
- billing a generation.
-
- Returns the same routing metadata as `create`, including the selected model and
- estimated cost. Prefer this over passing `dry_run` to `create`.
-
- Args:
- config_id: The slug of a saved Model Router config to route this request with.
-
- input: Model-agnostic video generation input. Fields are optional; the router selects a
- model and maps these options to it.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- return await self._post(
- "/v1/generate/video",
- body=await async_maybe_transform(
- {
- "config_id": config_id,
- "input": input,
- "dry_run": True,
- },
- video_create_params.VideoCreateParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=RoutedVideoDryRun,
- )
-
class VideoResourceWithRawResponse:
def __init__(self, video: VideoResource) -> None:
@@ -269,9 +173,6 @@ def __init__(self, video: VideoResource) -> None:
self.create = to_raw_response_wrapper(
video.create,
)
- self.preview = to_raw_response_wrapper(
- video.preview,
- )
class AsyncVideoResourceWithRawResponse:
@@ -281,9 +182,6 @@ def __init__(self, video: AsyncVideoResource) -> None:
self.create = async_to_raw_response_wrapper(
video.create,
)
- self.preview = async_to_raw_response_wrapper(
- video.preview,
- )
class VideoResourceWithStreamingResponse:
@@ -293,9 +191,6 @@ def __init__(self, video: VideoResource) -> None:
self.create = to_streamed_response_wrapper(
video.create,
)
- self.preview = to_streamed_response_wrapper(
- video.preview,
- )
class AsyncVideoResourceWithStreamingResponse:
@@ -305,6 +200,3 @@ def __init__(self, video: AsyncVideoResource) -> None:
self.create = async_to_streamed_response_wrapper(
video.create,
)
- self.preview = async_to_streamed_response_wrapper(
- video.preview,
- )
From 25e2a98d7a0cb7a2a9d85456d6e0d339dbcd45da Mon Sep 17 00:00:00 2001
From: Robin Andeer
Date: Tue, 21 Jul 2026 15:49:11 -0400
Subject: [PATCH 7/9] fix(client): drop dry_run from generate.video tests
---
src/runwayml/resources/generate/video.py | 6 ------
tests/api_resources/generate/test_video.py | 2 --
2 files changed, 8 deletions(-)
diff --git a/src/runwayml/resources/generate/video.py b/src/runwayml/resources/generate/video.py
index 53ceedc..3750712 100644
--- a/src/runwayml/resources/generate/video.py
+++ b/src/runwayml/resources/generate/video.py
@@ -64,9 +64,6 @@ def create(
Start a video generation task using a saved Model Router config instead of
naming a model.
- The SDK does not expose dry-run for this endpoint yet — use the HTTP API with
- ``dry_run=True`` if you need a routing preview without creating a task.
-
Args:
config_id: The slug of a saved Model Router config to route this request with.
@@ -133,9 +130,6 @@ async def create(
Start a video generation task using a saved Model Router config instead of
naming a model.
- The SDK does not expose dry-run for this endpoint yet — use the HTTP API with
- ``dry_run=True`` if you need a routing preview without creating a task.
-
Args:
config_id: The slug of a saved Model Router config to route this request with.
diff --git a/tests/api_resources/generate/test_video.py b/tests/api_resources/generate/test_video.py
index e9ec88d..48283d4 100644
--- a/tests/api_resources/generate/test_video.py
+++ b/tests/api_resources/generate/test_video.py
@@ -62,7 +62,6 @@ def test_method_create_with_all_params(self, client: RunwayML) -> None:
"resolution": "480p",
"seed": 0,
},
- dry_run=True,
)
assert_matches_type(VideoCreateResponse, video, path=["response"])
@@ -143,7 +142,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncRunwayML)
"resolution": "480p",
"seed": 0,
},
- dry_run=True,
)
assert_matches_type(VideoCreateResponse, video, path=["response"])
From 12ef058cab0ad6ec97d988c911ac3e210112a879 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 21 Jul 2026 19:49:28 +0000
Subject: [PATCH 8/9] release: 5.11.0
---
.release-please-manifest.json | 2 +-
CHANGELOG.md | 18 ++++++++++++++++++
pyproject.toml | 2 +-
src/runwayml/_version.py | 2 +-
4 files changed, 21 insertions(+), 3 deletions(-)
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 82fc926..e42b9af 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "5.10.0"
+ ".": "5.11.0"
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 612061a..dd164d4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,23 @@
# Changelog
+## 5.11.0 (2026-07-21)
+
+Full Changelog: [v5.10.0...v5.11.0](https://github.com/runwayml/sdk-python/compare/v5.10.0...v5.11.0)
+
+### Features
+
+* **api:** add Model Router CRUD and routed video generation ([05df606](https://github.com/runwayml/sdk-python/commit/05df606fe356a89e7cfa2765f77a1cddbdf06d71))
+* **client:** add generate.video.preview for dry-run routing ([3de6be4](https://github.com/runwayml/sdk-python/commit/3de6be47f1e49e3d462f077e76e21fc3ef4415a2))
+* **stlc:** configurable CI runner and private-production-repo support in workflow templates ([d83d189](https://github.com/runwayml/sdk-python/commit/d83d1895c3ef26cb6c803db36dacab1b43350fd7))
+
+
+### Bug Fixes
+
+* **client:** drop dry_run from generate.video tests ([25e2a98](https://github.com/runwayml/sdk-python/commit/25e2a98d7a0cb7a2a9d85456d6e0d339dbcd45da))
+* **client:** drop generate.video dry-run/preview from SDK surface ([266a47f](https://github.com/runwayml/sdk-python/commit/266a47f8f5ad5b29aa2147e2152d86b4d5669f95))
+* **client:** make generate.video responses awaitable ([9c4e62a](https://github.com/runwayml/sdk-python/commit/9c4e62a8c13e96ec733f6efe7cfa3fd7f7589357))
+* **client:** type dry_run separately from waitable generate.video ([9bc89e1](https://github.com/runwayml/sdk-python/commit/9bc89e19db9aced896ebc5f4e5452bc3f187ab07))
+
## 5.10.0 (2026-07-13)
Full Changelog: [v5.9.0...v5.10.0](https://github.com/runwayml/sdk-python/compare/v5.9.0...v5.10.0)
diff --git a/pyproject.toml b/pyproject.toml
index f6302c9..2ee47e8 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "runwayml"
-version = "5.10.0"
+version = "5.11.0"
description = "The official Python library for the runwayml API"
dynamic = ["readme"]
license = "Apache-2.0"
diff --git a/src/runwayml/_version.py b/src/runwayml/_version.py
index de6cf6d..e20c282 100644
--- a/src/runwayml/_version.py
+++ b/src/runwayml/_version.py
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
__title__ = "runwayml"
-__version__ = "5.10.0" # x-release-please-version
+__version__ = "5.11.0" # x-release-please-version
From 0f1e4798514ec7bddb2b0f871209d89b259e5de2 Mon Sep 17 00:00:00 2001
From: Robin Andeer
Date: Tue, 21 Jul 2026 15:51:19 -0400
Subject: [PATCH 9/9] chore: trim dry-run/preview noise from 5.11.0 changelog
---
CHANGELOG.md | 4 ----
1 file changed, 4 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dd164d4..8e78108 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,16 +7,12 @@ Full Changelog: [v5.10.0...v5.11.0](https://github.com/runwayml/sdk-python/compa
### Features
* **api:** add Model Router CRUD and routed video generation ([05df606](https://github.com/runwayml/sdk-python/commit/05df606fe356a89e7cfa2765f77a1cddbdf06d71))
-* **client:** add generate.video.preview for dry-run routing ([3de6be4](https://github.com/runwayml/sdk-python/commit/3de6be47f1e49e3d462f077e76e21fc3ef4415a2))
* **stlc:** configurable CI runner and private-production-repo support in workflow templates ([d83d189](https://github.com/runwayml/sdk-python/commit/d83d1895c3ef26cb6c803db36dacab1b43350fd7))
### Bug Fixes
-* **client:** drop dry_run from generate.video tests ([25e2a98](https://github.com/runwayml/sdk-python/commit/25e2a98d7a0cb7a2a9d85456d6e0d339dbcd45da))
-* **client:** drop generate.video dry-run/preview from SDK surface ([266a47f](https://github.com/runwayml/sdk-python/commit/266a47f8f5ad5b29aa2147e2152d86b4d5669f95))
* **client:** make generate.video responses awaitable ([9c4e62a](https://github.com/runwayml/sdk-python/commit/9c4e62a8c13e96ec733f6efe7cfa3fd7f7589357))
-* **client:** type dry_run separately from waitable generate.video ([9bc89e1](https://github.com/runwayml/sdk-python/commit/9bc89e19db9aced896ebc5f4e5452bc3f187ab07))
## 5.10.0 (2026-07-13)