From 7a9b7a449ce648ad4f220af83b35f800727ea28d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 30 Jan 2026 23:06:02 +0000 Subject: [PATCH 1/3] chore(devbox): rename tunnel auth enum (#7239) --- .stats.yml | 4 ++-- src/runloop_api_client/types/tunnel_view.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index 26ecaabc0..81d520c67 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 112 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-84e997ca5716b9378a58a1bdf3d6616cf3be80156a6aaed1bed469fe93ba2c95.yml -openapi_spec_hash: b44a4ba1c2c3cb775c14545f2bab05a8 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-b493cadf2e3b9658163a6bcf8f51e088dda169f12d68469c4441d17e889f5556.yml +openapi_spec_hash: b27ec3822d88d10efa268f1681fddff3 config_hash: 6c26299fd9ef01fb4713612a9a2ad17c diff --git a/src/runloop_api_client/types/tunnel_view.py b/src/runloop_api_client/types/tunnel_view.py index beb43daf9..540a44e7c 100644 --- a/src/runloop_api_client/types/tunnel_view.py +++ b/src/runloop_api_client/types/tunnel_view.py @@ -15,7 +15,7 @@ class TunnelView(BaseModel): Usage: https://{port}-{tunnel_key}.tunnel.runloop.ai """ - auth_mode: Literal["public_", "authenticated"] + auth_mode: Literal["open", "authenticated"] """The authentication mode for the tunnel.""" create_time_ms: int From 565d85787d40d35d78a575045faf342ee56aa360 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 30 Jan 2026 23:06:20 +0000 Subject: [PATCH 2/3] release: 1.5.1 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 8 ++++++++ pyproject.toml | 2 +- src/runloop_api_client/_version.py | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index fbd9082d7..4a2f7e609 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.5.0" + ".": "1.5.1" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index eacbba506..55d18da5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 1.5.1 (2026-01-30) + +Full Changelog: [v1.5.0...v1.5.1](https://github.com/runloopai/api-client-python/compare/v1.5.0...v1.5.1) + +### Chores + +* **devbox:** rename tunnel auth enum ([#7239](https://github.com/runloopai/api-client-python/issues/7239)) ([7a9b7a4](https://github.com/runloopai/api-client-python/commit/7a9b7a449ce648ad4f220af83b35f800727ea28d)) + ## 1.5.0 (2026-01-30) Full Changelog: [v1.4.0...v1.5.0](https://github.com/runloopai/api-client-python/compare/v1.4.0...v1.5.0) diff --git a/pyproject.toml b/pyproject.toml index 2b4148401..60897f964 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "runloop_api_client" -version = "1.5.0" +version = "1.5.1" 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 cfd147a9e..962984a45 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.5.0" # x-release-please-version +__version__ = "1.5.1" # x-release-please-version From c4797b33ce872f530efc62bd4af5cd6ec4b9bbca Mon Sep 17 00:00:00 2001 From: Albert Li Date: Fri, 30 Jan 2026 15:11:08 -0800 Subject: [PATCH 3/3] Add smoketests --- src/runloop_api_client/sdk/_types.py | 5 +++ src/runloop_api_client/sdk/async_devbox.py | 30 +++++++++++++- src/runloop_api_client/sdk/devbox.py | 30 +++++++++++++- tests/smoketests/sdk/test_async_devbox.py | 47 ++++++++++++++++++++++ tests/smoketests/sdk/test_devbox.py | 45 +++++++++++++++++++++ uv.lock | 2 +- 6 files changed, 156 insertions(+), 3 deletions(-) diff --git a/src/runloop_api_client/sdk/_types.py b/src/runloop_api_client/sdk/_types.py index d367f927e..b7048a536 100644 --- a/src/runloop_api_client/sdk/_types.py +++ b/src/runloop_api_client/sdk/_types.py @@ -22,6 +22,7 @@ NetworkPolicyListParams, DevboxCreateTunnelParams, DevboxDownloadFileParams, + DevboxEnableTunnelParams, DevboxRemoveTunnelParams, DevboxSnapshotDiskParams, NetworkPolicyCreateParams, @@ -121,6 +122,10 @@ class SDKDevboxCreateTunnelParams(DevboxCreateTunnelParams, LongRequestOptions): pass +class SDKDevboxEnableTunnelParams(DevboxEnableTunnelParams, LongRequestOptions): + pass + + class SDKDevboxRemoveTunnelParams(DevboxRemoveTunnelParams, LongRequestOptions): pass diff --git a/src/runloop_api_client/sdk/async_devbox.py b/src/runloop_api_client/sdk/async_devbox.py index 7e327826f..2ac12ebba 100644 --- a/src/runloop_api_client/sdk/async_devbox.py +++ b/src/runloop_api_client/sdk/async_devbox.py @@ -10,6 +10,7 @@ from ..types import ( DevboxView, + TunnelView, DevboxTunnelView, DevboxExecutionDetailView, DevboxCreateSSHKeyResponse, @@ -24,6 +25,7 @@ SDKDevboxUploadFileParams, SDKDevboxCreateTunnelParams, SDKDevboxDownloadFileParams, + SDKDevboxEnableTunnelParams, SDKDevboxExecuteAsyncParams, SDKDevboxRemoveTunnelParams, SDKDevboxSnapshotDiskParams, @@ -731,7 +733,9 @@ async def create_tunnel( self, **params: Unpack[SDKDevboxCreateTunnelParams], ) -> DevboxTunnelView: - """Create a network tunnel to expose a devbox port publicly. + """[Deprecated] Create a legacy tunnel to expose a devbox port publicly. + + Use :meth:`enable_tunnel` instead for the V2 tunnel API. :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKDevboxCreateTunnelParams` for available parameters :return: Details about the public endpoint @@ -748,6 +752,30 @@ async def create_tunnel( **params, ) + async def enable_tunnel( + self, + **params: Unpack[SDKDevboxEnableTunnelParams], + ) -> TunnelView: + """Enable a V2 tunnel for secure HTTP access to the devbox. + + V2 tunnels provide encrypted URL-based access without exposing internal IDs. + Each devbox can have one tunnel. The tunnel URL format is: + ``https://{port}-{tunnel_key}.tunnel.runloop.ai`` + + :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKDevboxEnableTunnelParams` for available parameters + :return: Tunnel details including the tunnel key for constructing URLs + :rtype: TunnelView + + Example: + >>> tunnel = await devbox.net.enable_tunnel(auth_mode="open") + >>> print(f"Tunnel key: {tunnel.tunnel_key}") + >>> # Access via: https://8080-{tunnel.tunnel_key}.tunnel.runloop.ai + """ + return await self._devbox._client.devboxes.enable_tunnel( + self._devbox.id, + **params, + ) + async def remove_tunnel( self, **params: Unpack[SDKDevboxRemoveTunnelParams], diff --git a/src/runloop_api_client/sdk/devbox.py b/src/runloop_api_client/sdk/devbox.py index 0c11955a3..bdf8d7389 100644 --- a/src/runloop_api_client/sdk/devbox.py +++ b/src/runloop_api_client/sdk/devbox.py @@ -10,6 +10,7 @@ from ..types import ( DevboxView, + TunnelView, DevboxTunnelView, DevboxExecutionDetailView, DevboxCreateSSHKeyResponse, @@ -25,6 +26,7 @@ SDKDevboxUploadFileParams, SDKDevboxCreateTunnelParams, SDKDevboxDownloadFileParams, + SDKDevboxEnableTunnelParams, SDKDevboxExecuteAsyncParams, SDKDevboxRemoveTunnelParams, SDKDevboxSnapshotDiskParams, @@ -734,7 +736,9 @@ def create_tunnel( self, **params: Unpack[SDKDevboxCreateTunnelParams], ) -> DevboxTunnelView: - """Create a network tunnel to expose a devbox port publicly. + """[Deprecated] Create a legacy tunnel to expose a devbox port publicly. + + Use :meth:`enable_tunnel` instead for the V2 tunnel API. :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKDevboxCreateTunnelParams` for available parameters :return: Details about the public endpoint @@ -751,6 +755,30 @@ def create_tunnel( **params, ) + def enable_tunnel( + self, + **params: Unpack[SDKDevboxEnableTunnelParams], + ) -> TunnelView: + """Enable a V2 tunnel for secure HTTP access to the devbox. + + V2 tunnels provide encrypted URL-based access without exposing internal IDs. + Each devbox can have one tunnel. The tunnel URL format is: + ``https://{port}-{tunnel_key}.tunnel.runloop.ai`` + + :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKDevboxEnableTunnelParams` for available parameters + :return: Tunnel details including the tunnel key for constructing URLs + :rtype: :class:`~runloop_api_client.types.tunnel_view.TunnelView` + + Example: + >>> tunnel = devbox.net.enable_tunnel(auth_mode="open") + >>> print(f"Tunnel key: {tunnel.tunnel_key}") + >>> # Access via: https://8080-{tunnel.tunnel_key}.tunnel.runloop.ai + """ + return self._devbox._client.devboxes.enable_tunnel( + self._devbox.id, + **params, + ) + def remove_tunnel( self, **params: Unpack[SDKDevboxRemoveTunnelParams], diff --git a/tests/smoketests/sdk/test_async_devbox.py b/tests/smoketests/sdk/test_async_devbox.py index 4f2e6603c..dec172301 100644 --- a/tests/smoketests/sdk/test_async_devbox.py +++ b/tests/smoketests/sdk/test_async_devbox.py @@ -458,6 +458,53 @@ async def test_create_and_remove_tunnel(self, async_sdk_client: AsyncRunloopSDK) finally: await devbox.shutdown() + @pytest.mark.timeout(TWO_MINUTE_TIMEOUT) + async def test_create_with_tunnel_param(self, async_sdk_client: AsyncRunloopSDK) -> None: + """Test creating a devbox with tunnel configuration in create params.""" + devbox = await async_sdk_client.devbox.create( + name=unique_name("sdk-async-devbox-tunnel-param"), + launch_parameters={"resource_size_request": "SMALL", "keep_alive_time_seconds": 60 * 5}, + tunnel={"auth_mode": "open"}, + ) + + try: + # Verify devbox is running + info = await devbox.get_info() + assert info.status == "running" + + # Verify tunnel was created at launch + assert info.tunnel is not None + assert info.tunnel.tunnel_key is not None + assert info.tunnel.auth_mode is not None + finally: + await devbox.shutdown() + + @pytest.mark.timeout(TWO_MINUTE_TIMEOUT) + async def test_enable_tunnel(self, async_sdk_client: AsyncRunloopSDK) -> None: + """Test enabling a V2 tunnel on an existing devbox.""" + devbox = await async_sdk_client.devbox.create( + name=unique_name("sdk-async-devbox-enable-tunnel"), + launch_parameters={"resource_size_request": "SMALL", "keep_alive_time_seconds": 60 * 5}, + ) + + try: + # Verify no tunnel exists initially + info = await devbox.get_info() + assert info.tunnel is None + + # Enable tunnel using the V2 API via SDK + tunnel = await devbox.net.enable_tunnel(auth_mode="open") + assert tunnel is not None + assert tunnel.tunnel_key is not None + assert tunnel.auth_mode is not None + + # Verify tunnel is now present in devbox info + info = await devbox.get_info() + assert info.tunnel is not None + assert info.tunnel.tunnel_key == tunnel.tunnel_key + finally: + await devbox.shutdown() + class TestAsyncDevboxCreationMethods: """Test various async devbox creation methods.""" diff --git a/tests/smoketests/sdk/test_devbox.py b/tests/smoketests/sdk/test_devbox.py index 87d5fc4d6..3923694fb 100644 --- a/tests/smoketests/sdk/test_devbox.py +++ b/tests/smoketests/sdk/test_devbox.py @@ -455,6 +455,51 @@ def test_create_and_remove_tunnel(self, sdk_client: RunloopSDK) -> None: finally: devbox.shutdown() + @pytest.mark.timeout(TWO_MINUTE_TIMEOUT) + def test_create_with_tunnel_param(self, sdk_client: RunloopSDK) -> None: + """Test creating a devbox with tunnel configuration in create params.""" + devbox = sdk_client.devbox.create( + name=unique_name("sdk-devbox-tunnel-param"), + launch_parameters={"resource_size_request": "SMALL", "keep_alive_time_seconds": 60 * 5}, + tunnel={"auth_mode": "open"}, + ) + + try: + # Verify devbox is running + info = devbox.get_info() + assert info.status == "running" + + # Verify tunnel was created at launch + assert info.tunnel is not None + assert info.tunnel.tunnel_key is not None + assert info.tunnel.auth_mode is not None + finally: + devbox.shutdown() + + @pytest.mark.timeout(TWO_MINUTE_TIMEOUT) + def test_enable_tunnel(self, sdk_client: RunloopSDK) -> None: + """Test enabling a V2 tunnel on an existing devbox.""" + devbox = sdk_client.devbox.create( + name=unique_name("sdk-devbox-enable-tunnel"), + launch_parameters={"resource_size_request": "SMALL", "keep_alive_time_seconds": 60 * 5}, + ) + + try: + info = devbox.get_info() + assert info.tunnel is None + + # Enable tunnel using the V2 API via SDK + tunnel = devbox.net.enable_tunnel(auth_mode="open") + assert tunnel is not None + assert tunnel.tunnel_key is not None + assert tunnel.auth_mode is not None + + info = devbox.get_info() + assert info.tunnel is not None + assert info.tunnel.tunnel_key == tunnel.tunnel_key + finally: + devbox.shutdown() + class TestDevboxCreationMethods: """Test various devbox creation methods.""" diff --git a/uv.lock b/uv.lock index bbb1cac9e..cee76adfb 100644 --- a/uv.lock +++ b/uv.lock @@ -2237,7 +2237,7 @@ wheels = [ [[package]] name = "runloop-api-client" -version = "1.4.0" +version = "1.5.1" source = { editable = "." } dependencies = [ { name = "anyio" },