From 366a3eefecb5239ccd7842c7b333c77740fbe8bc Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 23 Jul 2026 00:14:54 +0000 Subject: [PATCH 1/6] fix(mux): field-level merge on network policy egress updates (#10338) --- .stats.yml | 4 ++-- src/runloop_api_client/resources/network_policies.py | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.stats.yml b/.stats.yml index 4e9c9d433..893d80de6 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 119 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai/runloop-0b7cd0c2fc193b18189cd7f44cf45ece7726b5d485fb72577f7d235266432ea0.yml -openapi_spec_hash: 78c340dbfb9d3d58b24ef318fc2a657b +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai/runloop-d0efdaa19d8a5685def3947450df7f9bdaf9bad3243317d514add5d22129fc5f.yml +openapi_spec_hash: 7e484ea9f60294503bf4691fe3efef3a config_hash: 218b8d25038e627faab98532392ee9a0 diff --git a/src/runloop_api_client/resources/network_policies.py b/src/runloop_api_client/resources/network_policies.py index b02062311..135eabea6 100644 --- a/src/runloop_api_client/resources/network_policies.py +++ b/src/runloop_api_client/resources/network_policies.py @@ -191,7 +191,8 @@ def update( ) -> NetworkPolicyView: """Update an existing NetworkPolicy. - All fields are optional. + All fields are optional - null fields preserve + existing values, provided fields replace entirely. Args: allow_agent_gateway: If true, allows devbox egress to the agent gateway. @@ -525,7 +526,8 @@ async def update( ) -> NetworkPolicyView: """Update an existing NetworkPolicy. - All fields are optional. + All fields are optional - null fields preserve + existing values, provided fields replace entirely. Args: allow_agent_gateway: If true, allows devbox egress to the agent gateway. From 7e86af9421218becca73ebaf0aa110c40c2a2e1a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 23 Jul 2026 23:29:38 +0000 Subject: [PATCH 2/6] feat(mux): wait_for_eviction endpoint for flex eviction notifications (#10325) --- .stats.yml | 8 +-- api.md | 2 + .../resources/devboxes/devboxes.py | 63 +++++++++++++++++++ src/runloop_api_client/types/__init__.py | 1 + .../types/devbox_watch_evictions_response.py | 16 +++++ tests/api_resources/test_devboxes.py | 51 +++++++++++++++ 6 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 src/runloop_api_client/types/devbox_watch_evictions_response.py diff --git a/.stats.yml b/.stats.yml index 893d80de6..8b2900ea6 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 119 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai/runloop-d0efdaa19d8a5685def3947450df7f9bdaf9bad3243317d514add5d22129fc5f.yml -openapi_spec_hash: 7e484ea9f60294503bf4691fe3efef3a -config_hash: 218b8d25038e627faab98532392ee9a0 +configured_endpoints: 120 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai/runloop-f18b1bba718700c841b3904207cd8ba2c066421a691845d6283bbbb361cf161d.yml +openapi_spec_hash: 6044f4a4da13881c5f82fcc394c1f43b +config_hash: d9a5b7a4227236a55c89de56b56f4aec diff --git a/api.md b/api.md index b7db70b09..eb80a0151 100644 --- a/api.md +++ b/api.md @@ -214,6 +214,7 @@ from runloop_api_client.types import ( TunnelView, DevboxCreateSSHKeyResponse, DevboxReadFileContentsResponse, + DevboxWatchEvictionsResponse, ) ``` @@ -244,6 +245,7 @@ Methods: - client.devboxes.suspend(id) -> DevboxView - client.devboxes.upload_file(id, \*\*params) -> object - client.devboxes.wait_for_command(execution_id, \*, devbox_id, \*\*params) -> DevboxAsyncExecutionDetailView +- client.devboxes.watch_evictions() -> DevboxWatchEvictionsResponse - client.devboxes.write_file_contents(id, \*\*params) -> DevboxExecutionDetailView diff --git a/src/runloop_api_client/resources/devboxes/devboxes.py b/src/runloop_api_client/resources/devboxes/devboxes.py index f100014f8..b66fc7abe 100644 --- a/src/runloop_api_client/resources/devboxes/devboxes.py +++ b/src/runloop_api_client/resources/devboxes/devboxes.py @@ -97,6 +97,7 @@ from ...types.devbox_resource_usage_view import DevboxResourceUsageView from ...types.devbox_execution_detail_view import DevboxExecutionDetailView from ...types.devbox_create_ssh_key_response import DevboxCreateSSHKeyResponse +from ...types.devbox_watch_evictions_response import DevboxWatchEvictionsResponse from ...types.shared_params.launch_parameters import LaunchParameters from ...types.devbox_async_execution_detail_view import DevboxAsyncExecutionDetailView from ...types.shared_params.code_mount_parameters import CodeMountParameters @@ -1742,6 +1743,31 @@ def wait_for_command( cast_to=DevboxAsyncExecutionDetailView, ) + def watch_evictions( + self, + *, + # 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, + ) -> DevboxWatchEvictionsResponse: + """ + Subscribe, via server-sent events, to pending infrastructure evictions for every + Devbox in the account. On connect the stream emits one event per Devbox that + currently has a pending eviction, then one event as each further eviction is + scheduled. Best-effort and advisory: a Devbox stays running until its deadline, + and delivery is not guaranteed. + """ + return self._get( + "/v1/devboxes/watch_evictions", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DevboxWatchEvictionsResponse, + ) + def write_file_contents( self, id: str, @@ -3417,6 +3443,31 @@ async def wait_for_command( cast_to=DevboxAsyncExecutionDetailView, ) + async def watch_evictions( + self, + *, + # 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, + ) -> DevboxWatchEvictionsResponse: + """ + Subscribe, via server-sent events, to pending infrastructure evictions for every + Devbox in the account. On connect the stream emits one event per Devbox that + currently has a pending eviction, then one event as each further eviction is + scheduled. Best-effort and advisory: a Devbox stays running until its deadline, + and delivery is not guaranteed. + """ + return await self._get( + "/v1/devboxes/watch_evictions", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DevboxWatchEvictionsResponse, + ) + async def write_file_contents( self, id: str, @@ -3555,6 +3606,9 @@ def __init__(self, devboxes: DevboxesResource) -> None: self.wait_for_command = to_raw_response_wrapper( devboxes.wait_for_command, ) + self.watch_evictions = to_raw_response_wrapper( + devboxes.watch_evictions, + ) self.write_file_contents = to_raw_response_wrapper( devboxes.write_file_contents, ) @@ -3651,6 +3705,9 @@ def __init__(self, devboxes: AsyncDevboxesResource) -> None: self.wait_for_command = async_to_raw_response_wrapper( devboxes.wait_for_command, ) + self.watch_evictions = async_to_raw_response_wrapper( + devboxes.watch_evictions, + ) self.write_file_contents = async_to_raw_response_wrapper( devboxes.write_file_contents, ) @@ -3747,6 +3804,9 @@ def __init__(self, devboxes: DevboxesResource) -> None: self.wait_for_command = to_streamed_response_wrapper( devboxes.wait_for_command, ) + self.watch_evictions = to_streamed_response_wrapper( + devboxes.watch_evictions, + ) self.write_file_contents = to_streamed_response_wrapper( devboxes.write_file_contents, ) @@ -3843,6 +3903,9 @@ def __init__(self, devboxes: AsyncDevboxesResource) -> None: self.wait_for_command = async_to_streamed_response_wrapper( devboxes.wait_for_command, ) + self.watch_evictions = async_to_streamed_response_wrapper( + devboxes.watch_evictions, + ) self.write_file_contents = async_to_streamed_response_wrapper( devboxes.write_file_contents, ) diff --git a/src/runloop_api_client/types/__init__.py b/src/runloop_api_client/types/__init__.py index 4abd94f92..ce1e7f950 100644 --- a/src/runloop_api_client/types/__init__.py +++ b/src/runloop_api_client/types/__init__.py @@ -139,6 +139,7 @@ from .blueprint_build_logs_list_view import BlueprintBuildLogsListView as BlueprintBuildLogsListView from .devbox_create_ssh_key_response import DevboxCreateSSHKeyResponse as DevboxCreateSSHKeyResponse from .devbox_wait_for_command_params import DevboxWaitForCommandParams as DevboxWaitForCommandParams +from .devbox_watch_evictions_response import DevboxWatchEvictionsResponse as DevboxWatchEvictionsResponse from .devbox_read_file_contents_params import DevboxReadFileContentsParams as DevboxReadFileContentsParams from .benchmark_update_scenarios_params import BenchmarkUpdateScenariosParams as BenchmarkUpdateScenariosParams from .devbox_list_disk_snapshots_params import DevboxListDiskSnapshotsParams as DevboxListDiskSnapshotsParams diff --git a/src/runloop_api_client/types/devbox_watch_evictions_response.py b/src/runloop_api_client/types/devbox_watch_evictions_response.py new file mode 100644 index 000000000..e9962df9b --- /dev/null +++ b/src/runloop_api_client/types/devbox_watch_evictions_response.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel + +__all__ = ["DevboxWatchEvictionsResponse"] + + +class DevboxWatchEvictionsResponse(BaseModel): + devbox_id: str + """The ID of the Devbox with a pending eviction.""" + + eviction_deadline_ms: int + """Unix timestamp (milliseconds) after which the Devbox will be suspended. + + Advisory and best-effort. + """ diff --git a/tests/api_resources/test_devboxes.py b/tests/api_resources/test_devboxes.py index 6c3e89c8f..03f758453 100644 --- a/tests/api_resources/test_devboxes.py +++ b/tests/api_resources/test_devboxes.py @@ -20,6 +20,7 @@ DevboxResourceUsageView, DevboxExecutionDetailView, DevboxCreateSSHKeyResponse, + DevboxWatchEvictionsResponse, DevboxAsyncExecutionDetailView, ) from runloop_api_client._response import ( @@ -1190,6 +1191,31 @@ def test_path_params_wait_for_command(self, client: Runloop) -> None: statuses=["queued"], ) + @parametrize + def test_method_watch_evictions(self, client: Runloop) -> None: + devbox = client.devboxes.watch_evictions() + assert_matches_type(DevboxWatchEvictionsResponse, devbox, path=["response"]) + + @parametrize + def test_raw_response_watch_evictions(self, client: Runloop) -> None: + response = client.devboxes.with_raw_response.watch_evictions() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + devbox = response.parse() + assert_matches_type(DevboxWatchEvictionsResponse, devbox, path=["response"]) + + @parametrize + def test_streaming_response_watch_evictions(self, client: Runloop) -> None: + with client.devboxes.with_streaming_response.watch_evictions() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + devbox = response.parse() + assert_matches_type(DevboxWatchEvictionsResponse, devbox, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_write_file_contents(self, client: Runloop) -> None: devbox = client.devboxes.write_file_contents( @@ -2859,6 +2885,31 @@ async def test_path_params_wait_for_command(self, async_client: AsyncRunloop) -> statuses=["queued"], ) + @parametrize + async def test_method_watch_evictions(self, async_client: AsyncRunloop) -> None: + devbox = await async_client.devboxes.watch_evictions() + assert_matches_type(DevboxWatchEvictionsResponse, devbox, path=["response"]) + + @parametrize + async def test_raw_response_watch_evictions(self, async_client: AsyncRunloop) -> None: + response = await async_client.devboxes.with_raw_response.watch_evictions() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + devbox = await response.parse() + assert_matches_type(DevboxWatchEvictionsResponse, devbox, path=["response"]) + + @parametrize + async def test_streaming_response_watch_evictions(self, async_client: AsyncRunloop) -> None: + async with async_client.devboxes.with_streaming_response.watch_evictions() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + devbox = await response.parse() + assert_matches_type(DevboxWatchEvictionsResponse, devbox, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_write_file_contents(self, async_client: AsyncRunloop) -> None: devbox = await async_client.devboxes.write_file_contents( From cb82bca1fe679a72499256738b18e2d270a321f8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 00:08:21 +0000 Subject: [PATCH 3/6] chore(stainless): generate watch_evictions as an SSE stream (#10404) --- .stats.yml | 4 +-- api.md | 4 +-- .../resources/devboxes/devboxes.py | 15 +++++++---- src/runloop_api_client/types/__init__.py | 2 +- ...ponse.py => devbox_eviction_event_view.py} | 4 +-- tests/api_resources/test_devboxes.py | 27 +++++++++---------- 6 files changed, 29 insertions(+), 27 deletions(-) rename src/runloop_api_client/types/{devbox_watch_evictions_response.py => devbox_eviction_event_view.py} (79%) diff --git a/.stats.yml b/.stats.yml index 8b2900ea6..10c388023 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 120 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai/runloop-f18b1bba718700c841b3904207cd8ba2c066421a691845d6283bbbb361cf161d.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai/runloop-6fb96a2de54d260cad2b76366cada4eda15335275dbab7f6c108c1bb29acf309.yml openapi_spec_hash: 6044f4a4da13881c5f82fcc394c1f43b -config_hash: d9a5b7a4227236a55c89de56b56f4aec +config_hash: 92c04f0b12249cb2d2bcf9e503a9cbf9 diff --git a/api.md b/api.md index eb80a0151..fc7f676a3 100644 --- a/api.md +++ b/api.md @@ -201,6 +201,7 @@ Types: ```python from runloop_api_client.types import ( DevboxAsyncExecutionDetailView, + DevboxEvictionEventView, DevboxExecutionDetailView, DevboxKillExecutionRequest, DevboxListView, @@ -214,7 +215,6 @@ from runloop_api_client.types import ( TunnelView, DevboxCreateSSHKeyResponse, DevboxReadFileContentsResponse, - DevboxWatchEvictionsResponse, ) ``` @@ -245,7 +245,7 @@ Methods: - client.devboxes.suspend(id) -> DevboxView - client.devboxes.upload_file(id, \*\*params) -> object - client.devboxes.wait_for_command(execution_id, \*, devbox_id, \*\*params) -> DevboxAsyncExecutionDetailView -- client.devboxes.watch_evictions() -> DevboxWatchEvictionsResponse +- client.devboxes.watch_evictions() -> DevboxEvictionEventView - client.devboxes.write_file_contents(id, \*\*params) -> DevboxExecutionDetailView diff --git a/src/runloop_api_client/resources/devboxes/devboxes.py b/src/runloop_api_client/resources/devboxes/devboxes.py index b66fc7abe..0a3fdade5 100644 --- a/src/runloop_api_client/resources/devboxes/devboxes.py +++ b/src/runloop_api_client/resources/devboxes/devboxes.py @@ -69,6 +69,7 @@ LONG_POLL_CLIENT_BUFFER_SECONDS, EXEC_LONG_POLL_SERVER_MAX_SECONDS, ) +from ..._streaming import Stream, AsyncStream from ...pagination import ( SyncDevboxesCursorIDPage, AsyncDevboxesCursorIDPage, @@ -94,10 +95,10 @@ from ...types.shared_params.mount import Mount from ...types.devbox_snapshot_view import DevboxSnapshotView from ...types.shared.launch_parameters import LaunchParameters as SharedLaunchParameters +from ...types.devbox_eviction_event_view import DevboxEvictionEventView from ...types.devbox_resource_usage_view import DevboxResourceUsageView from ...types.devbox_execution_detail_view import DevboxExecutionDetailView from ...types.devbox_create_ssh_key_response import DevboxCreateSSHKeyResponse -from ...types.devbox_watch_evictions_response import DevboxWatchEvictionsResponse from ...types.shared_params.launch_parameters import LaunchParameters from ...types.devbox_async_execution_detail_view import DevboxAsyncExecutionDetailView from ...types.shared_params.code_mount_parameters import CodeMountParameters @@ -1752,7 +1753,7 @@ def watch_evictions( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> DevboxWatchEvictionsResponse: + ) -> Stream[DevboxEvictionEventView]: """ Subscribe, via server-sent events, to pending infrastructure evictions for every Devbox in the account. On connect the stream emits one event per Devbox that @@ -1765,7 +1766,9 @@ def watch_evictions( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=DevboxWatchEvictionsResponse, + cast_to=DevboxEvictionEventView, + stream=True, + stream_cls=Stream[DevboxEvictionEventView], ) def write_file_contents( @@ -3452,7 +3455,7 @@ async def watch_evictions( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> DevboxWatchEvictionsResponse: + ) -> AsyncStream[DevboxEvictionEventView]: """ Subscribe, via server-sent events, to pending infrastructure evictions for every Devbox in the account. On connect the stream emits one event per Devbox that @@ -3465,7 +3468,9 @@ async def watch_evictions( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=DevboxWatchEvictionsResponse, + cast_to=DevboxEvictionEventView, + stream=True, + stream_cls=AsyncStream[DevboxEvictionEventView], ) async def write_file_contents( diff --git a/src/runloop_api_client/types/__init__.py b/src/runloop_api_client/types/__init__.py index ce1e7f950..0bda186a3 100644 --- a/src/runloop_api_client/types/__init__.py +++ b/src/runloop_api_client/types/__init__.py @@ -110,6 +110,7 @@ from .scenario_start_run_params import ScenarioStartRunParams as ScenarioStartRunParams from .benchmark_start_run_params import BenchmarkStartRunParams as BenchmarkStartRunParams from .blueprint_build_parameters import BlueprintBuildParameters as BlueprintBuildParameters +from .devbox_eviction_event_view import DevboxEvictionEventView as DevboxEvictionEventView from .devbox_execute_sync_params import DevboxExecuteSyncParams as DevboxExecuteSyncParams from .devbox_resource_usage_view import DevboxResourceUsageView as DevboxResourceUsageView from .gateway_config_list_params import GatewayConfigListParams as GatewayConfigListParams @@ -139,7 +140,6 @@ from .blueprint_build_logs_list_view import BlueprintBuildLogsListView as BlueprintBuildLogsListView from .devbox_create_ssh_key_response import DevboxCreateSSHKeyResponse as DevboxCreateSSHKeyResponse from .devbox_wait_for_command_params import DevboxWaitForCommandParams as DevboxWaitForCommandParams -from .devbox_watch_evictions_response import DevboxWatchEvictionsResponse as DevboxWatchEvictionsResponse from .devbox_read_file_contents_params import DevboxReadFileContentsParams as DevboxReadFileContentsParams from .benchmark_update_scenarios_params import BenchmarkUpdateScenariosParams as BenchmarkUpdateScenariosParams from .devbox_list_disk_snapshots_params import DevboxListDiskSnapshotsParams as DevboxListDiskSnapshotsParams diff --git a/src/runloop_api_client/types/devbox_watch_evictions_response.py b/src/runloop_api_client/types/devbox_eviction_event_view.py similarity index 79% rename from src/runloop_api_client/types/devbox_watch_evictions_response.py rename to src/runloop_api_client/types/devbox_eviction_event_view.py index e9962df9b..11d646c27 100644 --- a/src/runloop_api_client/types/devbox_watch_evictions_response.py +++ b/src/runloop_api_client/types/devbox_eviction_event_view.py @@ -2,10 +2,10 @@ from .._models import BaseModel -__all__ = ["DevboxWatchEvictionsResponse"] +__all__ = ["DevboxEvictionEventView"] -class DevboxWatchEvictionsResponse(BaseModel): +class DevboxEvictionEventView(BaseModel): devbox_id: str """The ID of the Devbox with a pending eviction.""" diff --git a/tests/api_resources/test_devboxes.py b/tests/api_resources/test_devboxes.py index 03f758453..0d9495f80 100644 --- a/tests/api_resources/test_devboxes.py +++ b/tests/api_resources/test_devboxes.py @@ -20,7 +20,6 @@ DevboxResourceUsageView, DevboxExecutionDetailView, DevboxCreateSSHKeyResponse, - DevboxWatchEvictionsResponse, DevboxAsyncExecutionDetailView, ) from runloop_api_client._response import ( @@ -1193,17 +1192,16 @@ def test_path_params_wait_for_command(self, client: Runloop) -> None: @parametrize def test_method_watch_evictions(self, client: Runloop) -> None: - devbox = client.devboxes.watch_evictions() - assert_matches_type(DevboxWatchEvictionsResponse, devbox, path=["response"]) + devbox_stream = client.devboxes.watch_evictions() + devbox_stream.response.close() @parametrize def test_raw_response_watch_evictions(self, client: Runloop) -> None: response = client.devboxes.with_raw_response.watch_evictions() - assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" - devbox = response.parse() - assert_matches_type(DevboxWatchEvictionsResponse, devbox, path=["response"]) + stream = response.parse() + stream.close() @parametrize def test_streaming_response_watch_evictions(self, client: Runloop) -> None: @@ -1211,8 +1209,8 @@ def test_streaming_response_watch_evictions(self, client: Runloop) -> None: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" - devbox = response.parse() - assert_matches_type(DevboxWatchEvictionsResponse, devbox, path=["response"]) + stream = response.parse() + stream.close() assert cast(Any, response.is_closed) is True @@ -2887,17 +2885,16 @@ async def test_path_params_wait_for_command(self, async_client: AsyncRunloop) -> @parametrize async def test_method_watch_evictions(self, async_client: AsyncRunloop) -> None: - devbox = await async_client.devboxes.watch_evictions() - assert_matches_type(DevboxWatchEvictionsResponse, devbox, path=["response"]) + devbox_stream = await async_client.devboxes.watch_evictions() + await devbox_stream.response.aclose() @parametrize async def test_raw_response_watch_evictions(self, async_client: AsyncRunloop) -> None: response = await async_client.devboxes.with_raw_response.watch_evictions() - assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" - devbox = await response.parse() - assert_matches_type(DevboxWatchEvictionsResponse, devbox, path=["response"]) + stream = await response.parse() + await stream.close() @parametrize async def test_streaming_response_watch_evictions(self, async_client: AsyncRunloop) -> None: @@ -2905,8 +2902,8 @@ async def test_streaming_response_watch_evictions(self, async_client: AsyncRunlo assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" - devbox = await response.parse() - assert_matches_type(DevboxWatchEvictionsResponse, devbox, path=["response"]) + stream = await response.parse() + await stream.close() assert cast(Any, response.is_closed) is True From 61f4143ae27b6efd1d1abda4a2ceceb725a1709d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 15:59:30 +0000 Subject: [PATCH 4/6] fix(mux): move eviction-watch SSE route off the /{id} slot (#10409) --- .stats.yml | 6 +++--- api.md | 2 +- src/runloop_api_client/resources/devboxes/devboxes.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.stats.yml b/.stats.yml index 10c388023..8b4084be5 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 120 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai/runloop-6fb96a2de54d260cad2b76366cada4eda15335275dbab7f6c108c1bb29acf309.yml -openapi_spec_hash: 6044f4a4da13881c5f82fcc394c1f43b -config_hash: 92c04f0b12249cb2d2bcf9e503a9cbf9 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai/runloop-50686c7a06e432373c5dd7e771ab1483a6ca0531b1dd9a10fc31c0cf2833bcdf.yml +openapi_spec_hash: a554b4d3815148c094778b53bfe20b21 +config_hash: 6d102da8b9fcc3d0deb42d7a707e78c6 diff --git a/api.md b/api.md index fc7f676a3..6becb60c5 100644 --- a/api.md +++ b/api.md @@ -245,7 +245,7 @@ Methods: - client.devboxes.suspend(id) -> DevboxView - client.devboxes.upload_file(id, \*\*params) -> object - client.devboxes.wait_for_command(execution_id, \*, devbox_id, \*\*params) -> DevboxAsyncExecutionDetailView -- client.devboxes.watch_evictions() -> DevboxEvictionEventView +- client.devboxes.watch_evictions() -> DevboxEvictionEventView - client.devboxes.write_file_contents(id, \*\*params) -> DevboxExecutionDetailView diff --git a/src/runloop_api_client/resources/devboxes/devboxes.py b/src/runloop_api_client/resources/devboxes/devboxes.py index 0a3fdade5..19578d24c 100644 --- a/src/runloop_api_client/resources/devboxes/devboxes.py +++ b/src/runloop_api_client/resources/devboxes/devboxes.py @@ -1762,7 +1762,7 @@ def watch_evictions( and delivery is not guaranteed. """ return self._get( - "/v1/devboxes/watch_evictions", + "/v1/devboxes/evictions/watch", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -3464,7 +3464,7 @@ async def watch_evictions( and delivery is not guaranteed. """ return await self._get( - "/v1/devboxes/watch_evictions", + "/v1/devboxes/evictions/watch", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), From b7362149f441126fb018c10489b18e25370bef1f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 28 Jul 2026 22:17:29 +0000 Subject: [PATCH 5/6] release: 1.27.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 19 +++++++++++++++++++ pyproject.toml | 2 +- src/runloop_api_client/_version.py | 2 +- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index bfaab56f6..4eb89879b 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.24.0" + ".": "1.27.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index de0a6c03f..a97258f1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## 1.27.0 (2026-07-28) + +Full Changelog: [v1.24.0...v1.27.0](https://github.com/runloopai/api-client-python/compare/v1.24.0...v1.27.0) + +### Features + +* **mux:** wait_for_eviction endpoint for flex eviction notifications ([#10325](https://github.com/runloopai/api-client-python/issues/10325)) ([7e86af9](https://github.com/runloopai/api-client-python/commit/7e86af9421218becca73ebaf0aa110c40c2a2e1a)) + + +### Bug Fixes + +* **mux:** field-level merge on network policy egress updates ([#10338](https://github.com/runloopai/api-client-python/issues/10338)) ([366a3ee](https://github.com/runloopai/api-client-python/commit/366a3eefecb5239ccd7842c7b333c77740fbe8bc)) +* **mux:** move eviction-watch SSE route off the /{id} slot ([#10409](https://github.com/runloopai/api-client-python/issues/10409)) ([61f4143](https://github.com/runloopai/api-client-python/commit/61f4143ae27b6efd1d1abda4a2ceceb725a1709d)) + + +### Chores + +* **stainless:** generate watch_evictions as an SSE stream ([#10404](https://github.com/runloopai/api-client-python/issues/10404)) ([cb82bca](https://github.com/runloopai/api-client-python/commit/cb82bca1fe679a72499256738b18e2d270a321f8)) + ## 1.24.0 (2026-07-22) Full Changelog: [v1.23.3...v1.24.0](https://github.com/runloopai/api-client-python/compare/v1.23.3...v1.24.0) diff --git a/pyproject.toml b/pyproject.toml index 77d099e9e..149da858b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "runloop_api_client" -version = "1.24.0" +version = "1.27.0" description = "The official Python library for the runloop API" dynamic = ["readme"] license = "MIT" diff --git a/src/runloop_api_client/_version.py b/src/runloop_api_client/_version.py index 5e32f9066..59ac2c1db 100644 --- a/src/runloop_api_client/_version.py +++ b/src/runloop_api_client/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "runloop_api_client" -__version__ = "1.24.0" # x-release-please-version +__version__ = "1.27.0" # x-release-please-version From a3e83a4b9c59da52ccee1f83e9e8811439b9144d Mon Sep 17 00:00:00 2001 From: Reflex Date: Wed, 29 Jul 2026 01:19:05 +0000 Subject: [PATCH 6/6] docs: note H2 pool isolation in 1.27.0 changelog Co-authored-by: Cursor --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a97258f1b..3416050b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Full Changelog: [v1.24.0...v1.27.0](https://github.com/runloopai/api-client-pyth ### Bug Fixes +* isolate long-polls and file transfers on separate HTTP/2 pools ([#825](https://github.com/runloopai/api-client-python/issues/825)) ([d316188](https://github.com/runloopai/api-client-python/commit/d3161880b655904c31664975bb6f210c4df623b7)) * **mux:** field-level merge on network policy egress updates ([#10338](https://github.com/runloopai/api-client-python/issues/10338)) ([366a3ee](https://github.com/runloopai/api-client-python/commit/366a3eefecb5239ccd7842c7b333c77740fbe8bc)) * **mux:** move eviction-watch SSE route off the /{id} slot ([#10409](https://github.com/runloopai/api-client-python/issues/10409)) ([61f4143](https://github.com/runloopai/api-client-python/commit/61f4143ae27b6efd1d1abda4a2ceceb725a1709d))