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)