Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "1.24.0"
".": "1.27.0"
}
8 changes: 4 additions & 4 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -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
config_hash: 218b8d25038e627faab98532392ee9a0
configured_endpoints: 120
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai/runloop-50686c7a06e432373c5dd7e771ab1483a6ca0531b1dd9a10fc31c0cf2833bcdf.yml
openapi_spec_hash: a554b4d3815148c094778b53bfe20b21
config_hash: 6d102da8b9fcc3d0deb42d7a707e78c6
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
2 changes: 2 additions & 0 deletions api.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ Types:
```python
from runloop_api_client.types import (
DevboxAsyncExecutionDetailView,
DevboxEvictionEventView,
DevboxExecutionDetailView,
DevboxKillExecutionRequest,
DevboxListView,
Expand Down Expand Up @@ -244,6 +245,7 @@ Methods:
- <code title="post /v1/devboxes/{id}/suspend">client.devboxes.<a href="./src/runloop_api_client/resources/devboxes/devboxes.py">suspend</a>(id) -> <a href="./src/runloop_api_client/types/devbox_view.py">DevboxView</a></code>
- <code title="post /v1/devboxes/{id}/upload_file">client.devboxes.<a href="./src/runloop_api_client/resources/devboxes/devboxes.py">upload_file</a>(id, \*\*<a href="src/runloop_api_client/types/devbox_upload_file_params.py">params</a>) -> object</code>
- <code title="post /v1/devboxes/{devbox_id}/executions/{execution_id}/wait_for_status">client.devboxes.<a href="./src/runloop_api_client/resources/devboxes/devboxes.py">wait_for_command</a>(execution_id, \*, devbox_id, \*\*<a href="src/runloop_api_client/types/devbox_wait_for_command_params.py">params</a>) -> <a href="./src/runloop_api_client/types/devbox_async_execution_detail_view.py">DevboxAsyncExecutionDetailView</a></code>
- <code title="get /v1/devboxes/evictions/watch">client.devboxes.<a href="./src/runloop_api_client/resources/devboxes/devboxes.py">watch_evictions</a>() -> <a href="./src/runloop_api_client/types/devbox_eviction_event_view.py">DevboxEvictionEventView</a></code>
- <code title="post /v1/devboxes/{id}/write_file_contents">client.devboxes.<a href="./src/runloop_api_client/resources/devboxes/devboxes.py">write_file_contents</a>(id, \*\*<a href="src/runloop_api_client/types/devbox_write_file_contents_params.py">params</a>) -> <a href="./src/runloop_api_client/types/devbox_execution_detail_view.py">DevboxExecutionDetailView</a></code>


Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
2 changes: 1 addition & 1 deletion src/runloop_api_client/_version.py
Original file line number Diff line number Diff line change
@@ -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
68 changes: 68 additions & 0 deletions src/runloop_api_client/resources/devboxes/devboxes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -94,6 +95,7 @@
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
Expand Down Expand Up @@ -1742,6 +1744,33 @@ 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,
) -> 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
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/evictions/watch",
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=DevboxEvictionEventView,
stream=True,
stream_cls=Stream[DevboxEvictionEventView],
)

def write_file_contents(
self,
id: str,
Expand Down Expand Up @@ -3417,6 +3446,33 @@ 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,
) -> 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
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/evictions/watch",
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=DevboxEvictionEventView,
stream=True,
stream_cls=AsyncStream[DevboxEvictionEventView],
)

async def write_file_contents(
self,
id: str,
Expand Down Expand Up @@ -3555,6 +3611,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,
)
Expand Down Expand Up @@ -3651,6 +3710,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,
)
Expand Down Expand Up @@ -3747,6 +3809,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,
)
Expand Down Expand Up @@ -3843,6 +3908,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,
)
Expand Down
6 changes: 4 additions & 2 deletions src/runloop_api_client/resources/network_policies.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions src/runloop_api_client/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 16 additions & 0 deletions src/runloop_api_client/types/devbox_eviction_event_view.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from .._models import BaseModel

__all__ = ["DevboxEvictionEventView"]


class DevboxEvictionEventView(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.
"""
48 changes: 48 additions & 0 deletions tests/api_resources/test_devboxes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1190,6 +1190,30 @@ def test_path_params_wait_for_command(self, client: Runloop) -> None:
statuses=["queued"],
)

@parametrize
def test_method_watch_evictions(self, client: Runloop) -> None:
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.http_request.headers.get("X-Stainless-Lang") == "python"
stream = response.parse()
stream.close()

@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"

stream = response.parse()
stream.close()

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(
Expand Down Expand Up @@ -2859,6 +2883,30 @@ 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_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.http_request.headers.get("X-Stainless-Lang") == "python"
stream = await response.parse()
await stream.close()

@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"

stream = await response.parse()
await stream.close()

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(
Expand Down