Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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.5.0"
".": "1.5.1"
}
4 changes: 2 additions & 2 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)
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.5.0"
version = "1.5.1"
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.5.0" # x-release-please-version
__version__ = "1.5.1" # x-release-please-version
5 changes: 5 additions & 0 deletions src/runloop_api_client/sdk/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
NetworkPolicyListParams,
DevboxCreateTunnelParams,
DevboxDownloadFileParams,
DevboxEnableTunnelParams,
DevboxRemoveTunnelParams,
DevboxSnapshotDiskParams,
NetworkPolicyCreateParams,
Expand Down Expand Up @@ -121,6 +122,10 @@ class SDKDevboxCreateTunnelParams(DevboxCreateTunnelParams, LongRequestOptions):
pass


class SDKDevboxEnableTunnelParams(DevboxEnableTunnelParams, LongRequestOptions):
pass


class SDKDevboxRemoveTunnelParams(DevboxRemoveTunnelParams, LongRequestOptions):
pass

Expand Down
30 changes: 29 additions & 1 deletion src/runloop_api_client/sdk/async_devbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from ..types import (
DevboxView,
TunnelView,
DevboxTunnelView,
DevboxExecutionDetailView,
DevboxCreateSSHKeyResponse,
Expand All @@ -24,6 +25,7 @@
SDKDevboxUploadFileParams,
SDKDevboxCreateTunnelParams,
SDKDevboxDownloadFileParams,
SDKDevboxEnableTunnelParams,
SDKDevboxExecuteAsyncParams,
SDKDevboxRemoveTunnelParams,
SDKDevboxSnapshotDiskParams,
Expand Down Expand Up @@ -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
Expand All @@ -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],
Expand Down
30 changes: 29 additions & 1 deletion src/runloop_api_client/sdk/devbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from ..types import (
DevboxView,
TunnelView,
DevboxTunnelView,
DevboxExecutionDetailView,
DevboxCreateSSHKeyResponse,
Expand All @@ -25,6 +26,7 @@
SDKDevboxUploadFileParams,
SDKDevboxCreateTunnelParams,
SDKDevboxDownloadFileParams,
SDKDevboxEnableTunnelParams,
SDKDevboxExecuteAsyncParams,
SDKDevboxRemoveTunnelParams,
SDKDevboxSnapshotDiskParams,
Expand Down Expand Up @@ -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
Expand All @@ -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],
Expand Down
2 changes: 1 addition & 1 deletion src/runloop_api_client/types/tunnel_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
47 changes: 47 additions & 0 deletions tests/smoketests/sdk/test_async_devbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
45 changes: 45 additions & 0 deletions tests/smoketests/sdk/test_devbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading