From ee5d5773d408ac8e61101fd0187ec071709bebab Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 18:25:42 +0000 Subject: [PATCH 01/13] chore: format all `api.md` files --- scripts/format | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/format b/scripts/format index 3f910b784..c8e1f69d2 100755 --- a/scripts/format +++ b/scripts/format @@ -11,4 +11,4 @@ uv run ruff check --fix . uv run ruff format echo "==> Formatting docs" -uv run python scripts/utils/ruffen-docs.py README.md api.md README-SDK.md +uv run python scripts/utils/ruffen-docs.py README.md $(find . -type f -name api.md) From 8b16ebe7aba9f96592f03ed2844211209b35d9e6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 01:44:10 +0000 Subject: [PATCH 02/13] refactor: deprecate and make Nullable total_count and remaining_count of ListViews (#7533) --- .stats.yml | 4 +-- api.md | 2 +- .../resources/devboxes/devboxes.py | 17 ++++++++-- src/runloop_api_client/types/__init__.py | 1 + .../types/agent_list_view.py | 16 +++++++--- .../types/benchmark_job_list_view.py | 6 ++-- .../types/benchmark_run_list_view.py | 8 ++--- .../types/blueprint_list_view.py | 6 ++-- .../types/devbox_list_view.py | 6 ++-- .../types/devbox_shutdown_params.py | 12 +++++++ .../types/devbox_snapshot_list_view.py | 8 ++--- .../types/gateway_config_list_view.py | 9 ++++-- .../types/mcp_config_list_view.py | 9 ++++-- .../types/network_policy_list_view.py | 9 ++++-- .../types/object_list_view.py | 16 +++++++--- .../types/repository_connection_list_view.py | 8 ++--- .../types/scenario_definition_list_view.py | 8 ++--- .../types/scenario_run_list_view.py | 8 ++--- src/runloop_api_client/types/scenario_view.py | 3 ++ .../types/secret_list_view.py | 18 +++++++---- tests/api_resources/test_devboxes.py | 32 ++++++++++++++----- 21 files changed, 139 insertions(+), 67 deletions(-) create mode 100644 src/runloop_api_client/types/devbox_shutdown_params.py diff --git a/.stats.yml b/.stats.yml index 3e985df2f..44040dae9 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 117 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-c8d61a0c8b88fa30666ba021d1239eb0d549902354513c4741e9216bcbfa8e6d.yml -openapi_spec_hash: 433e6fb4ce076012b696f69ae7596c67 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-8704a652545c3831c8b7e246ec17f1a626b59911eb5701ddc922ca6cae1c8d57.yml +openapi_spec_hash: e609bc5ed3fdce498bc7d16921bbba5e config_hash: eb28692edd68a6ae95cf92af931c9976 diff --git a/api.md b/api.md index 6cbe1e604..2c8438e72 100644 --- a/api.md +++ b/api.md @@ -160,7 +160,7 @@ Methods: - client.devboxes.remove_tunnel(id, \*\*params) -> object - client.devboxes.resume(id) -> DevboxView - client.devboxes.retrieve_resource_usage(id) -> DevboxResourceUsageView -- client.devboxes.shutdown(id) -> DevboxView +- client.devboxes.shutdown(id, \*\*params) -> DevboxView - client.devboxes.snapshot_disk(id, \*\*params) -> DevboxSnapshotView - client.devboxes.snapshot_disk_async(id, \*\*params) -> DevboxSnapshotView - client.devboxes.suspend(id) -> DevboxView diff --git a/src/runloop_api_client/resources/devboxes/devboxes.py b/src/runloop_api_client/resources/devboxes/devboxes.py index 79ac29f11..84a286521 100644 --- a/src/runloop_api_client/resources/devboxes/devboxes.py +++ b/src/runloop_api_client/resources/devboxes/devboxes.py @@ -24,6 +24,7 @@ devbox_create_params, devbox_update_params, devbox_execute_params, + devbox_shutdown_params, devbox_upload_file_params, devbox_execute_sync_params, devbox_create_tunnel_params, @@ -1415,6 +1416,7 @@ def shutdown( self, id: str, *, + force: 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, @@ -1427,9 +1429,13 @@ def shutdown( This will permanently stop the Devbox. If you want to save the state of the Devbox, you should take a snapshot before shutting down or - should suspend the Devbox instead of shutting down. + should suspend the Devbox instead of shutting down. If the Devbox has any + in-progress snapshots, the shutdown will be rejected with a 409 Conflict unless + force=true is specified. Args: + force: If true, force shutdown even if snapshots are in progress. Defaults to false. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -1450,6 +1456,7 @@ def shutdown( extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key, + query=maybe_transform({"force": force}, devbox_shutdown_params.DevboxShutdownParams), ), cast_to=DevboxView, ) @@ -3077,6 +3084,7 @@ async def shutdown( self, id: str, *, + force: 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, @@ -3089,9 +3097,13 @@ async def shutdown( This will permanently stop the Devbox. If you want to save the state of the Devbox, you should take a snapshot before shutting down or - should suspend the Devbox instead of shutting down. + should suspend the Devbox instead of shutting down. If the Devbox has any + in-progress snapshots, the shutdown will be rejected with a 409 Conflict unless + force=true is specified. Args: + force: If true, force shutdown even if snapshots are in progress. Defaults to false. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -3112,6 +3124,7 @@ async def shutdown( extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key, + query=await async_maybe_transform({"force": force}, devbox_shutdown_params.DevboxShutdownParams), ), cast_to=DevboxView, ) diff --git a/src/runloop_api_client/types/__init__.py b/src/runloop_api_client/types/__init__.py index e6543484f..2cd10b43d 100644 --- a/src/runloop_api_client/types/__init__.py +++ b/src/runloop_api_client/types/__init__.py @@ -55,6 +55,7 @@ from .blueprint_list_params import BlueprintListParams as BlueprintListParams from .devbox_execute_params import DevboxExecuteParams as DevboxExecuteParams from .blueprint_preview_view import BlueprintPreviewView as BlueprintPreviewView +from .devbox_shutdown_params import DevboxShutdownParams as DevboxShutdownParams from .mcp_config_list_params import McpConfigListParams as McpConfigListParams from .object_download_params import ObjectDownloadParams as ObjectDownloadParams from .repository_list_params import RepositoryListParams as RepositoryListParams diff --git a/src/runloop_api_client/types/agent_list_view.py b/src/runloop_api_client/types/agent_list_view.py index bfb1560e1..9e57a9769 100644 --- a/src/runloop_api_client/types/agent_list_view.py +++ b/src/runloop_api_client/types/agent_list_view.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List +from typing import List, Optional from .._models import BaseModel from .agent_view import AgentView @@ -17,8 +17,14 @@ class AgentListView(BaseModel): has_more: bool """Whether there are more Agents to fetch.""" - remaining_count: int - """The count of remaining Agents.""" + remaining_count: Optional[int] = None + """The count of remaining Agents. - total_count: int - """The total count of Agents.""" + Deprecated: will be removed in a future breaking change. + """ + + total_count: Optional[int] = None + """The total count of Agents. + + Deprecated: will be removed in a future breaking change. + """ diff --git a/src/runloop_api_client/types/benchmark_job_list_view.py b/src/runloop_api_client/types/benchmark_job_list_view.py index 5090fe8e8..f0e1da7d0 100644 --- a/src/runloop_api_client/types/benchmark_job_list_view.py +++ b/src/runloop_api_client/types/benchmark_job_list_view.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List +from typing import List, Optional from .._models import BaseModel from .benchmark_job_view import BenchmarkJobView @@ -14,6 +14,6 @@ class BenchmarkJobListView(BaseModel): jobs: List[BenchmarkJobView] """List of BenchmarkJobs matching filter.""" - remaining_count: int + remaining_count: Optional[int] = None - total_count: int + total_count: Optional[int] = None diff --git a/src/runloop_api_client/types/benchmark_run_list_view.py b/src/runloop_api_client/types/benchmark_run_list_view.py index 0d0ca11e1..e85506bab 100644 --- a/src/runloop_api_client/types/benchmark_run_list_view.py +++ b/src/runloop_api_client/types/benchmark_run_list_view.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List +from typing import List, Optional from .._models import BaseModel from .benchmark_run_view import BenchmarkRunView @@ -11,9 +11,9 @@ class BenchmarkRunListView(BaseModel): has_more: bool - remaining_count: int - runs: List[BenchmarkRunView] """List of BenchmarkRuns matching filter.""" - total_count: int + remaining_count: Optional[int] = None + + total_count: Optional[int] = None diff --git a/src/runloop_api_client/types/blueprint_list_view.py b/src/runloop_api_client/types/blueprint_list_view.py index ba7c6066f..7d97314f5 100644 --- a/src/runloop_api_client/types/blueprint_list_view.py +++ b/src/runloop_api_client/types/blueprint_list_view.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List +from typing import List, Optional from .._models import BaseModel from .blueprint_view import BlueprintView @@ -14,6 +14,6 @@ class BlueprintListView(BaseModel): has_more: bool - remaining_count: int + remaining_count: Optional[int] = None - total_count: int + total_count: Optional[int] = None diff --git a/src/runloop_api_client/types/devbox_list_view.py b/src/runloop_api_client/types/devbox_list_view.py index f4f266240..6bd522ee5 100644 --- a/src/runloop_api_client/types/devbox_list_view.py +++ b/src/runloop_api_client/types/devbox_list_view.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List +from typing import List, Optional from .._models import BaseModel from .devbox_view import DevboxView @@ -14,6 +14,6 @@ class DevboxListView(BaseModel): has_more: bool - remaining_count: int + remaining_count: Optional[int] = None - total_count: int + total_count: Optional[int] = None diff --git a/src/runloop_api_client/types/devbox_shutdown_params.py b/src/runloop_api_client/types/devbox_shutdown_params.py new file mode 100644 index 000000000..1a6af9336 --- /dev/null +++ b/src/runloop_api_client/types/devbox_shutdown_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["DevboxShutdownParams"] + + +class DevboxShutdownParams(TypedDict, total=False): + force: str + """If true, force shutdown even if snapshots are in progress. Defaults to false.""" diff --git a/src/runloop_api_client/types/devbox_snapshot_list_view.py b/src/runloop_api_client/types/devbox_snapshot_list_view.py index 3e687f0cc..ca54f2eb1 100644 --- a/src/runloop_api_client/types/devbox_snapshot_list_view.py +++ b/src/runloop_api_client/types/devbox_snapshot_list_view.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List +from typing import List, Optional from .._models import BaseModel from .devbox_snapshot_view import DevboxSnapshotView @@ -11,9 +11,9 @@ class DevboxSnapshotListView(BaseModel): has_more: bool - remaining_count: int - snapshots: List[DevboxSnapshotView] """List of snapshots matching filter.""" - total_count: int + remaining_count: Optional[int] = None + + total_count: Optional[int] = None diff --git a/src/runloop_api_client/types/gateway_config_list_view.py b/src/runloop_api_client/types/gateway_config_list_view.py index 77fce4455..05511971f 100644 --- a/src/runloop_api_client/types/gateway_config_list_view.py +++ b/src/runloop_api_client/types/gateway_config_list_view.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List +from typing import List, Optional from .._models import BaseModel from .gateway_config_view import GatewayConfigView @@ -17,5 +17,8 @@ class GatewayConfigListView(BaseModel): has_more: bool """Whether there are more results available beyond this page.""" - total_count: int - """Total count of GatewayConfigs that match the query.""" + total_count: Optional[int] = None + """Total count of GatewayConfigs that match the query. + + Deprecated: will be removed in a future breaking change. + """ diff --git a/src/runloop_api_client/types/mcp_config_list_view.py b/src/runloop_api_client/types/mcp_config_list_view.py index 72075a255..4992698c5 100644 --- a/src/runloop_api_client/types/mcp_config_list_view.py +++ b/src/runloop_api_client/types/mcp_config_list_view.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List +from typing import List, Optional from .._models import BaseModel from .mcp_config_view import McpConfigView @@ -17,5 +17,8 @@ class McpConfigListView(BaseModel): mcp_configs: List[McpConfigView] """The list of McpConfigs.""" - total_count: int - """Total count of McpConfigs that match the query.""" + total_count: Optional[int] = None + """Total count of McpConfigs that match the query. + + Deprecated: will be removed in a future breaking change. + """ diff --git a/src/runloop_api_client/types/network_policy_list_view.py b/src/runloop_api_client/types/network_policy_list_view.py index 17bc7868f..ba031d0a9 100644 --- a/src/runloop_api_client/types/network_policy_list_view.py +++ b/src/runloop_api_client/types/network_policy_list_view.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List +from typing import List, Optional from .._models import BaseModel from .network_policy_view import NetworkPolicyView @@ -17,5 +17,8 @@ class NetworkPolicyListView(BaseModel): network_policies: List[NetworkPolicyView] """The list of NetworkPolicies.""" - total_count: int - """Total count of items in this response.""" + total_count: Optional[int] = None + """Total count of items in this response. + + Deprecated: will be removed in a future breaking change. + """ diff --git a/src/runloop_api_client/types/object_list_view.py b/src/runloop_api_client/types/object_list_view.py index cfd546c0c..689c0899b 100644 --- a/src/runloop_api_client/types/object_list_view.py +++ b/src/runloop_api_client/types/object_list_view.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List +from typing import List, Optional from .._models import BaseModel from .object_view import ObjectView @@ -17,8 +17,14 @@ class ObjectListView(BaseModel): objects: List[ObjectView] """List of Object entities.""" - remaining_count: int - """Number of Objects remaining after this page.""" + remaining_count: Optional[int] = None + """Number of Objects remaining after this page. - total_count: int - """Total number of Objects across all pages.""" + Deprecated: will be removed in a future breaking change. + """ + + total_count: Optional[int] = None + """Total number of Objects across all pages. + + Deprecated: will be removed in a future breaking change. + """ diff --git a/src/runloop_api_client/types/repository_connection_list_view.py b/src/runloop_api_client/types/repository_connection_list_view.py index 8085c4718..eea040bc4 100644 --- a/src/runloop_api_client/types/repository_connection_list_view.py +++ b/src/runloop_api_client/types/repository_connection_list_view.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List +from typing import List, Optional from .._models import BaseModel from .repository_connection_view import RepositoryConnectionView @@ -11,9 +11,9 @@ class RepositoryConnectionListView(BaseModel): has_more: bool - remaining_count: int - repositories: List[RepositoryConnectionView] """List of repositories matching filter.""" - total_count: int + remaining_count: Optional[int] = None + + total_count: Optional[int] = None diff --git a/src/runloop_api_client/types/scenario_definition_list_view.py b/src/runloop_api_client/types/scenario_definition_list_view.py index 5aa4ac3e4..f39cf1ed9 100644 --- a/src/runloop_api_client/types/scenario_definition_list_view.py +++ b/src/runloop_api_client/types/scenario_definition_list_view.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List +from typing import List, Optional from .._models import BaseModel from .scenario_view import ScenarioView @@ -11,9 +11,9 @@ class ScenarioDefinitionListView(BaseModel): has_more: bool - remaining_count: int - scenarios: List[ScenarioView] """List of Scenarios matching filter.""" - total_count: int + remaining_count: Optional[int] = None + + total_count: Optional[int] = None diff --git a/src/runloop_api_client/types/scenario_run_list_view.py b/src/runloop_api_client/types/scenario_run_list_view.py index db0d16d31..142292dda 100644 --- a/src/runloop_api_client/types/scenario_run_list_view.py +++ b/src/runloop_api_client/types/scenario_run_list_view.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List +from typing import List, Optional from .._models import BaseModel from .scenario_run_view import ScenarioRunView @@ -11,9 +11,9 @@ class ScenarioRunListView(BaseModel): has_more: bool - remaining_count: int - runs: List[ScenarioRunView] """List of ScenarioRuns matching filter.""" - total_count: int + remaining_count: Optional[int] = None + + total_count: Optional[int] = None diff --git a/src/runloop_api_client/types/scenario_view.py b/src/runloop_api_client/types/scenario_view.py index 4604bd957..6685bbce2 100644 --- a/src/runloop_api_client/types/scenario_view.py +++ b/src/runloop_api_client/types/scenario_view.py @@ -31,6 +31,9 @@ class ScenarioView(BaseModel): scoring_contract: ScoringContract """The scoring contract for the Scenario.""" + status: str + """The state of the scenario.""" + environment: Optional[ScenarioEnvironment] = None """The Environment in which the Scenario is run.""" diff --git a/src/runloop_api_client/types/secret_list_view.py b/src/runloop_api_client/types/secret_list_view.py index 4d66fa2e4..2f10bae2c 100644 --- a/src/runloop_api_client/types/secret_list_view.py +++ b/src/runloop_api_client/types/secret_list_view.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List +from typing import List, Optional from .._models import BaseModel from .secret_view import SecretView @@ -14,11 +14,17 @@ class SecretListView(BaseModel): has_more: bool """True if there are more results available beyond this page.""" - remaining_count: int - """Number of Secrets remaining after this page.""" - secrets: List[SecretView] """List of Secret objects. Values are omitted for security.""" - total_count: int - """Total number of Secrets across all pages.""" + remaining_count: Optional[int] = None + """Number of Secrets remaining after this page. + + Deprecated: will be removed in a future breaking change. + """ + + total_count: Optional[int] = None + """Total number of Secrets across all pages. + + Deprecated: will be removed in a future breaking change. + """ diff --git a/tests/api_resources/test_devboxes.py b/tests/api_resources/test_devboxes.py index eef022a71..12c9f5187 100644 --- a/tests/api_resources/test_devboxes.py +++ b/tests/api_resources/test_devboxes.py @@ -893,14 +893,22 @@ def test_path_params_retrieve_resource_usage(self, client: Runloop) -> None: @parametrize def test_method_shutdown(self, client: Runloop) -> None: devbox = client.devboxes.shutdown( - "id", + id="id", + ) + assert_matches_type(DevboxView, devbox, path=["response"]) + + @parametrize + def test_method_shutdown_with_all_params(self, client: Runloop) -> None: + devbox = client.devboxes.shutdown( + id="id", + force="force", ) assert_matches_type(DevboxView, devbox, path=["response"]) @parametrize def test_raw_response_shutdown(self, client: Runloop) -> None: response = client.devboxes.with_raw_response.shutdown( - "id", + id="id", ) assert response.is_closed is True @@ -911,7 +919,7 @@ def test_raw_response_shutdown(self, client: Runloop) -> None: @parametrize def test_streaming_response_shutdown(self, client: Runloop) -> None: with client.devboxes.with_streaming_response.shutdown( - "id", + id="id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -925,7 +933,7 @@ def test_streaming_response_shutdown(self, client: Runloop) -> None: def test_path_params_shutdown(self, client: Runloop) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): client.devboxes.with_raw_response.shutdown( - "", + id="", ) @parametrize @@ -2549,14 +2557,22 @@ async def test_path_params_retrieve_resource_usage(self, async_client: AsyncRunl @parametrize async def test_method_shutdown(self, async_client: AsyncRunloop) -> None: devbox = await async_client.devboxes.shutdown( - "id", + id="id", + ) + assert_matches_type(DevboxView, devbox, path=["response"]) + + @parametrize + async def test_method_shutdown_with_all_params(self, async_client: AsyncRunloop) -> None: + devbox = await async_client.devboxes.shutdown( + id="id", + force="force", ) assert_matches_type(DevboxView, devbox, path=["response"]) @parametrize async def test_raw_response_shutdown(self, async_client: AsyncRunloop) -> None: response = await async_client.devboxes.with_raw_response.shutdown( - "id", + id="id", ) assert response.is_closed is True @@ -2567,7 +2583,7 @@ async def test_raw_response_shutdown(self, async_client: AsyncRunloop) -> None: @parametrize async def test_streaming_response_shutdown(self, async_client: AsyncRunloop) -> None: async with async_client.devboxes.with_streaming_response.shutdown( - "id", + id="id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -2581,7 +2597,7 @@ async def test_streaming_response_shutdown(self, async_client: AsyncRunloop) -> async def test_path_params_shutdown(self, async_client: AsyncRunloop) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): await async_client.devboxes.with_raw_response.shutdown( - "", + id="", ) @parametrize From e6e5208c19360f24f10b3c0da84119f7eb356bb1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 01:02:11 +0000 Subject: [PATCH 03/13] chore(scenarios): make scenario status enum instead of string (#7552) --- .stats.yml | 4 ++-- src/runloop_api_client/types/scenario_view.py | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.stats.yml b/.stats.yml index 44040dae9..27e05a0be 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 117 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-8704a652545c3831c8b7e246ec17f1a626b59911eb5701ddc922ca6cae1c8d57.yml -openapi_spec_hash: e609bc5ed3fdce498bc7d16921bbba5e +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-1ee005e7d8f97067abb96c6a38af53b69d362f2511ca775a9b78580a9643c253.yml +openapi_spec_hash: 9c54d19abf7993fd55714759f3265f9c config_hash: eb28692edd68a6ae95cf92af931c9976 diff --git a/src/runloop_api_client/types/scenario_view.py b/src/runloop_api_client/types/scenario_view.py index 6685bbce2..3bb470708 100644 --- a/src/runloop_api_client/types/scenario_view.py +++ b/src/runloop_api_client/types/scenario_view.py @@ -31,8 +31,11 @@ class ScenarioView(BaseModel): scoring_contract: ScoringContract """The scoring contract for the Scenario.""" - status: str - """The state of the scenario.""" + status: Literal["active", "archived"] + """Whether the scenario is active or archived. + + Archived scenarios are excluded from listings and cannot be updated. + """ environment: Optional[ScenarioEnvironment] = None """The Environment in which the Scenario is run.""" From 273fb5ec761bcc5948571d9757780d86b6f0f50d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 25 Feb 2026 20:32:18 +0000 Subject: [PATCH 04/13] chore: update mock server docs --- CONTRIBUTING.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9201cf4cd..cdf385047 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -88,8 +88,7 @@ $ pip install ./path-to-wheel-file.whl Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests. ```sh -# you will need npm installed -npx prism mock path/to/your/openapi.yml +$ ./scripts/mock ``` ```sh From 836f18d52c20b4b43f9135a52571f8c7e87a0477 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 00:39:49 +0000 Subject: [PATCH 05/13] chore: Add archive method to stainless (#7537) --- .stats.yml | 8 +- api.md | 1 + .../resources/devboxes/devboxes.py | 22 ++++- .../resources/scenarios/scenarios.py | 96 +++++++++++++++++++ .../types/devbox_create_params.py | 6 ++ .../types/devbox_enable_tunnel_params.py | 6 ++ src/runloop_api_client/types/tunnel_view.py | 6 ++ tests/api_resources/test_devboxes.py | 12 ++- tests/api_resources/test_scenarios.py | 76 +++++++++++++++ 9 files changed, 225 insertions(+), 8 deletions(-) diff --git a/.stats.yml b/.stats.yml index 27e05a0be..a869301e9 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 117 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-1ee005e7d8f97067abb96c6a38af53b69d362f2511ca775a9b78580a9643c253.yml -openapi_spec_hash: 9c54d19abf7993fd55714759f3265f9c -config_hash: eb28692edd68a6ae95cf92af931c9976 +configured_endpoints: 118 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-1a92de99959f20ab5850a7f89db21cb66269b2e0002ffc5e5afbe660d05b0768.yml +openapi_spec_hash: 41e4660f26e27e6ab94d223da4ec1253 +config_hash: cbda3692cb48ab8582a0df1674b9e5c8 diff --git a/api.md b/api.md index 2c8438e72..1c0166794 100644 --- a/api.md +++ b/api.md @@ -277,6 +277,7 @@ Methods: - client.scenarios.retrieve(id) -> ScenarioView - client.scenarios.update(id, \*\*params) -> ScenarioView - client.scenarios.list(\*\*params) -> SyncScenariosCursorIDPage[ScenarioView] +- client.scenarios.archive(id) -> ScenarioView - client.scenarios.list_public(\*\*params) -> SyncScenariosCursorIDPage[ScenarioView] - client.scenarios.start_run(\*\*params) -> ScenarioRunView diff --git a/src/runloop_api_client/resources/devboxes/devboxes.py b/src/runloop_api_client/resources/devboxes/devboxes.py index 84a286521..79ac0022d 100644 --- a/src/runloop_api_client/resources/devboxes/devboxes.py +++ b/src/runloop_api_client/resources/devboxes/devboxes.py @@ -807,6 +807,7 @@ def enable_tunnel( id: str, *, auth_mode: Optional[Literal["open", "authenticated"]] | Omit = omit, + http_keep_alive: Optional[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, @@ -826,6 +827,9 @@ def enable_tunnel( Args: auth_mode: Authentication mode for the tunnel. Defaults to 'public' if not specified. + http_keep_alive: When true, HTTP traffic through the tunnel counts as activity for idle lifecycle + policies, resetting the idle timer. Defaults to true if not specified. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -840,7 +844,13 @@ def enable_tunnel( raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( f"/v1/devboxes/{id}/enable_tunnel", - body=maybe_transform({"auth_mode": auth_mode}, devbox_enable_tunnel_params.DevboxEnableTunnelParams), + body=maybe_transform( + { + "auth_mode": auth_mode, + "http_keep_alive": http_keep_alive, + }, + devbox_enable_tunnel_params.DevboxEnableTunnelParams, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -2472,6 +2482,7 @@ async def enable_tunnel( id: str, *, auth_mode: Optional[Literal["open", "authenticated"]] | Omit = omit, + http_keep_alive: Optional[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, @@ -2491,6 +2502,9 @@ async def enable_tunnel( Args: auth_mode: Authentication mode for the tunnel. Defaults to 'public' if not specified. + http_keep_alive: When true, HTTP traffic through the tunnel counts as activity for idle lifecycle + policies, resetting the idle timer. Defaults to true if not specified. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -2506,7 +2520,11 @@ async def enable_tunnel( return await self._post( f"/v1/devboxes/{id}/enable_tunnel", body=await async_maybe_transform( - {"auth_mode": auth_mode}, devbox_enable_tunnel_params.DevboxEnableTunnelParams + { + "auth_mode": auth_mode, + "http_keep_alive": http_keep_alive, + }, + devbox_enable_tunnel_params.DevboxEnableTunnelParams, ), options=make_request_options( extra_headers=extra_headers, diff --git a/src/runloop_api_client/resources/scenarios/scenarios.py b/src/runloop_api_client/resources/scenarios/scenarios.py index b4a822cd3..5c31e4282 100644 --- a/src/runloop_api_client/resources/scenarios/scenarios.py +++ b/src/runloop_api_client/resources/scenarios/scenarios.py @@ -353,6 +353,48 @@ def list( model=ScenarioView, ) + def archive( + 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, + idempotency_key: str | None = None, + ) -> ScenarioView: + """Archive a previously created Scenario. + + The scenario will no longer appear in + list endpoints but can still be retrieved 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 + + idempotency_key: Specify a custom idempotency key for this request + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._post( + f"/v1/scenarios/{id}/archive", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=ScenarioView, + ) + def list_public( self, *, @@ -823,6 +865,48 @@ def list( model=ScenarioView, ) + async def archive( + 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, + idempotency_key: str | None = None, + ) -> ScenarioView: + """Archive a previously created Scenario. + + The scenario will no longer appear in + list endpoints but can still be retrieved 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 + + idempotency_key: Specify a custom idempotency key for this request + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._post( + f"/v1/scenarios/{id}/archive", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=ScenarioView, + ) + def list_public( self, *, @@ -1005,6 +1089,9 @@ def __init__(self, scenarios: ScenariosResource) -> None: self.list = to_raw_response_wrapper( scenarios.list, ) + self.archive = to_raw_response_wrapper( + scenarios.archive, + ) self.list_public = to_raw_response_wrapper( scenarios.list_public, ) @@ -1037,6 +1124,9 @@ def __init__(self, scenarios: AsyncScenariosResource) -> None: self.list = async_to_raw_response_wrapper( scenarios.list, ) + self.archive = async_to_raw_response_wrapper( + scenarios.archive, + ) self.list_public = async_to_raw_response_wrapper( scenarios.list_public, ) @@ -1069,6 +1159,9 @@ def __init__(self, scenarios: ScenariosResource) -> None: self.list = to_streamed_response_wrapper( scenarios.list, ) + self.archive = to_streamed_response_wrapper( + scenarios.archive, + ) self.list_public = to_streamed_response_wrapper( scenarios.list_public, ) @@ -1101,6 +1194,9 @@ def __init__(self, scenarios: AsyncScenariosResource) -> None: self.list = async_to_streamed_response_wrapper( scenarios.list, ) + self.archive = async_to_streamed_response_wrapper( + scenarios.archive, + ) self.list_public = async_to_streamed_response_wrapper( scenarios.list_public, ) diff --git a/src/runloop_api_client/types/devbox_create_params.py b/src/runloop_api_client/types/devbox_create_params.py index ae2f0db59..f916d55f4 100644 --- a/src/runloop_api_client/types/devbox_create_params.py +++ b/src/runloop_api_client/types/devbox_create_params.py @@ -142,3 +142,9 @@ class Tunnel(TypedDict, total=False): auth_mode: Optional[Literal["open", "authenticated"]] """Authentication mode for the tunnel. Defaults to 'public' if not specified.""" + + http_keep_alive: Optional[bool] + """ + When true, HTTP traffic through the tunnel counts as activity for idle lifecycle + policies, resetting the idle timer. Defaults to true if not specified. + """ diff --git a/src/runloop_api_client/types/devbox_enable_tunnel_params.py b/src/runloop_api_client/types/devbox_enable_tunnel_params.py index 9ccc53e75..4906c85ef 100644 --- a/src/runloop_api_client/types/devbox_enable_tunnel_params.py +++ b/src/runloop_api_client/types/devbox_enable_tunnel_params.py @@ -11,3 +11,9 @@ class DevboxEnableTunnelParams(TypedDict, total=False): auth_mode: Optional[Literal["open", "authenticated"]] """Authentication mode for the tunnel. Defaults to 'public' if not specified.""" + + http_keep_alive: Optional[bool] + """ + When true, HTTP traffic through the tunnel counts as activity for idle lifecycle + policies, resetting the idle timer. Defaults to true if not specified. + """ diff --git a/src/runloop_api_client/types/tunnel_view.py b/src/runloop_api_client/types/tunnel_view.py index 540a44e7c..9287cd4d2 100644 --- a/src/runloop_api_client/types/tunnel_view.py +++ b/src/runloop_api_client/types/tunnel_view.py @@ -21,6 +21,12 @@ class TunnelView(BaseModel): create_time_ms: int """Creation time of the tunnel (Unix timestamp milliseconds).""" + http_keep_alive: bool + """ + When true, HTTP traffic through the tunnel counts as activity for idle lifecycle + policies, resetting the idle timer. + """ + tunnel_key: str """The encrypted tunnel key used to construct the tunnel URL. diff --git a/tests/api_resources/test_devboxes.py b/tests/api_resources/test_devboxes.py index 12c9f5187..c6f6625e3 100644 --- a/tests/api_resources/test_devboxes.py +++ b/tests/api_resources/test_devboxes.py @@ -111,7 +111,10 @@ def test_method_create_with_all_params(self, client: Runloop) -> None: repo_connection_id="repo_connection_id", secrets={"foo": "string"}, snapshot_id="snapshot_id", - tunnel={"auth_mode": "open"}, + tunnel={ + "auth_mode": "open", + "http_keep_alive": True, + }, ) assert_matches_type(DevboxView, devbox, path=["response"]) @@ -447,6 +450,7 @@ def test_method_enable_tunnel_with_all_params(self, client: Runloop) -> None: devbox = client.devboxes.enable_tunnel( id="id", auth_mode="open", + http_keep_alive=True, ) assert_matches_type(TunnelView, devbox, path=["response"]) @@ -1775,7 +1779,10 @@ async def test_method_create_with_all_params(self, async_client: AsyncRunloop) - repo_connection_id="repo_connection_id", secrets={"foo": "string"}, snapshot_id="snapshot_id", - tunnel={"auth_mode": "open"}, + tunnel={ + "auth_mode": "open", + "http_keep_alive": True, + }, ) assert_matches_type(DevboxView, devbox, path=["response"]) @@ -2111,6 +2118,7 @@ async def test_method_enable_tunnel_with_all_params(self, async_client: AsyncRun devbox = await async_client.devboxes.enable_tunnel( id="id", auth_mode="open", + http_keep_alive=True, ) assert_matches_type(TunnelView, devbox, path=["response"]) diff --git a/tests/api_resources/test_scenarios.py b/tests/api_resources/test_scenarios.py index 5b79fd558..736a8395c 100644 --- a/tests/api_resources/test_scenarios.py +++ b/tests/api_resources/test_scenarios.py @@ -319,6 +319,44 @@ def test_streaming_response_list(self, client: Runloop) -> None: assert cast(Any, response.is_closed) is True + @parametrize + def test_method_archive(self, client: Runloop) -> None: + scenario = client.scenarios.archive( + "id", + ) + assert_matches_type(ScenarioView, scenario, path=["response"]) + + @parametrize + def test_raw_response_archive(self, client: Runloop) -> None: + response = client.scenarios.with_raw_response.archive( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + scenario = response.parse() + assert_matches_type(ScenarioView, scenario, path=["response"]) + + @parametrize + def test_streaming_response_archive(self, client: Runloop) -> None: + with client.scenarios.with_streaming_response.archive( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + scenario = response.parse() + assert_matches_type(ScenarioView, scenario, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_archive(self, client: Runloop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.scenarios.with_raw_response.archive( + "", + ) + @parametrize def test_method_list_public(self, client: Runloop) -> None: scenario = client.scenarios.list_public() @@ -730,6 +768,44 @@ async def test_streaming_response_list(self, async_client: AsyncRunloop) -> None assert cast(Any, response.is_closed) is True + @parametrize + async def test_method_archive(self, async_client: AsyncRunloop) -> None: + scenario = await async_client.scenarios.archive( + "id", + ) + assert_matches_type(ScenarioView, scenario, path=["response"]) + + @parametrize + async def test_raw_response_archive(self, async_client: AsyncRunloop) -> None: + response = await async_client.scenarios.with_raw_response.archive( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + scenario = await response.parse() + assert_matches_type(ScenarioView, scenario, path=["response"]) + + @parametrize + async def test_streaming_response_archive(self, async_client: AsyncRunloop) -> None: + async with async_client.scenarios.with_streaming_response.archive( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + scenario = await response.parse() + assert_matches_type(ScenarioView, scenario, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_archive(self, async_client: AsyncRunloop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.scenarios.with_raw_response.archive( + "", + ) + @parametrize async def test_method_list_public(self, async_client: AsyncRunloop) -> None: scenario = await async_client.scenarios.list_public() From e636aca4eedb81b35d80bd6f5229cc71153ec545 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 04:21:08 +0000 Subject: [PATCH 06/13] chore(internal): add request options to SSE classes --- src/runloop_api_client/_response.py | 3 +++ src/runloop_api_client/_streaming.py | 9 +++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/runloop_api_client/_response.py b/src/runloop_api_client/_response.py index 31591fad8..ddafda4fe 100644 --- a/src/runloop_api_client/_response.py +++ b/src/runloop_api_client/_response.py @@ -152,6 +152,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: ), response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) @@ -162,6 +163,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: cast_to=extract_stream_chunk_type(self._stream_cls), response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) @@ -175,6 +177,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: cast_to=cast_to, response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) diff --git a/src/runloop_api_client/_streaming.py b/src/runloop_api_client/_streaming.py index 509365648..ec516672b 100644 --- a/src/runloop_api_client/_streaming.py +++ b/src/runloop_api_client/_streaming.py @@ -32,6 +32,7 @@ if TYPE_CHECKING: from ._client import Runloop, AsyncRunloop + from ._models import FinalRequestOptions _T = TypeVar("_T") @@ -41,7 +42,7 @@ class Stream(Generic[_T]): """Provides the core interface to iterate over a synchronous stream response.""" response: httpx.Response - + _options: Optional[FinalRequestOptions] = None _decoder: SSEBytesDecoder def __init__( @@ -50,10 +51,12 @@ def __init__( cast_to: type[_T], response: httpx.Response, client: Runloop, + options: Optional[FinalRequestOptions] = None, ) -> None: self.response = response self._cast_to = cast_to self._client = client + self._options = options self._decoder = client._make_sse_decoder() self._iterator = self.__stream__() @@ -114,7 +117,7 @@ class AsyncStream(Generic[_T]): """Provides the core interface to iterate over an asynchronous stream response.""" response: httpx.Response - + _options: Optional[FinalRequestOptions] = None _decoder: SSEDecoder | SSEBytesDecoder def __init__( @@ -123,10 +126,12 @@ def __init__( cast_to: type[_T], response: httpx.Response, client: AsyncRunloop, + options: Optional[FinalRequestOptions] = None, ) -> None: self.response = response self._cast_to = cast_to self._client = client + self._options = options self._decoder = client._make_sse_decoder() self._iterator = self.__stream__() From 564cd5730fbf94b7014390ee71d7595cb7d6e71a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 04:35:11 +0000 Subject: [PATCH 07/13] chore(internal): make `test_proxy_environment_variables` more resilient --- tests/test_client.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_client.py b/tests/test_client.py index 3f5426415..f2b774db7 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1002,6 +1002,8 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: # Test that the proxy environment variables are set correctly monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + # Delete in case our environment has this set + monkeypatch.delenv("HTTP_PROXY", raising=False) client = DefaultHttpxClient() @@ -1953,6 +1955,8 @@ async def test_get_platform(self) -> None: async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: # Test that the proxy environment variables are set correctly monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + # Delete in case our environment has this set + monkeypatch.delenv("HTTP_PROXY", raising=False) client = DefaultAsyncHttpxClient() From 21d4ce865094d9272841871eefde3cf15c183d19 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 25 Feb 2026 04:17:46 +0000 Subject: [PATCH 08/13] chore(internal): make `test_proxy_environment_variables` more resilient to env --- tests/test_client.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index f2b774db7..725862258 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1002,8 +1002,14 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: # Test that the proxy environment variables are set correctly monkeypatch.setenv("HTTPS_PROXY", "https://example.org") - # Delete in case our environment has this set + # Delete in case our environment has any proxy env vars set monkeypatch.delenv("HTTP_PROXY", raising=False) + monkeypatch.delenv("ALL_PROXY", raising=False) + monkeypatch.delenv("NO_PROXY", raising=False) + monkeypatch.delenv("http_proxy", raising=False) + monkeypatch.delenv("https_proxy", raising=False) + monkeypatch.delenv("all_proxy", raising=False) + monkeypatch.delenv("no_proxy", raising=False) client = DefaultHttpxClient() @@ -1955,8 +1961,14 @@ async def test_get_platform(self) -> None: async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: # Test that the proxy environment variables are set correctly monkeypatch.setenv("HTTPS_PROXY", "https://example.org") - # Delete in case our environment has this set + # Delete in case our environment has any proxy env vars set monkeypatch.delenv("HTTP_PROXY", raising=False) + monkeypatch.delenv("ALL_PROXY", raising=False) + monkeypatch.delenv("NO_PROXY", raising=False) + monkeypatch.delenv("http_proxy", raising=False) + monkeypatch.delenv("https_proxy", raising=False) + monkeypatch.delenv("all_proxy", raising=False) + monkeypatch.delenv("no_proxy", raising=False) client = DefaultAsyncHttpxClient() From 85f2146d2f25085d19d5bf34096066b0ba18cb5d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 25 Feb 2026 19:05:09 +0000 Subject: [PATCH 09/13] feat: Add AI gateway and MCP gateway flags to network policy create (#7638) --- .stats.yml | 4 +-- .../resources/network_policies.py | 36 +++++++++++++++++++ .../types/network_policy_create_params.py | 12 +++++++ .../types/network_policy_update_params.py | 6 ++++ .../types/network_policy_view.py | 9 +++++ tests/api_resources/test_network_policies.py | 8 +++++ 6 files changed, 73 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index a869301e9..e8c2e4563 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 118 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-1a92de99959f20ab5850a7f89db21cb66269b2e0002ffc5e5afbe660d05b0768.yml -openapi_spec_hash: 41e4660f26e27e6ab94d223da4ec1253 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-dd0f43e15cb67179deaf86f83ae04c6fae4ff858ee33e82a3b89231566cdc5bb.yml +openapi_spec_hash: 569176c1c4f48efd25a44fa526fad9d1 config_hash: cbda3692cb48ab8582a0df1674b9e5c8 diff --git a/src/runloop_api_client/resources/network_policies.py b/src/runloop_api_client/resources/network_policies.py index 1cda5b42d..388126cee 100644 --- a/src/runloop_api_client/resources/network_policies.py +++ b/src/runloop_api_client/resources/network_policies.py @@ -48,8 +48,10 @@ def create( self, *, name: str, + allow_ai_gateway: Optional[bool] | Omit = omit, allow_all: Optional[bool] | Omit = omit, allow_devbox_to_devbox: Optional[bool] | Omit = omit, + allow_mcp_gateway: Optional[bool] | Omit = omit, allowed_hostnames: Optional[SequenceNotStr[str]] | Omit = omit, description: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -69,6 +71,9 @@ def create( name: The human-readable name for the NetworkPolicy. Must be unique within the account. + allow_ai_gateway: (Optional) If true, allows devbox egress to the AI credential gateway for + credential proxying. Defaults to false. + allow_all: (Optional) If true, all egress traffic is allowed (ALLOW_ALL policy). Defaults to false. @@ -76,6 +81,9 @@ def create( tunnels. Defaults to false. If allow_all is true, this is automatically set to true. + allow_mcp_gateway: (Optional) If true, allows devbox egress to the MCP hub for MCP server access. + Defaults to false. + allowed_hostnames: (Optional) DNS-based allow list with wildcard support. Examples: ['github.com', '*.npmjs.org']. @@ -96,8 +104,10 @@ def create( body=maybe_transform( { "name": name, + "allow_ai_gateway": allow_ai_gateway, "allow_all": allow_all, "allow_devbox_to_devbox": allow_devbox_to_devbox, + "allow_mcp_gateway": allow_mcp_gateway, "allowed_hostnames": allowed_hostnames, "description": description, }, @@ -150,8 +160,10 @@ def update( self, id: str, *, + allow_ai_gateway: Optional[bool] | Omit = omit, allow_all: Optional[bool] | Omit = omit, allow_devbox_to_devbox: Optional[bool] | Omit = omit, + allow_mcp_gateway: Optional[bool] | Omit = omit, allowed_hostnames: Optional[SequenceNotStr[str]] | Omit = omit, description: Optional[str] | Omit = omit, name: Optional[str] | Omit = omit, @@ -168,10 +180,14 @@ def update( All fields are optional. Args: + allow_ai_gateway: If true, allows devbox egress to the AI credential gateway. + allow_all: If true, all egress traffic is allowed (ALLOW_ALL policy). allow_devbox_to_devbox: If true, allows traffic between the account's own devboxes via tunnels. + allow_mcp_gateway: If true, allows devbox egress to the MCP hub. + allowed_hostnames: Updated DNS-based allow list with wildcard support. Examples: ['github.com', '*.npmjs.org']. @@ -195,8 +211,10 @@ def update( f"/v1/network-policies/{id}", body=maybe_transform( { + "allow_ai_gateway": allow_ai_gateway, "allow_all": allow_all, "allow_devbox_to_devbox": allow_devbox_to_devbox, + "allow_mcp_gateway": allow_mcp_gateway, "allowed_hostnames": allowed_hostnames, "description": description, "name": name, @@ -334,8 +352,10 @@ async def create( self, *, name: str, + allow_ai_gateway: Optional[bool] | Omit = omit, allow_all: Optional[bool] | Omit = omit, allow_devbox_to_devbox: Optional[bool] | Omit = omit, + allow_mcp_gateway: Optional[bool] | Omit = omit, allowed_hostnames: Optional[SequenceNotStr[str]] | Omit = omit, description: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -355,6 +375,9 @@ async def create( name: The human-readable name for the NetworkPolicy. Must be unique within the account. + allow_ai_gateway: (Optional) If true, allows devbox egress to the AI credential gateway for + credential proxying. Defaults to false. + allow_all: (Optional) If true, all egress traffic is allowed (ALLOW_ALL policy). Defaults to false. @@ -362,6 +385,9 @@ async def create( tunnels. Defaults to false. If allow_all is true, this is automatically set to true. + allow_mcp_gateway: (Optional) If true, allows devbox egress to the MCP hub for MCP server access. + Defaults to false. + allowed_hostnames: (Optional) DNS-based allow list with wildcard support. Examples: ['github.com', '*.npmjs.org']. @@ -382,8 +408,10 @@ async def create( body=await async_maybe_transform( { "name": name, + "allow_ai_gateway": allow_ai_gateway, "allow_all": allow_all, "allow_devbox_to_devbox": allow_devbox_to_devbox, + "allow_mcp_gateway": allow_mcp_gateway, "allowed_hostnames": allowed_hostnames, "description": description, }, @@ -436,8 +464,10 @@ async def update( self, id: str, *, + allow_ai_gateway: Optional[bool] | Omit = omit, allow_all: Optional[bool] | Omit = omit, allow_devbox_to_devbox: Optional[bool] | Omit = omit, + allow_mcp_gateway: Optional[bool] | Omit = omit, allowed_hostnames: Optional[SequenceNotStr[str]] | Omit = omit, description: Optional[str] | Omit = omit, name: Optional[str] | Omit = omit, @@ -454,10 +484,14 @@ async def update( All fields are optional. Args: + allow_ai_gateway: If true, allows devbox egress to the AI credential gateway. + allow_all: If true, all egress traffic is allowed (ALLOW_ALL policy). allow_devbox_to_devbox: If true, allows traffic between the account's own devboxes via tunnels. + allow_mcp_gateway: If true, allows devbox egress to the MCP hub. + allowed_hostnames: Updated DNS-based allow list with wildcard support. Examples: ['github.com', '*.npmjs.org']. @@ -481,8 +515,10 @@ async def update( f"/v1/network-policies/{id}", body=await async_maybe_transform( { + "allow_ai_gateway": allow_ai_gateway, "allow_all": allow_all, "allow_devbox_to_devbox": allow_devbox_to_devbox, + "allow_mcp_gateway": allow_mcp_gateway, "allowed_hostnames": allowed_hostnames, "description": description, "name": name, diff --git a/src/runloop_api_client/types/network_policy_create_params.py b/src/runloop_api_client/types/network_policy_create_params.py index e1a718a48..91a023188 100644 --- a/src/runloop_api_client/types/network_policy_create_params.py +++ b/src/runloop_api_client/types/network_policy_create_params.py @@ -17,6 +17,12 @@ class NetworkPolicyCreateParams(TypedDict, total=False): Must be unique within the account. """ + allow_ai_gateway: Optional[bool] + """ + (Optional) If true, allows devbox egress to the AI credential gateway for + credential proxying. Defaults to false. + """ + allow_all: Optional[bool] """(Optional) If true, all egress traffic is allowed (ALLOW_ALL policy). @@ -30,6 +36,12 @@ class NetworkPolicyCreateParams(TypedDict, total=False): true. """ + allow_mcp_gateway: Optional[bool] + """(Optional) If true, allows devbox egress to the MCP hub for MCP server access. + + Defaults to false. + """ + allowed_hostnames: Optional[SequenceNotStr[str]] """(Optional) DNS-based allow list with wildcard support. diff --git a/src/runloop_api_client/types/network_policy_update_params.py b/src/runloop_api_client/types/network_policy_update_params.py index 22e9550ff..ad7c62992 100644 --- a/src/runloop_api_client/types/network_policy_update_params.py +++ b/src/runloop_api_client/types/network_policy_update_params.py @@ -11,12 +11,18 @@ class NetworkPolicyUpdateParams(TypedDict, total=False): + allow_ai_gateway: Optional[bool] + """If true, allows devbox egress to the AI credential gateway.""" + allow_all: Optional[bool] """If true, all egress traffic is allowed (ALLOW_ALL policy).""" allow_devbox_to_devbox: Optional[bool] """If true, allows traffic between the account's own devboxes via tunnels.""" + allow_mcp_gateway: Optional[bool] + """If true, allows devbox egress to the MCP hub.""" + allowed_hostnames: Optional[SequenceNotStr[str]] """Updated DNS-based allow list with wildcard support. diff --git a/src/runloop_api_client/types/network_policy_view.py b/src/runloop_api_client/types/network_policy_view.py index 4d743cc8a..197902b02 100644 --- a/src/runloop_api_client/types/network_policy_view.py +++ b/src/runloop_api_client/types/network_policy_view.py @@ -10,6 +10,12 @@ class Egress(BaseModel): """The egress rules for this policy.""" + allow_ai_gateway: bool + """ + If true, allows devbox egress to the AI credential gateway for credential + proxying. + """ + allow_all: bool """If true, all egress traffic is allowed and other fields are ignored. @@ -19,6 +25,9 @@ class Egress(BaseModel): allow_devbox_to_devbox: bool """If true, allows traffic between the account's own devboxes via tunnels.""" + allow_mcp_gateway: bool + """If true, allows devbox egress to the MCP hub for MCP server access.""" + allowed_hostnames: List[str] """DNS-based allow list with wildcard support. diff --git a/tests/api_resources/test_network_policies.py b/tests/api_resources/test_network_policies.py index a528b4256..22a868cc4 100644 --- a/tests/api_resources/test_network_policies.py +++ b/tests/api_resources/test_network_policies.py @@ -31,8 +31,10 @@ def test_method_create(self, client: Runloop) -> None: def test_method_create_with_all_params(self, client: Runloop) -> None: network_policy = client.network_policies.create( name="name", + allow_ai_gateway=True, allow_all=True, allow_devbox_to_devbox=True, + allow_mcp_gateway=True, allowed_hostnames=["string"], description="description", ) @@ -111,8 +113,10 @@ def test_method_update(self, client: Runloop) -> None: def test_method_update_with_all_params(self, client: Runloop) -> None: network_policy = client.network_policies.update( id="id", + allow_ai_gateway=True, allow_all=True, allow_devbox_to_devbox=True, + allow_mcp_gateway=True, allowed_hostnames=["string"], description="description", name="name", @@ -240,8 +244,10 @@ async def test_method_create(self, async_client: AsyncRunloop) -> None: async def test_method_create_with_all_params(self, async_client: AsyncRunloop) -> None: network_policy = await async_client.network_policies.create( name="name", + allow_ai_gateway=True, allow_all=True, allow_devbox_to_devbox=True, + allow_mcp_gateway=True, allowed_hostnames=["string"], description="description", ) @@ -320,8 +326,10 @@ async def test_method_update(self, async_client: AsyncRunloop) -> None: async def test_method_update_with_all_params(self, async_client: AsyncRunloop) -> None: network_policy = await async_client.network_policies.update( id="id", + allow_ai_gateway=True, allow_all=True, allow_devbox_to_devbox=True, + allow_mcp_gateway=True, allowed_hostnames=["string"], description="description", name="name", From ce6c53c3499499bacf278130e5f984ec43148d20 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 25 Feb 2026 20:33:17 +0000 Subject: [PATCH 10/13] release: 1.9.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 25 +++++++++++++++++++++++++ pyproject.toml | 2 +- src/runloop_api_client/_version.py | 2 +- 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index c523ce19f..c3c95522a 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.8.0" + ".": "1.9.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 78604da88..984b847cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ # Changelog +## 1.9.0 (2026-02-25) + +Full Changelog: [v1.8.0...v1.9.0](https://github.com/runloopai/api-client-python/compare/v1.8.0...v1.9.0) + +### Features + +* Add AI gateway and MCP gateway flags to network policy create ([#7638](https://github.com/runloopai/api-client-python/issues/7638)) ([85f2146](https://github.com/runloopai/api-client-python/commit/85f2146d2f25085d19d5bf34096066b0ba18cb5d)) + + +### Chores + +* Add archive method to stainless ([#7537](https://github.com/runloopai/api-client-python/issues/7537)) ([836f18d](https://github.com/runloopai/api-client-python/commit/836f18d52c20b4b43f9135a52571f8c7e87a0477)) +* **benchmarks:** removed scorer ([#740](https://github.com/runloopai/api-client-python/issues/740)) ([1033264](https://github.com/runloopai/api-client-python/commit/1033264b0ed34cc47e2e16cac3ae5986f2a4e381)) +* format all `api.md` files ([ee5d577](https://github.com/runloopai/api-client-python/commit/ee5d5773d408ac8e61101fd0187ec071709bebab)) +* **internal:** add request options to SSE classes ([e636aca](https://github.com/runloopai/api-client-python/commit/e636aca4eedb81b35d80bd6f5229cc71153ec545)) +* **internal:** make `test_proxy_environment_variables` more resilient ([564cd57](https://github.com/runloopai/api-client-python/commit/564cd5730fbf94b7014390ee71d7595cb7d6e71a)) +* **internal:** make `test_proxy_environment_variables` more resilient to env ([21d4ce8](https://github.com/runloopai/api-client-python/commit/21d4ce865094d9272841871eefde3cf15c183d19)) +* **scenarios:** make scenario status enum instead of string ([#7552](https://github.com/runloopai/api-client-python/issues/7552)) ([e6e5208](https://github.com/runloopai/api-client-python/commit/e6e5208c19360f24f10b3c0da84119f7eb356bb1)) +* update mock server docs ([273fb5e](https://github.com/runloopai/api-client-python/commit/273fb5ec761bcc5948571d9757780d86b6f0f50d)) + + +### Refactors + +* deprecate and make Nullable total_count and remaining_count of ListViews ([#7533](https://github.com/runloopai/api-client-python/issues/7533)) ([8b16ebe](https://github.com/runloopai/api-client-python/commit/8b16ebe7aba9f96592f03ed2844211209b35d9e6)) + ## 1.8.0 (2026-02-12) Full Changelog: [v1.7.0...v1.8.0](https://github.com/runloopai/api-client-python/compare/v1.7.0...v1.8.0) diff --git a/pyproject.toml b/pyproject.toml index c872715e3..8b0c80fa7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "runloop_api_client" -version = "1.8.0" +version = "1.9.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 da91dc1ce..f2dd2d557 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.8.0" # x-release-please-version +__version__ = "1.9.0" # x-release-please-version From 1a366d428305ce26bb9ee23e51c9689635494d73 Mon Sep 17 00:00:00 2001 From: Albert Li Date: Wed, 25 Feb 2026 14:26:39 -0800 Subject: [PATCH 11/13] Fix tests --- src/runloop_api_client/sdk/_types.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/runloop_api_client/sdk/_types.py b/src/runloop_api_client/sdk/_types.py index 9e7f8780e..6b22c351e 100644 --- a/src/runloop_api_client/sdk/_types.py +++ b/src/runloop_api_client/sdk/_types.py @@ -1,4 +1,4 @@ -from typing import Union, Callable, Optional +from typing import Literal, Union, Callable, Optional from typing_extensions import TypedDict from ..types import ( @@ -219,6 +219,9 @@ class ScenarioPreview(ScenarioView): input_context: InputContextPreview # type: ignore[assignment] """The input context for the Scenario.""" + status: Optional[Literal["active", "archived"]] = None # type: ignore[assignment] + """Status is not set for previews.""" + class SDKBenchmarkCreateParams(BenchmarkCreateParams, LongRequestOptions): pass From f835bb736131213a712fb15f1604a7ee74942099 Mon Sep 17 00:00:00 2001 From: Albert Li Date: Wed, 25 Feb 2026 14:30:27 -0800 Subject: [PATCH 12/13] lint --- src/runloop_api_client/sdk/_types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runloop_api_client/sdk/_types.py b/src/runloop_api_client/sdk/_types.py index 6b22c351e..1eea070a2 100644 --- a/src/runloop_api_client/sdk/_types.py +++ b/src/runloop_api_client/sdk/_types.py @@ -1,4 +1,4 @@ -from typing import Literal, Union, Callable, Optional +from typing import Union, Literal, Callable, Optional from typing_extensions import TypedDict from ..types import ( From df0f1962e7a3eb875aa6d0b8161ccfe93c511eff Mon Sep 17 00:00:00 2001 From: Albert Li Date: Wed, 25 Feb 2026 14:31:15 -0800 Subject: [PATCH 13/13] Update version --- uv.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uv.lock b/uv.lock index dcf165cb8..26c8d993a 100644 --- a/uv.lock +++ b/uv.lock @@ -2267,7 +2267,7 @@ wheels = [ [[package]] name = "runloop-api-client" -version = "1.8.0" +version = "1.9.0" source = { editable = "." } dependencies = [ { name = "anyio" },