From a3ef1141526815f0bfd186b8368b5d8f041865a1 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 5 Sep 2025 15:03:26 +0000
Subject: [PATCH 1/7] chore(internal): codegen related update
---
mypy.ini | 50 ------------------------------------------------
pyproject.toml | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 52 insertions(+), 50 deletions(-)
delete mode 100644 mypy.ini
diff --git a/mypy.ini b/mypy.ini
deleted file mode 100644
index 0a62aa9d6..000000000
--- a/mypy.ini
+++ /dev/null
@@ -1,50 +0,0 @@
-[mypy]
-pretty = True
-show_error_codes = True
-
-# Exclude _files.py because mypy isn't smart enough to apply
-# the correct type narrowing and as this is an internal module
-# it's fine to just use Pyright.
-#
-# We also exclude our `tests` as mypy doesn't always infer
-# types correctly and Pyright will still catch any type errors.
-exclude = ^(src/runloop_api_client/_files\.py|_dev/.*\.py|tests/.*)$
-
-strict_equality = True
-implicit_reexport = True
-check_untyped_defs = True
-no_implicit_optional = True
-
-warn_return_any = True
-warn_unreachable = True
-warn_unused_configs = True
-
-# Turn these options off as it could cause conflicts
-# with the Pyright options.
-warn_unused_ignores = False
-warn_redundant_casts = False
-
-disallow_any_generics = True
-disallow_untyped_defs = True
-disallow_untyped_calls = True
-disallow_subclassing_any = True
-disallow_incomplete_defs = True
-disallow_untyped_decorators = True
-cache_fine_grained = True
-
-# By default, mypy reports an error if you assign a value to the result
-# of a function call that doesn't return anything. We do this in our test
-# cases:
-# ```
-# result = ...
-# assert result is None
-# ```
-# Changing this codegen to make mypy happy would increase complexity
-# and would not be worth it.
-disable_error_code = func-returns-value,overload-cannot-match
-
-# https://github.com/python/mypy/issues/12162
-[mypy.overrides]
-module = "black.files.*"
-ignore_errors = true
-ignore_missing_imports = true
diff --git a/pyproject.toml b/pyproject.toml
index 52e917e86..cf2752640 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -166,6 +166,58 @@ reportOverlappingOverload = false
reportImportCycles = false
reportPrivateUsage = false
+[tool.mypy]
+pretty = true
+show_error_codes = true
+
+# Exclude _files.py because mypy isn't smart enough to apply
+# the correct type narrowing and as this is an internal module
+# it's fine to just use Pyright.
+#
+# We also exclude our `tests` as mypy doesn't always infer
+# types correctly and Pyright will still catch any type errors.
+exclude = ['src/runloop_api_client/_files.py', '_dev/.*.py', 'tests/.*']
+
+strict_equality = true
+implicit_reexport = true
+check_untyped_defs = true
+no_implicit_optional = true
+
+warn_return_any = true
+warn_unreachable = true
+warn_unused_configs = true
+
+# Turn these options off as it could cause conflicts
+# with the Pyright options.
+warn_unused_ignores = false
+warn_redundant_casts = false
+
+disallow_any_generics = true
+disallow_untyped_defs = true
+disallow_untyped_calls = true
+disallow_subclassing_any = true
+disallow_incomplete_defs = true
+disallow_untyped_decorators = true
+cache_fine_grained = true
+
+# By default, mypy reports an error if you assign a value to the result
+# of a function call that doesn't return anything. We do this in our test
+# cases:
+# ```
+# result = ...
+# assert result is None
+# ```
+# Changing this codegen to make mypy happy would increase complexity
+# and would not be worth it.
+disable_error_code = "func-returns-value,overload-cannot-match"
+
+# https://github.com/python/mypy/issues/12162
+[[tool.mypy.overrides]]
+module = "black.files.*"
+ignore_errors = true
+ignore_missing_imports = true
+
+
[tool.ruff]
line-length = 120
output-format = "grouped"
From 9dcd7d5200270490c81547f342328d6abe691380 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 5 Sep 2025 23:03:35 +0000
Subject: [PATCH 2/7] feat(api): api update
---
.stats.yml | 8 +-
api.md | 9 +-
.../resources/devboxes/executions.py | 38 ++--
.../types/devboxes/__init__.py | 14 +-
...execution_stream_stderr_updates_params.py} | 4 +-
...xecution_stream_stderr_updates_response.py | 7 +
.../execution_stream_stdout_updates_params.py | 14 ++
...xecution_stream_stdout_updates_response.py | 7 +
.../types/devboxes/execution_update_chunk.py | 40 ----
.../api_resources/devboxes/test_executions.py | 180 +++++++++++++-----
10 files changed, 205 insertions(+), 116 deletions(-)
rename src/runloop_api_client/types/devboxes/{execution_stream_updates_params.py => execution_stream_stderr_updates_params.py} (70%)
create mode 100644 src/runloop_api_client/types/devboxes/execution_stream_stderr_updates_response.py
create mode 100644 src/runloop_api_client/types/devboxes/execution_stream_stdout_updates_params.py
create mode 100644 src/runloop_api_client/types/devboxes/execution_stream_stdout_updates_response.py
delete mode 100644 src/runloop_api_client/types/devboxes/execution_update_chunk.py
diff --git a/.stats.yml b/.stats.yml
index 900ce23a3..3d29bf629 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 101
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-6b4c63a026f224ec02ccd715e063e07107b545bb859218afaac2b3df84cd227a.yml
-openapi_spec_hash: 76072cd766a9c45cff8890bb2bb8b1d5
-config_hash: a8ac5e38099129b07ae4decb0774719d
+configured_endpoints: 102
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-d709c5c5b17fec3a155d3367a00e123712be9fabdc802d7f2069b77b79883057.yml
+openapi_spec_hash: 7a92b2d1612b211cc7507bdc49adf657
+config_hash: 3b21dd730a91da5e18dd16ab3e1870a8
diff --git a/api.md b/api.md
index 0c827fe6b..7098ecd64 100644
--- a/api.md
+++ b/api.md
@@ -257,7 +257,11 @@ Methods:
Types:
```python
-from runloop_api_client.types.devboxes import ExecutionUpdateChunk
+from runloop_api_client.types.devboxes import (
+ ExecutionUpdateChunk,
+ ExecutionStreamStderrUpdatesResponse,
+ ExecutionStreamStdoutUpdatesResponse,
+)
```
Methods:
@@ -266,7 +270,8 @@ Methods:
- client.devboxes.executions.execute_async(id, \*\*params) -> DevboxAsyncExecutionDetailView
- client.devboxes.executions.execute_sync(id, \*\*params) -> DevboxExecutionDetailView
- client.devboxes.executions.kill(execution_id, \*, devbox_id, \*\*params) -> DevboxAsyncExecutionDetailView
-- client.devboxes.executions.stream_stdout_updates(execution_id, \*, devbox_id, \*\*params) -> DevboxAsyncExecutionDetailView
+- client.devboxes.executions.stream_stderr_updates(execution_id, \*, devbox_id, \*\*params) -> str
+- client.devboxes.executions.stream_stdout_updates(execution_id, \*, devbox_id, \*\*params) -> str
# Scenarios
diff --git a/src/runloop_api_client/resources/devboxes/executions.py b/src/runloop_api_client/resources/devboxes/executions.py
index 0002113ff..2bd7a728d 100755
--- a/src/runloop_api_client/resources/devboxes/executions.py
+++ b/src/runloop_api_client/resources/devboxes/executions.py
@@ -26,12 +26,14 @@
execution_retrieve_params,
execution_execute_sync_params,
execution_execute_async_params,
- execution_stream_updates_params,
+ execution_stream_stderr_updates_params,
+ execution_stream_stdout_updates_params,
)
from ...lib.polling_async import async_poll_until
from ...types.devbox_execution_detail_view import DevboxExecutionDetailView
-from ...types.devboxes.execution_update_chunk import ExecutionUpdateChunk
from ...types.devbox_async_execution_detail_view import DevboxAsyncExecutionDetailView
+from ...types.devboxes.execution_stream_stderr_updates_response import ExecutionStreamStderrUpdatesResponse
+from ...types.devboxes.execution_stream_stdout_updates_response import ExecutionStreamStdoutUpdatesResponse
__all__ = ["ExecutionsResource", "AsyncExecutionsResource"]
@@ -338,7 +340,7 @@ def stream_stdout_updates(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
- ) -> Stream[ExecutionUpdateChunk]:
+ ) -> Stream[ExecutionStreamStdoutUpdatesResponse]:
"""
Tails the stdout logs for the given execution with SSE streaming
@@ -366,7 +368,7 @@ def stream_stdout_updates(
extra_body=extra_body,
timeout=timeout,
query=maybe_transform(
- {"offset": offset}, execution_stream_updates_params.ExecutionStreamUpdatesParams
+ {"offset": offset}, execution_stream_stdout_updates_params.ExecutionStreamStdoutUpdatesParams
),
),
cast_to=DevboxAsyncExecutionDetailView,
@@ -384,7 +386,7 @@ def create_stream(last_offset: str | None) -> Stream[ExecutionUpdateChunk]:
extra_body=extra_body,
timeout=timeout,
query=maybe_transform(
- {"offset": new_offset}, execution_stream_updates_params.ExecutionStreamUpdatesParams
+ {"offset": new_offset}, execution_stream_stdout_updates_params.ExecutionStreamStdoutUpdatesParams
),
),
cast_to=DevboxAsyncExecutionDetailView,
@@ -445,7 +447,7 @@ def stream_stderr_updates(
extra_body=extra_body,
timeout=timeout,
query=maybe_transform(
- {"offset": offset}, execution_stream_updates_params.ExecutionStreamUpdatesParams
+ {"offset": offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams
),
),
cast_to=DevboxAsyncExecutionDetailView,
@@ -463,7 +465,7 @@ def create_stream(last_offset: str | None) -> Stream[ExecutionUpdateChunk]:
extra_body=extra_body,
timeout=timeout,
query=maybe_transform(
- {"offset": new_offset}, execution_stream_updates_params.ExecutionStreamUpdatesParams
+ {"offset": new_offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams
),
),
cast_to=DevboxAsyncExecutionDetailView,
@@ -765,7 +767,7 @@ async def kill(
cast_to=DevboxAsyncExecutionDetailView,
)
- async def stream_updates(
+ async def stream_stderr_updates(
self,
execution_id: str,
*,
@@ -777,9 +779,9 @@ async def stream_updates(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
- ) -> AsyncStream[ExecutionUpdateChunk]:
+ ) -> AsyncStream[ExecutionStreamStderrUpdatesResponse]:
"""
- Tails the logs for the given execution with SSE streaming
+ Tails the stderr logs for the given execution with SSE streaming
Args:
offset: The byte offset to start the stream from
@@ -799,14 +801,14 @@ async def stream_updates(
# If caller requested a raw or streaming response wrapper, return the underlying stream as-is
if extra_headers and extra_headers.get(RAW_RESPONSE_HEADER):
return await self._get(
- f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_updates",
+ f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates",
options=make_request_options(
extra_headers=extra_headers,
extra_query=extra_query,
extra_body=extra_body,
timeout=timeout,
query=await async_maybe_transform(
- {"offset": offset}, execution_stream_updates_params.ExecutionStreamUpdatesParams
+ {"offset": offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams
),
),
cast_to=DevboxAsyncExecutionDetailView,
@@ -817,14 +819,14 @@ async def stream_updates(
async def create_stream(last_offset: str | None) -> AsyncStream[ExecutionUpdateChunk]:
new_offset = last_offset if last_offset is not None else (None if isinstance(offset, NotGiven) else offset)
return await self._get(
- f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_updates",
+ f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates",
options=make_request_options(
extra_headers=extra_headers,
extra_query=extra_query,
extra_body=extra_body,
timeout=timeout,
query=await async_maybe_transform(
- {"offset": new_offset}, execution_stream_updates_params.ExecutionStreamUpdatesParams
+ {"offset": new_offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams
),
),
cast_to=DevboxAsyncExecutionDetailView,
@@ -885,7 +887,7 @@ async def stream_stdout_updates(
extra_body=extra_body,
timeout=timeout,
query=await async_maybe_transform(
- {"offset": offset}, execution_stream_updates_params.ExecutionStreamUpdatesParams
+ {"offset": offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams
),
),
cast_to=DevboxAsyncExecutionDetailView,
@@ -903,7 +905,7 @@ async def create_stream(last_offset: str | None) -> AsyncStream[ExecutionUpdateC
extra_body=extra_body,
timeout=timeout,
query=await async_maybe_transform(
- {"offset": new_offset}, execution_stream_updates_params.ExecutionStreamUpdatesParams
+ {"offset": new_offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams
),
),
cast_to=DevboxAsyncExecutionDetailView,
@@ -964,7 +966,7 @@ async def stream_stderr_updates(
extra_body=extra_body,
timeout=timeout,
query=await async_maybe_transform(
- {"offset": offset}, execution_stream_updates_params.ExecutionStreamUpdatesParams
+ {"offset": offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams
),
),
cast_to=DevboxAsyncExecutionDetailView,
@@ -982,7 +984,7 @@ async def create_stream(last_offset: str | None) -> AsyncStream[ExecutionUpdateC
extra_body=extra_body,
timeout=timeout,
query=await async_maybe_transform(
- {"offset": new_offset}, execution_stream_updates_params.ExecutionStreamUpdatesParams
+ {"offset": new_offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams
),
),
cast_to=DevboxAsyncExecutionDetailView,
diff --git a/src/runloop_api_client/types/devboxes/__init__.py b/src/runloop_api_client/types/devboxes/__init__.py
index 43e642744..7ee0b171d 100644
--- a/src/runloop_api_client/types/devboxes/__init__.py
+++ b/src/runloop_api_client/types/devboxes/__init__.py
@@ -52,7 +52,6 @@
from .watched_file_response import WatchedFileResponse as WatchedFileResponse
from .code_description_param import CodeDescriptionParam as CodeDescriptionParam
from .computer_create_params import ComputerCreateParams as ComputerCreateParams
-from .execution_update_chunk import ExecutionUpdateChunk as ExecutionUpdateChunk
from .file_contents_response import FileContentsResponse as FileContentsResponse
from .health_status_response import HealthStatusResponse as HealthStatusResponse
from .lsp_diagnostics_params import LspDiagnosticsParams as LspDiagnosticsParams
@@ -77,7 +76,6 @@
from .code_action_application_result import CodeActionApplicationResult as CodeActionApplicationResult
from .execution_execute_async_params import ExecutionExecuteAsyncParams as ExecutionExecuteAsyncParams
from .lsp_set_watch_directory_params import LspSetWatchDirectoryParams as LspSetWatchDirectoryParams
-from .execution_stream_updates_params import ExecutionStreamUpdatesParams as ExecutionStreamUpdatesParams
from .lsp_get_code_segment_info_params import LspGetCodeSegmentInfoParams as LspGetCodeSegmentInfoParams
from .lsp_set_watch_directory_response import LspSetWatchDirectoryResponse as LspSetWatchDirectoryResponse
from .computer_mouse_interaction_params import ComputerMouseInteractionParams as ComputerMouseInteractionParams
@@ -91,6 +89,18 @@
from .computer_keyboard_interaction_response import (
ComputerKeyboardInteractionResponse as ComputerKeyboardInteractionResponse,
)
+from .execution_stream_stderr_updates_params import (
+ ExecutionStreamStderrUpdatesParams as ExecutionStreamStderrUpdatesParams,
+)
+from .execution_stream_stdout_updates_params import (
+ ExecutionStreamStdoutUpdatesParams as ExecutionStreamStdoutUpdatesParams,
+)
+from .execution_stream_stderr_updates_response import (
+ ExecutionStreamStderrUpdatesResponse as ExecutionStreamStderrUpdatesResponse,
+)
+from .execution_stream_stdout_updates_response import (
+ ExecutionStreamStdoutUpdatesResponse as ExecutionStreamStdoutUpdatesResponse,
+)
from .lsp_get_code_actions_for_diagnostic_params import (
LspGetCodeActionsForDiagnosticParams as LspGetCodeActionsForDiagnosticParams,
)
diff --git a/src/runloop_api_client/types/devboxes/execution_stream_updates_params.py b/src/runloop_api_client/types/devboxes/execution_stream_stderr_updates_params.py
similarity index 70%
rename from src/runloop_api_client/types/devboxes/execution_stream_updates_params.py
rename to src/runloop_api_client/types/devboxes/execution_stream_stderr_updates_params.py
index 3473cbc2c..b82639556 100644
--- a/src/runloop_api_client/types/devboxes/execution_stream_updates_params.py
+++ b/src/runloop_api_client/types/devboxes/execution_stream_stderr_updates_params.py
@@ -4,10 +4,10 @@
from typing_extensions import Required, TypedDict
-__all__ = ["ExecutionStreamUpdatesParams"]
+__all__ = ["ExecutionStreamStderrUpdatesParams"]
-class ExecutionStreamUpdatesParams(TypedDict, total=False):
+class ExecutionStreamStderrUpdatesParams(TypedDict, total=False):
devbox_id: Required[str]
offset: str
diff --git a/src/runloop_api_client/types/devboxes/execution_stream_stderr_updates_response.py b/src/runloop_api_client/types/devboxes/execution_stream_stderr_updates_response.py
new file mode 100644
index 000000000..1f7ec2adc
--- /dev/null
+++ b/src/runloop_api_client/types/devboxes/execution_stream_stderr_updates_response.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["ExecutionStreamStderrUpdatesResponse"]
+
+ExecutionStreamStderrUpdatesResponse: TypeAlias = str
diff --git a/src/runloop_api_client/types/devboxes/execution_stream_stdout_updates_params.py b/src/runloop_api_client/types/devboxes/execution_stream_stdout_updates_params.py
new file mode 100644
index 000000000..7296156b0
--- /dev/null
+++ b/src/runloop_api_client/types/devboxes/execution_stream_stdout_updates_params.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["ExecutionStreamStdoutUpdatesParams"]
+
+
+class ExecutionStreamStdoutUpdatesParams(TypedDict, total=False):
+ devbox_id: Required[str]
+
+ offset: str
+ """The byte offset to start the stream from"""
diff --git a/src/runloop_api_client/types/devboxes/execution_stream_stdout_updates_response.py b/src/runloop_api_client/types/devboxes/execution_stream_stdout_updates_response.py
new file mode 100644
index 000000000..da6e2bd20
--- /dev/null
+++ b/src/runloop_api_client/types/devboxes/execution_stream_stdout_updates_response.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["ExecutionStreamStdoutUpdatesResponse"]
+
+ExecutionStreamStdoutUpdatesResponse: TypeAlias = str
diff --git a/src/runloop_api_client/types/devboxes/execution_update_chunk.py b/src/runloop_api_client/types/devboxes/execution_update_chunk.py
deleted file mode 100644
index f5366d0cb..000000000
--- a/src/runloop_api_client/types/devboxes/execution_update_chunk.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import Optional
-from typing_extensions import Literal
-
-from ..._models import BaseModel
-
-__all__ = ["ExecutionUpdateChunk"]
-
-
-class ExecutionUpdateChunk(BaseModel):
- devbox_id: str
- """Devbox id where command was executed."""
-
- execution_id: str
- """Ephemeral id of the execution in progress."""
-
- status: Literal["queued", "running", "completed"]
- """Current status of the execution."""
-
- exit_status: Optional[int] = None
- """Exit code of command execution.
-
- This field will remain unset until the execution has completed.
- """
-
- shell_name: Optional[str] = None
- """Shell name."""
-
- stderr: Optional[str] = None
- """Standard error generated by command.
-
- This field will remain unset until the execution has completed.
- """
-
- stdout: Optional[str] = None
- """Standard out generated by command.
-
- This field will remain unset until the execution has completed.
- """
diff --git a/tests/api_resources/devboxes/test_executions.py b/tests/api_resources/devboxes/test_executions.py
index 9b932b160..0dd236647 100755
--- a/tests/api_resources/devboxes/test_executions.py
+++ b/tests/api_resources/devboxes/test_executions.py
@@ -239,22 +239,16 @@ def test_path_params_kill(self, client: Runloop) -> None:
)
@parametrize
- def test_method_stream_stdout_updates(self, client: Runloop, respx_mock: MockRouter) -> None:
- respx_mock.get("/v1/devboxes/devbox_id/executions/execution_id/stream_stdout_updates").mock(
- return_value=httpx.Response(200)
- )
- execution_stream = client.devboxes.executions.stream_stdout_updates(
+ def test_method_stream_stderr_updates(self, client: Runloop) -> None:
+ execution_stream = client.devboxes.executions.stream_stderr_updates(
execution_id="execution_id",
devbox_id="devbox_id",
)
execution_stream.response.close()
@parametrize
- def test_method_stream_stdout_updates_with_all_params(self, client: Runloop, respx_mock: MockRouter) -> None:
- respx_mock.get("/v1/devboxes/devbox_id/executions/execution_id/stream_stdout_updates").mock(
- return_value=httpx.Response(200)
- )
- execution_stream = client.devboxes.executions.stream_stdout_updates(
+ def test_method_stream_stderr_updates_with_all_params(self, client: Runloop) -> None:
+ execution_stream = client.devboxes.executions.stream_stderr_updates(
execution_id="execution_id",
devbox_id="devbox_id",
offset="offset",
@@ -262,11 +256,8 @@ def test_method_stream_stdout_updates_with_all_params(self, client: Runloop, res
execution_stream.response.close()
@parametrize
- def test_raw_response_stream_stdout_updates(self, client: Runloop, respx_mock: MockRouter) -> None:
- respx_mock.get("/v1/devboxes/devbox_id/executions/execution_id/stream_stdout_updates").mock(
- return_value=httpx.Response(200)
- )
- response = client.devboxes.executions.with_raw_response.stream_stdout_updates(
+ def test_raw_response_stream_stderr_updates(self, client: Runloop) -> None:
+ response = client.devboxes.executions.with_raw_response.stream_stderr_updates(
execution_id="execution_id",
devbox_id="devbox_id",
)
@@ -276,11 +267,8 @@ def test_raw_response_stream_stdout_updates(self, client: Runloop, respx_mock: M
stream.close()
@parametrize
- def test_streaming_response_stream_stdout_updates(self, client: Runloop, respx_mock: MockRouter) -> None:
- respx_mock.get("/v1/devboxes/devbox_id/executions/execution_id/stream_stdout_updates").mock(
- return_value=httpx.Response(200)
- )
- with client.devboxes.executions.with_streaming_response.stream_stdout_updates(
+ def test_streaming_response_stream_stderr_updates(self, client: Runloop) -> None:
+ with client.devboxes.executions.with_streaming_response.stream_stderr_updates(
execution_id="execution_id",
devbox_id="devbox_id",
) as response:
@@ -293,9 +281,9 @@ def test_streaming_response_stream_stdout_updates(self, client: Runloop, respx_m
assert cast(Any, response.is_closed) is True
@parametrize
- def test_path_params_stream_stdout_updates(self, client: Runloop) -> None:
+ def test_path_params_stream_stderr_updates(self, client: Runloop) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `devbox_id` but received ''"):
- client.devboxes.executions.with_raw_response.stream_stdout_updates(
+ client.devboxes.executions.with_raw_response.stream_stderr_updates(
execution_id="execution_id",
devbox_id="",
)
@@ -362,6 +350,62 @@ def fake_get(_path: str, *, options: Any, **_kwargs: Any):
assert calls[1] is not None
assert seen_offsets == ["5", "9", "10"]
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `execution_id` but received ''"):
+ client.devboxes.executions.with_raw_response.stream_stderr_updates(
+ execution_id="",
+ devbox_id="devbox_id",
+ )
+
+ @parametrize
+ def test_method_stream_stdout_updates(self, client: Runloop) -> None:
+ execution_stream = client.devboxes.executions.stream_stdout_updates(
+ execution_id="execution_id",
+ devbox_id="devbox_id",
+ )
+ execution_stream.response.close()
+
+ @parametrize
+ def test_method_stream_stdout_updates_with_all_params(self, client: Runloop) -> None:
+ execution_stream = client.devboxes.executions.stream_stdout_updates(
+ execution_id="execution_id",
+ devbox_id="devbox_id",
+ offset="offset",
+ )
+ execution_stream.response.close()
+
+ @parametrize
+ def test_raw_response_stream_stdout_updates(self, client: Runloop) -> None:
+ response = client.devboxes.executions.with_raw_response.stream_stdout_updates(
+ execution_id="execution_id",
+ devbox_id="devbox_id",
+ )
+
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ stream = response.parse()
+ stream.close()
+
+ @parametrize
+ def test_streaming_response_stream_stdout_updates(self, client: Runloop) -> None:
+ with client.devboxes.executions.with_streaming_response.stream_stdout_updates(
+ execution_id="execution_id",
+ devbox_id="devbox_id",
+ ) 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_path_params_stream_stdout_updates(self, client: Runloop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `devbox_id` but received ''"):
+ client.devboxes.executions.with_raw_response.stream_stdout_updates(
+ execution_id="execution_id",
+ devbox_id="",
+ )
+
with pytest.raises(ValueError, match=r"Expected a non-empty value for `execution_id` but received ''"):
client.devboxes.executions.with_raw_response.stream_stdout_updates(
execution_id="",
@@ -755,24 +799,16 @@ async def test_path_params_kill(self, async_client: AsyncRunloop) -> None:
)
@parametrize
- async def test_method_stream_stdout_updates(self, async_client: AsyncRunloop, respx_mock: MockRouter) -> None:
- respx_mock.get("/v1/devboxes/devbox_id/executions/execution_id/stream_stdout_updates").mock(
- return_value=httpx.Response(200)
- )
- execution_stream = await async_client.devboxes.executions.stream_stdout_updates(
+ async def test_method_stream_stderr_updates(self, async_client: AsyncRunloop) -> None:
+ execution_stream = await async_client.devboxes.executions.stream_stderr_updates(
execution_id="execution_id",
devbox_id="devbox_id",
)
await execution_stream.response.aclose()
@parametrize
- async def test_method_stream_stdout_updates_with_all_params(
- self, async_client: AsyncRunloop, respx_mock: MockRouter
- ) -> None:
- respx_mock.get("/v1/devboxes/devbox_id/executions/execution_id/stream_stdout_updates").mock(
- return_value=httpx.Response(200)
- )
- execution_stream = await async_client.devboxes.executions.stream_stdout_updates(
+ async def test_method_stream_stderr_updates_with_all_params(self, async_client: AsyncRunloop) -> None:
+ execution_stream = await async_client.devboxes.executions.stream_stderr_updates(
execution_id="execution_id",
devbox_id="devbox_id",
offset="offset",
@@ -780,11 +816,8 @@ async def test_method_stream_stdout_updates_with_all_params(
await execution_stream.response.aclose()
@parametrize
- async def test_raw_response_stream_stdout_updates(self, async_client: AsyncRunloop, respx_mock: MockRouter) -> None:
- respx_mock.get("/v1/devboxes/devbox_id/executions/execution_id/stream_stdout_updates").mock(
- return_value=httpx.Response(200)
- )
- response = await async_client.devboxes.executions.with_raw_response.stream_stdout_updates(
+ async def test_raw_response_stream_stderr_updates(self, async_client: AsyncRunloop) -> None:
+ response = await async_client.devboxes.executions.with_raw_response.stream_stderr_updates(
execution_id="execution_id",
devbox_id="devbox_id",
)
@@ -794,13 +827,8 @@ async def test_raw_response_stream_stdout_updates(self, async_client: AsyncRunlo
await stream.close()
@parametrize
- async def test_streaming_response_stream_stdout_updates(
- self, async_client: AsyncRunloop, respx_mock: MockRouter
- ) -> None:
- respx_mock.get("/v1/devboxes/devbox_id/executions/execution_id/stream_stdout_updates").mock(
- return_value=httpx.Response(200)
- )
- async with async_client.devboxes.executions.with_streaming_response.stream_stdout_updates(
+ async def test_streaming_response_stream_stderr_updates(self, async_client: AsyncRunloop) -> None:
+ async with async_client.devboxes.executions.with_streaming_response.stream_stderr_updates(
execution_id="execution_id",
devbox_id="devbox_id",
) as response:
@@ -813,9 +841,9 @@ async def test_streaming_response_stream_stdout_updates(
assert cast(Any, response.is_closed) is True
@parametrize
- async def test_path_params_stream_stdout_updates(self, async_client: AsyncRunloop) -> None:
+ async def test_path_params_stream_stderr_updates(self, async_client: AsyncRunloop) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `devbox_id` but received ''"):
- await async_client.devboxes.executions.with_raw_response.stream_stdout_updates(
+ await async_client.devboxes.executions.with_raw_response.stream_stderr_updates(
execution_id="execution_id",
devbox_id="",
)
@@ -883,6 +911,62 @@ async def fake_get(_path: str, *, options: Any, **_kwargs: Any):
assert calls[1] is not None
assert seen_offsets == ["5", "9", "10"]
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `execution_id` but received ''"):
+ await async_client.devboxes.executions.with_raw_response.stream_stderr_updates(
+ execution_id="",
+ devbox_id="devbox_id",
+ )
+
+ @parametrize
+ async def test_method_stream_stdout_updates(self, async_client: AsyncRunloop) -> None:
+ execution_stream = await async_client.devboxes.executions.stream_stdout_updates(
+ execution_id="execution_id",
+ devbox_id="devbox_id",
+ )
+ await execution_stream.response.aclose()
+
+ @parametrize
+ async def test_method_stream_stdout_updates_with_all_params(self, async_client: AsyncRunloop) -> None:
+ execution_stream = await async_client.devboxes.executions.stream_stdout_updates(
+ execution_id="execution_id",
+ devbox_id="devbox_id",
+ offset="offset",
+ )
+ await execution_stream.response.aclose()
+
+ @parametrize
+ async def test_raw_response_stream_stdout_updates(self, async_client: AsyncRunloop) -> None:
+ response = await async_client.devboxes.executions.with_raw_response.stream_stdout_updates(
+ execution_id="execution_id",
+ devbox_id="devbox_id",
+ )
+
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ stream = await response.parse()
+ await stream.close()
+
+ @parametrize
+ async def test_streaming_response_stream_stdout_updates(self, async_client: AsyncRunloop) -> None:
+ async with async_client.devboxes.executions.with_streaming_response.stream_stdout_updates(
+ execution_id="execution_id",
+ devbox_id="devbox_id",
+ ) 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_path_params_stream_stdout_updates(self, async_client: AsyncRunloop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `devbox_id` but received ''"):
+ await async_client.devboxes.executions.with_raw_response.stream_stdout_updates(
+ execution_id="execution_id",
+ devbox_id="",
+ )
+
with pytest.raises(ValueError, match=r"Expected a non-empty value for `execution_id` but received ''"):
await async_client.devboxes.executions.with_raw_response.stream_stdout_updates(
execution_id="",
From 9a11b39772e3980738d78a06efdfba60edbab3a7 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 6 Sep 2025 03:46:08 +0000
Subject: [PATCH 3/7] chore(tests): simplify `get_platform` test
`nest_asyncio` is archived and broken on some platforms so it's
not worth keeping in our test suite.
---
pyproject.toml | 1 -
requirements-dev.lock | 1 -
tests/test_client.py | 53 +++++--------------------------------------
3 files changed, 6 insertions(+), 49 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index cf2752640..3d68d633a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -57,7 +57,6 @@ dev-dependencies = [
"dirty-equals>=0.6.0",
"importlib-metadata>=6.7.0",
"rich>=13.7.1",
- "nest_asyncio==1.6.0",
"pytest-xdist>=3.6.1",
]
diff --git a/requirements-dev.lock b/requirements-dev.lock
index de857a2d6..2a74d01aa 100644
--- a/requirements-dev.lock
+++ b/requirements-dev.lock
@@ -75,7 +75,6 @@ multidict==6.4.4
mypy==1.14.1
mypy-extensions==1.0.0
# via mypy
-nest-asyncio==1.6.0
nodeenv==1.8.0
# via pyright
nox==2023.4.22
diff --git a/tests/test_client.py b/tests/test_client.py
index cb84f9ebe..ca53896b3 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -6,13 +6,10 @@
import os
import sys
import json
-import time
import asyncio
import inspect
-import subprocess
import tracemalloc
from typing import Any, Union, cast
-from textwrap import dedent
from unittest import mock
from typing_extensions import Literal
@@ -23,14 +20,17 @@
from runloop_api_client import Runloop, AsyncRunloop, APIResponseValidationError
from runloop_api_client._types import Omit
+from runloop_api_client._utils import asyncify
from runloop_api_client._models import BaseModel, FinalRequestOptions
from runloop_api_client._exceptions import RunloopError, APIStatusError, APITimeoutError, APIResponseValidationError
from runloop_api_client._base_client import (
DEFAULT_TIMEOUT,
HTTPX_DEFAULT_TIMEOUT,
BaseClient,
+ OtherPlatform,
DefaultHttpxClient,
DefaultAsyncHttpxClient,
+ get_platform,
make_request_options,
)
@@ -1727,50 +1727,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
assert response.http_request.headers.get("x-stainless-retry-count") == "42"
- def test_get_platform(self) -> None:
- # A previous implementation of asyncify could leave threads unterminated when
- # used with nest_asyncio.
- #
- # Since nest_asyncio.apply() is global and cannot be un-applied, this
- # test is run in a separate process to avoid affecting other tests.
- test_code = dedent("""
- import asyncio
- import nest_asyncio
- import threading
-
- from runloop_api_client._utils import asyncify
- from runloop_api_client._base_client import get_platform
-
- async def test_main() -> None:
- result = await asyncify(get_platform)()
- print(result)
- for thread in threading.enumerate():
- print(thread.name)
-
- nest_asyncio.apply()
- asyncio.run(test_main())
- """)
- with subprocess.Popen(
- [sys.executable, "-c", test_code],
- text=True,
- ) as process:
- timeout = 10 # seconds
-
- start_time = time.monotonic()
- while True:
- return_code = process.poll()
- if return_code is not None:
- if return_code != 0:
- raise AssertionError("calling get_platform using asyncify resulted in a non-zero exit code")
-
- # success
- break
-
- if time.monotonic() - start_time > timeout:
- process.kill()
- raise AssertionError("calling get_platform using asyncify resulted in a hung process")
-
- time.sleep(0.1)
+ async def test_get_platform(self) -> None:
+ platform = await asyncify(get_platform)()
+ assert isinstance(platform, (str, OtherPlatform))
async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None:
# Test that the proxy environment variables are set correctly
From 6f4e3974439b534a205abcc347656dd349b7e256 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 8 Sep 2025 19:51:53 +0000
Subject: [PATCH 4/7] feat(api): api update
---
.stats.yml | 6 +-
api.md | 10 +-
.../resources/devboxes/executions.py | 333 ++++--------------
.../types/devboxes/__init__.py | 7 +-
...xecution_stream_stderr_updates_response.py | 7 -
...xecution_stream_stdout_updates_response.py | 7 -
.../types/devboxes/execution_update_chunk.py | 15 +
7 files changed, 84 insertions(+), 301 deletions(-)
delete mode 100644 src/runloop_api_client/types/devboxes/execution_stream_stderr_updates_response.py
delete mode 100644 src/runloop_api_client/types/devboxes/execution_stream_stdout_updates_response.py
create mode 100644 src/runloop_api_client/types/devboxes/execution_update_chunk.py
diff --git a/.stats.yml b/.stats.yml
index 3d29bf629..3426ad757 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 102
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-d709c5c5b17fec3a155d3367a00e123712be9fabdc802d7f2069b77b79883057.yml
-openapi_spec_hash: 7a92b2d1612b211cc7507bdc49adf657
-config_hash: 3b21dd730a91da5e18dd16ab3e1870a8
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-aa2f6b7bd5f38374ef743af2f5399fc37358408730dcbb1305e181bb91a2e903.yml
+openapi_spec_hash: a3f793428f6b5de9a59ba8c80ee8bbea
+config_hash: a74944d06979c9df5e08aee874ae5caa
diff --git a/api.md b/api.md
index 7098ecd64..807a478ed 100644
--- a/api.md
+++ b/api.md
@@ -257,11 +257,7 @@ Methods:
Types:
```python
-from runloop_api_client.types.devboxes import (
- ExecutionUpdateChunk,
- ExecutionStreamStderrUpdatesResponse,
- ExecutionStreamStdoutUpdatesResponse,
-)
+from runloop_api_client.types.devboxes import ExecutionUpdateChunk
```
Methods:
@@ -270,8 +266,8 @@ Methods:
- client.devboxes.executions.execute_async(id, \*\*params) -> DevboxAsyncExecutionDetailView
- client.devboxes.executions.execute_sync(id, \*\*params) -> DevboxExecutionDetailView
- client.devboxes.executions.kill(execution_id, \*, devbox_id, \*\*params) -> DevboxAsyncExecutionDetailView
-- client.devboxes.executions.stream_stderr_updates(execution_id, \*, devbox_id, \*\*params) -> str
-- client.devboxes.executions.stream_stdout_updates(execution_id, \*, devbox_id, \*\*params) -> str
+- client.devboxes.executions.stream_stderr_updates(execution_id, \*, devbox_id, \*\*params) -> ExecutionUpdateChunk
+- client.devboxes.executions.stream_stdout_updates(execution_id, \*, devbox_id, \*\*params) -> ExecutionUpdateChunk
# Scenarios
diff --git a/src/runloop_api_client/resources/devboxes/executions.py b/src/runloop_api_client/resources/devboxes/executions.py
index 2bd7a728d..c8a40a4e6 100755
--- a/src/runloop_api_client/resources/devboxes/executions.py
+++ b/src/runloop_api_client/resources/devboxes/executions.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing import Optional, cast
+from typing import Optional
import httpx
@@ -16,8 +16,8 @@
async_to_raw_response_wrapper,
async_to_streamed_response_wrapper,
)
-from ..._constants import DEFAULT_TIMEOUT, RAW_RESPONSE_HEADER
-from ..._streaming import Stream, AsyncStream, ReconnectingStream, AsyncReconnectingStream
+from ..._constants import DEFAULT_TIMEOUT
+from ..._streaming import Stream, AsyncStream
from ..._exceptions import APIStatusError, APITimeoutError
from ...lib.polling import PollingConfig, poll_until
from ..._base_client import make_request_options
@@ -31,9 +31,8 @@
)
from ...lib.polling_async import async_poll_until
from ...types.devbox_execution_detail_view import DevboxExecutionDetailView
+from ...types.devboxes.execution_update_chunk import ExecutionUpdateChunk
from ...types.devbox_async_execution_detail_view import DevboxAsyncExecutionDetailView
-from ...types.devboxes.execution_stream_stderr_updates_response import ExecutionStreamStderrUpdatesResponse
-from ...types.devboxes.execution_stream_stdout_updates_response import ExecutionStreamStdoutUpdatesResponse
__all__ = ["ExecutionsResource", "AsyncExecutionsResource"]
@@ -328,7 +327,7 @@ def kill(
cast_to=DevboxAsyncExecutionDetailView,
)
- def stream_stdout_updates(
+ def stream_stderr_updates(
self,
execution_id: str,
*,
@@ -340,9 +339,9 @@ def stream_stdout_updates(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
- ) -> Stream[ExecutionStreamStdoutUpdatesResponse]:
+ ) -> Stream[ExecutionUpdateChunk]:
"""
- Tails the stdout logs for the given execution with SSE streaming
+ Tails the stderr logs for the given execution with SSE streaming
Args:
offset: The byte offset to start the stream from
@@ -359,55 +358,23 @@ def stream_stdout_updates(
raise ValueError(f"Expected a non-empty value for `devbox_id` but received {devbox_id!r}")
if not execution_id:
raise ValueError(f"Expected a non-empty value for `execution_id` but received {execution_id!r}")
- if extra_headers and extra_headers.get(RAW_RESPONSE_HEADER):
- return self._get(
- f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stdout_updates",
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform(
- {"offset": offset}, execution_stream_stdout_updates_params.ExecutionStreamStdoutUpdatesParams
- ),
- ),
- cast_to=DevboxAsyncExecutionDetailView,
- stream=True,
- stream_cls=Stream[ExecutionUpdateChunk],
- )
-
- def create_stream(last_offset: str | None) -> Stream[ExecutionUpdateChunk]:
- new_offset = last_offset if last_offset is not None else (None if isinstance(offset, NotGiven) else offset)
- return self._get(
- f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stdout_updates",
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform(
- {"offset": new_offset}, execution_stream_stdout_updates_params.ExecutionStreamStdoutUpdatesParams
- ),
+ return self._get(
+ f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {"offset": offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams
),
- cast_to=DevboxAsyncExecutionDetailView,
- stream=True,
- stream_cls=Stream[ExecutionUpdateChunk],
- )
-
- initial_stream = create_stream(None)
-
- def get_offset(item: ExecutionUpdateChunk) -> str | None:
- value = getattr(item, "offset", None)
- if value is None:
- return None
- return str(value)
-
- return cast(
- Stream[ExecutionUpdateChunk],
- ReconnectingStream(current_stream=initial_stream, stream_creator=create_stream, get_offset=get_offset),
+ ),
+ cast_to=ExecutionUpdateChunk,
+ stream=True,
+ stream_cls=Stream[ExecutionUpdateChunk],
)
- def stream_stderr_updates(
+ def stream_stdout_updates(
self,
execution_id: str,
*,
@@ -421,7 +388,7 @@ def stream_stderr_updates(
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
) -> Stream[ExecutionUpdateChunk]:
"""
- Tails the stderr logs for the given execution with SSE streaming
+ Tails the stdout logs for the given execution with SSE streaming
Args:
offset: The byte offset to start the stream from
@@ -438,52 +405,20 @@ def stream_stderr_updates(
raise ValueError(f"Expected a non-empty value for `devbox_id` but received {devbox_id!r}")
if not execution_id:
raise ValueError(f"Expected a non-empty value for `execution_id` but received {execution_id!r}")
- if extra_headers and extra_headers.get(RAW_RESPONSE_HEADER):
- return self._get(
- f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates",
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform(
- {"offset": offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams
- ),
- ),
- cast_to=DevboxAsyncExecutionDetailView,
- stream=True,
- stream_cls=Stream[ExecutionUpdateChunk],
- )
-
- def create_stream(last_offset: str | None) -> Stream[ExecutionUpdateChunk]:
- new_offset = last_offset if last_offset is not None else (None if isinstance(offset, NotGiven) else offset)
- return self._get(
- f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates",
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform(
- {"offset": new_offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams
- ),
+ return self._get(
+ f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stdout_updates",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {"offset": offset}, execution_stream_stdout_updates_params.ExecutionStreamStdoutUpdatesParams
),
- cast_to=DevboxAsyncExecutionDetailView,
- stream=True,
- stream_cls=Stream[ExecutionUpdateChunk],
- )
-
- initial_stream = create_stream(None)
-
- def get_offset(item: ExecutionUpdateChunk) -> str | None:
- value = getattr(item, "offset", None)
- if value is None:
- return None
- return str(value)
-
- return cast(
- Stream[ExecutionUpdateChunk],
- ReconnectingStream(current_stream=initial_stream, stream_creator=create_stream, get_offset=get_offset),
+ ),
+ cast_to=ExecutionUpdateChunk,
+ stream=True,
+ stream_cls=Stream[ExecutionUpdateChunk],
)
@@ -779,7 +714,7 @@ async def stream_stderr_updates(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
- ) -> AsyncStream[ExecutionStreamStderrUpdatesResponse]:
+ ) -> AsyncStream[ExecutionUpdateChunk]:
"""
Tails the stderr logs for the given execution with SSE streaming
@@ -798,53 +733,20 @@ async def stream_stderr_updates(
raise ValueError(f"Expected a non-empty value for `devbox_id` but received {devbox_id!r}")
if not execution_id:
raise ValueError(f"Expected a non-empty value for `execution_id` but received {execution_id!r}")
- # If caller requested a raw or streaming response wrapper, return the underlying stream as-is
- if extra_headers and extra_headers.get(RAW_RESPONSE_HEADER):
- return await self._get(
- f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates",
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=await async_maybe_transform(
- {"offset": offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams
- ),
- ),
- cast_to=DevboxAsyncExecutionDetailView,
- stream=True,
- stream_cls=AsyncStream[ExecutionUpdateChunk],
- )
-
- async def create_stream(last_offset: str | None) -> AsyncStream[ExecutionUpdateChunk]:
- new_offset = last_offset if last_offset is not None else (None if isinstance(offset, NotGiven) else offset)
- return await self._get(
- f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates",
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=await async_maybe_transform(
- {"offset": new_offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams
- ),
+ return await self._get(
+ f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {"offset": offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams
),
- cast_to=DevboxAsyncExecutionDetailView,
- stream=True,
- stream_cls=AsyncStream[ExecutionUpdateChunk],
- )
-
- initial_stream = await create_stream(None)
-
- def get_offset(item: ExecutionUpdateChunk) -> str | None:
- value = getattr(item, "offset", None)
- if value is None:
- return None
- return str(value)
-
- return cast(
- AsyncStream[ExecutionUpdateChunk],
- AsyncReconnectingStream(current_stream=initial_stream, stream_creator=create_stream, get_offset=get_offset),
+ ),
+ cast_to=ExecutionUpdateChunk,
+ stream=True,
+ stream_cls=AsyncStream[ExecutionUpdateChunk],
)
async def stream_stdout_updates(
@@ -878,131 +780,20 @@ async def stream_stdout_updates(
raise ValueError(f"Expected a non-empty value for `devbox_id` but received {devbox_id!r}")
if not execution_id:
raise ValueError(f"Expected a non-empty value for `execution_id` but received {execution_id!r}")
- if extra_headers and extra_headers.get(RAW_RESPONSE_HEADER):
- return await self._get(
- f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stdout_updates",
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=await async_maybe_transform(
- {"offset": offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams
- ),
- ),
- cast_to=DevboxAsyncExecutionDetailView,
- stream=True,
- stream_cls=AsyncStream[ExecutionUpdateChunk],
- )
-
- async def create_stream(last_offset: str | None) -> AsyncStream[ExecutionUpdateChunk]:
- new_offset = last_offset if last_offset is not None else (None if isinstance(offset, NotGiven) else offset)
- return await self._get(
- f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stdout_updates",
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=await async_maybe_transform(
- {"offset": new_offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams
- ),
- ),
- cast_to=DevboxAsyncExecutionDetailView,
- stream=True,
- stream_cls=AsyncStream[ExecutionUpdateChunk],
- )
-
- initial_stream = await create_stream(None)
-
- def get_offset(item: ExecutionUpdateChunk) -> str | None:
- value = getattr(item, "offset", None)
- if value is None:
- return None
- return str(value)
-
- return cast(
- AsyncStream[ExecutionUpdateChunk],
- AsyncReconnectingStream(current_stream=initial_stream, stream_creator=create_stream, get_offset=get_offset),
- )
-
- async def stream_stderr_updates(
- self,
- execution_id: str,
- *,
- devbox_id: str,
- offset: str | NotGiven = NOT_GIVEN,
- # 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[ExecutionUpdateChunk]:
- """
- Tails the stderr logs for the given execution with SSE streaming
-
- Args:
- offset: The byte offset to start the stream from
-
- 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
- """
- if not devbox_id:
- raise ValueError(f"Expected a non-empty value for `devbox_id` but received {devbox_id!r}")
- if not execution_id:
- raise ValueError(f"Expected a non-empty value for `execution_id` but received {execution_id!r}")
- if extra_headers and extra_headers.get(RAW_RESPONSE_HEADER):
- return await self._get(
- f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates",
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=await async_maybe_transform(
- {"offset": offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams
- ),
- ),
- cast_to=DevboxAsyncExecutionDetailView,
- stream=True,
- stream_cls=AsyncStream[ExecutionUpdateChunk],
- )
-
- async def create_stream(last_offset: str | None) -> AsyncStream[ExecutionUpdateChunk]:
- new_offset = last_offset if last_offset is not None else (None if isinstance(offset, NotGiven) else offset)
- return await self._get(
- f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates",
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=await async_maybe_transform(
- {"offset": new_offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams
- ),
+ return await self._get(
+ f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stdout_updates",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {"offset": offset}, execution_stream_stdout_updates_params.ExecutionStreamStdoutUpdatesParams
),
- cast_to=DevboxAsyncExecutionDetailView,
- stream=True,
- stream_cls=AsyncStream[ExecutionUpdateChunk],
- )
-
- initial_stream = await create_stream(None)
-
- def get_offset(item: ExecutionUpdateChunk) -> str | None:
- value = getattr(item, "offset", None)
- if value is None:
- return None
- return str(value)
-
- return cast(
- AsyncStream[ExecutionUpdateChunk],
- AsyncReconnectingStream(current_stream=initial_stream, stream_creator=create_stream, get_offset=get_offset),
+ ),
+ cast_to=ExecutionUpdateChunk,
+ stream=True,
+ stream_cls=AsyncStream[ExecutionUpdateChunk],
)
diff --git a/src/runloop_api_client/types/devboxes/__init__.py b/src/runloop_api_client/types/devboxes/__init__.py
index 7ee0b171d..472d68e9a 100644
--- a/src/runloop_api_client/types/devboxes/__init__.py
+++ b/src/runloop_api_client/types/devboxes/__init__.py
@@ -52,6 +52,7 @@
from .watched_file_response import WatchedFileResponse as WatchedFileResponse
from .code_description_param import CodeDescriptionParam as CodeDescriptionParam
from .computer_create_params import ComputerCreateParams as ComputerCreateParams
+from .execution_update_chunk import ExecutionUpdateChunk as ExecutionUpdateChunk
from .file_contents_response import FileContentsResponse as FileContentsResponse
from .health_status_response import HealthStatusResponse as HealthStatusResponse
from .lsp_diagnostics_params import LspDiagnosticsParams as LspDiagnosticsParams
@@ -95,12 +96,6 @@
from .execution_stream_stdout_updates_params import (
ExecutionStreamStdoutUpdatesParams as ExecutionStreamStdoutUpdatesParams,
)
-from .execution_stream_stderr_updates_response import (
- ExecutionStreamStderrUpdatesResponse as ExecutionStreamStderrUpdatesResponse,
-)
-from .execution_stream_stdout_updates_response import (
- ExecutionStreamStdoutUpdatesResponse as ExecutionStreamStdoutUpdatesResponse,
-)
from .lsp_get_code_actions_for_diagnostic_params import (
LspGetCodeActionsForDiagnosticParams as LspGetCodeActionsForDiagnosticParams,
)
diff --git a/src/runloop_api_client/types/devboxes/execution_stream_stderr_updates_response.py b/src/runloop_api_client/types/devboxes/execution_stream_stderr_updates_response.py
deleted file mode 100644
index 1f7ec2adc..000000000
--- a/src/runloop_api_client/types/devboxes/execution_stream_stderr_updates_response.py
+++ /dev/null
@@ -1,7 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing_extensions import TypeAlias
-
-__all__ = ["ExecutionStreamStderrUpdatesResponse"]
-
-ExecutionStreamStderrUpdatesResponse: TypeAlias = str
diff --git a/src/runloop_api_client/types/devboxes/execution_stream_stdout_updates_response.py b/src/runloop_api_client/types/devboxes/execution_stream_stdout_updates_response.py
deleted file mode 100644
index da6e2bd20..000000000
--- a/src/runloop_api_client/types/devboxes/execution_stream_stdout_updates_response.py
+++ /dev/null
@@ -1,7 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing_extensions import TypeAlias
-
-__all__ = ["ExecutionStreamStdoutUpdatesResponse"]
-
-ExecutionStreamStdoutUpdatesResponse: TypeAlias = str
diff --git a/src/runloop_api_client/types/devboxes/execution_update_chunk.py b/src/runloop_api_client/types/devboxes/execution_update_chunk.py
new file mode 100644
index 000000000..eda7add55
--- /dev/null
+++ b/src/runloop_api_client/types/devboxes/execution_update_chunk.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ..._models import BaseModel
+
+__all__ = ["ExecutionUpdateChunk"]
+
+
+class ExecutionUpdateChunk(BaseModel):
+ output: str
+ """The latest log stream chunk."""
+
+ offset: Optional[int] = None
+ """The byte offset of this chunk of log stream."""
From 2cb906951cf082a14adc7cf27a1d27816341f2a7 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 8 Sep 2025 22:35:09 +0000
Subject: [PATCH 5/7] feat(api): api update
---
.stats.yml | 6 +++---
api.md | 1 +
src/runloop_api_client/resources/devboxes/executions.py | 6 ++++++
.../types/devboxes/execution_kill_params.py | 4 ++++
4 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 3426ad757..1eaf9aeec 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 102
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-aa2f6b7bd5f38374ef743af2f5399fc37358408730dcbb1305e181bb91a2e903.yml
-openapi_spec_hash: a3f793428f6b5de9a59ba8c80ee8bbea
-config_hash: a74944d06979c9df5e08aee874ae5caa
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-a8e2289b32b616e0bd7e15995a6553d0335bd21e30617a013d07450f8f5a7a20.yml
+openapi_spec_hash: cd13a7af1ffe64c7ce8753f87786e01f
+config_hash: 457068371129b47b67f0f2d6b8267368
diff --git a/api.md b/api.md
index 807a478ed..3076ddf36 100644
--- a/api.md
+++ b/api.md
@@ -73,6 +73,7 @@ Types:
from runloop_api_client.types import (
DevboxAsyncExecutionDetailView,
DevboxExecutionDetailView,
+ DevboxKillExecutionRequest,
DevboxListView,
DevboxSnapshotListView,
DevboxSnapshotView,
diff --git a/src/runloop_api_client/resources/devboxes/executions.py b/src/runloop_api_client/resources/devboxes/executions.py
index c8a40a4e6..0cc776a7f 100755
--- a/src/runloop_api_client/resources/devboxes/executions.py
+++ b/src/runloop_api_client/resources/devboxes/executions.py
@@ -300,6 +300,9 @@ def kill(
killing the launched process. Optionally kill the entire process group.
Args:
+ kill_process_group: Whether to kill the entire process group (default: false). If true, kills all
+ processes in the same process group as the target process.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -673,6 +676,9 @@ async def kill(
killing the launched process. Optionally kill the entire process group.
Args:
+ kill_process_group: Whether to kill the entire process group (default: false). If true, kills all
+ processes in the same process group as the target process.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
diff --git a/src/runloop_api_client/types/devboxes/execution_kill_params.py b/src/runloop_api_client/types/devboxes/execution_kill_params.py
index 384da945c..0df5c8615 100644
--- a/src/runloop_api_client/types/devboxes/execution_kill_params.py
+++ b/src/runloop_api_client/types/devboxes/execution_kill_params.py
@@ -12,3 +12,7 @@ class ExecutionKillParams(TypedDict, total=False):
devbox_id: Required[str]
kill_process_group: Optional[bool]
+ """Whether to kill the entire process group (default: false).
+
+ If true, kills all processes in the same process group as the target process.
+ """
From 0ddad80f6bf03558e2b501ffd6931995a5baa782 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 8 Sep 2025 22:35:28 +0000
Subject: [PATCH 6/7] release: 0.59.0
---
.release-please-manifest.json | 2 +-
CHANGELOG.md | 16 ++++++++++++++++
pyproject.toml | 2 +-
src/runloop_api_client/_version.py | 2 +-
4 files changed, 19 insertions(+), 3 deletions(-)
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 2fbefb942..85c311828 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.58.0"
+ ".": "0.59.0"
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5095bbba2..7b00c7c10 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,21 @@
# Changelog
+## 0.59.0 (2025-09-08)
+
+Full Changelog: [v0.58.0...v0.59.0](https://github.com/runloopai/api-client-python/compare/v0.58.0...v0.59.0)
+
+### Features
+
+* **api:** api update ([d28f600](https://github.com/runloopai/api-client-python/commit/d28f60090e9c8c3c508812bfaf4bacbdd0b64330))
+* **api:** api update ([a7b44bc](https://github.com/runloopai/api-client-python/commit/a7b44bc091060518d507dbd806694de9ba09c1f8))
+* **api:** api update ([39c93bd](https://github.com/runloopai/api-client-python/commit/39c93bd7061e77c01fbb1cd913ede94ce90664c4))
+
+
+### Chores
+
+* **internal:** codegen related update ([4ed8b7a](https://github.com/runloopai/api-client-python/commit/4ed8b7a3ab98378214f74e79202051260f8488da))
+* **tests:** simplify `get_platform` test ([6b38a37](https://github.com/runloopai/api-client-python/commit/6b38a37e0e3630b360090fd4ab91b4ffc431c46c))
+
## 0.58.0 (2025-09-04)
Full Changelog: [v0.57.0...v0.58.0](https://github.com/runloopai/api-client-python/compare/v0.57.0...v0.58.0)
diff --git a/pyproject.toml b/pyproject.toml
index 3d68d633a..3e52c5ea7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "runloop_api_client"
-version = "0.58.0"
+version = "0.59.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 d2b85015b..533907a8a 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__ = "0.58.0" # x-release-please-version
+__version__ = "0.59.0" # x-release-please-version
From b8c6195ee311cdaa4734bc1ea2458b7b5271ce4d Mon Sep 17 00:00:00 2001
From: Albert Li
Date: Tue, 9 Sep 2025 13:01:03 -0700
Subject: [PATCH 7/7] Fix merge conflicts
---
.../resources/devboxes/executions.py | 243 ++++++++++++++----
.../api_resources/devboxes/test_executions.py | 180 ++++---------
2 files changed, 236 insertions(+), 187 deletions(-)
diff --git a/src/runloop_api_client/resources/devboxes/executions.py b/src/runloop_api_client/resources/devboxes/executions.py
index 0cc776a7f..27cd21c79 100755
--- a/src/runloop_api_client/resources/devboxes/executions.py
+++ b/src/runloop_api_client/resources/devboxes/executions.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing import Optional
+from typing import Optional, cast
import httpx
@@ -16,8 +16,8 @@
async_to_raw_response_wrapper,
async_to_streamed_response_wrapper,
)
-from ..._constants import DEFAULT_TIMEOUT
-from ..._streaming import Stream, AsyncStream
+from ..._constants import DEFAULT_TIMEOUT, RAW_RESPONSE_HEADER
+from ..._streaming import Stream, AsyncStream, ReconnectingStream, AsyncReconnectingStream
from ..._exceptions import APIStatusError, APITimeoutError
from ...lib.polling import PollingConfig, poll_until
from ..._base_client import make_request_options
@@ -361,20 +361,53 @@ def stream_stderr_updates(
raise ValueError(f"Expected a non-empty value for `devbox_id` but received {devbox_id!r}")
if not execution_id:
raise ValueError(f"Expected a non-empty value for `execution_id` but received {execution_id!r}")
- return self._get(
- f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates",
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform(
- {"offset": offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams
+ if extra_headers and extra_headers.get(RAW_RESPONSE_HEADER):
+ return self._get(
+ f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {"offset": offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams
+ ),
),
- ),
- cast_to=ExecutionUpdateChunk,
- stream=True,
- stream_cls=Stream[ExecutionUpdateChunk],
+ cast_to=DevboxAsyncExecutionDetailView,
+ stream=True,
+ stream_cls=Stream[ExecutionUpdateChunk],
+ )
+
+ def create_stream(last_offset: str | None) -> Stream[ExecutionUpdateChunk]:
+ new_offset = last_offset if last_offset is not None else (None if isinstance(offset, NotGiven) else offset)
+ return self._get(
+ f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {"offset": new_offset},
+ execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams,
+ ),
+ ),
+ cast_to=DevboxAsyncExecutionDetailView,
+ stream=True,
+ stream_cls=Stream[ExecutionUpdateChunk],
+ )
+
+ initial_stream = create_stream(None)
+
+ def get_offset(item: ExecutionUpdateChunk) -> str | None:
+ value = getattr(item, "offset", None)
+ if value is None:
+ return None
+ return str(value)
+
+ return cast(
+ Stream[ExecutionUpdateChunk],
+ ReconnectingStream(current_stream=initial_stream, stream_creator=create_stream, get_offset=get_offset),
)
def stream_stdout_updates(
@@ -408,20 +441,53 @@ def stream_stdout_updates(
raise ValueError(f"Expected a non-empty value for `devbox_id` but received {devbox_id!r}")
if not execution_id:
raise ValueError(f"Expected a non-empty value for `execution_id` but received {execution_id!r}")
- return self._get(
- f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stdout_updates",
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform(
- {"offset": offset}, execution_stream_stdout_updates_params.ExecutionStreamStdoutUpdatesParams
+ if extra_headers and extra_headers.get(RAW_RESPONSE_HEADER):
+ return self._get(
+ f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stdout_updates",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {"offset": offset}, execution_stream_stdout_updates_params.ExecutionStreamStdoutUpdatesParams
+ ),
),
- ),
- cast_to=ExecutionUpdateChunk,
- stream=True,
- stream_cls=Stream[ExecutionUpdateChunk],
+ cast_to=DevboxAsyncExecutionDetailView,
+ stream=True,
+ stream_cls=Stream[ExecutionUpdateChunk],
+ )
+
+ def create_stream(last_offset: str | None) -> Stream[ExecutionUpdateChunk]:
+ new_offset = last_offset if last_offset is not None else (None if isinstance(offset, NotGiven) else offset)
+ return self._get(
+ f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stdout_updates",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {"offset": new_offset},
+ execution_stream_stdout_updates_params.ExecutionStreamStdoutUpdatesParams,
+ ),
+ ),
+ cast_to=DevboxAsyncExecutionDetailView,
+ stream=True,
+ stream_cls=Stream[ExecutionUpdateChunk],
+ )
+
+ initial_stream = create_stream(None)
+
+ def get_offset(item: ExecutionUpdateChunk) -> str | None:
+ value = getattr(item, "offset", None)
+ if value is None:
+ return None
+ return str(value)
+
+ return cast(
+ Stream[ExecutionUpdateChunk],
+ ReconnectingStream(current_stream=initial_stream, stream_creator=create_stream, get_offset=get_offset),
)
@@ -739,20 +805,53 @@ async def stream_stderr_updates(
raise ValueError(f"Expected a non-empty value for `devbox_id` but received {devbox_id!r}")
if not execution_id:
raise ValueError(f"Expected a non-empty value for `execution_id` but received {execution_id!r}")
- return await self._get(
- f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates",
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=await async_maybe_transform(
- {"offset": offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams
+ if extra_headers and extra_headers.get(RAW_RESPONSE_HEADER):
+ return await self._get(
+ f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {"offset": offset}, execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams
+ ),
),
- ),
- cast_to=ExecutionUpdateChunk,
- stream=True,
- stream_cls=AsyncStream[ExecutionUpdateChunk],
+ cast_to=DevboxAsyncExecutionDetailView,
+ stream=True,
+ stream_cls=AsyncStream[ExecutionUpdateChunk],
+ )
+
+ async def create_stream(last_offset: str | None) -> AsyncStream[ExecutionUpdateChunk]:
+ new_offset = last_offset if last_offset is not None else (None if isinstance(offset, NotGiven) else offset)
+ return await self._get(
+ f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {"offset": new_offset},
+ execution_stream_stderr_updates_params.ExecutionStreamStderrUpdatesParams,
+ ),
+ ),
+ cast_to=DevboxAsyncExecutionDetailView,
+ stream=True,
+ stream_cls=AsyncStream[ExecutionUpdateChunk],
+ )
+
+ initial_stream = await create_stream(None)
+
+ def get_offset(item: ExecutionUpdateChunk) -> str | None:
+ value = getattr(item, "offset", None)
+ if value is None:
+ return None
+ return str(value)
+
+ return cast(
+ AsyncStream[ExecutionUpdateChunk],
+ AsyncReconnectingStream(current_stream=initial_stream, stream_creator=create_stream, get_offset=get_offset),
)
async def stream_stdout_updates(
@@ -786,20 +885,54 @@ async def stream_stdout_updates(
raise ValueError(f"Expected a non-empty value for `devbox_id` but received {devbox_id!r}")
if not execution_id:
raise ValueError(f"Expected a non-empty value for `execution_id` but received {execution_id!r}")
- return await self._get(
- f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stdout_updates",
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=await async_maybe_transform(
- {"offset": offset}, execution_stream_stdout_updates_params.ExecutionStreamStdoutUpdatesParams
+ # If caller requested a raw or streaming response wrapper, return the underlying stream as-is
+ if extra_headers and extra_headers.get(RAW_RESPONSE_HEADER):
+ return await self._get(
+ f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stdout_updates",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {"offset": offset}, execution_stream_stdout_updates_params.ExecutionStreamStdoutUpdatesParams
+ ),
),
- ),
- cast_to=ExecutionUpdateChunk,
- stream=True,
- stream_cls=AsyncStream[ExecutionUpdateChunk],
+ cast_to=DevboxAsyncExecutionDetailView,
+ stream=True,
+ stream_cls=AsyncStream[ExecutionUpdateChunk],
+ )
+
+ async def create_stream(last_offset: str | None) -> AsyncStream[ExecutionUpdateChunk]:
+ new_offset = last_offset if last_offset is not None else (None if isinstance(offset, NotGiven) else offset)
+ return await self._get(
+ f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stdout_updates",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {"offset": new_offset},
+ execution_stream_stdout_updates_params.ExecutionStreamStdoutUpdatesParams,
+ ),
+ ),
+ cast_to=DevboxAsyncExecutionDetailView,
+ stream=True,
+ stream_cls=AsyncStream[ExecutionUpdateChunk],
+ )
+
+ initial_stream = await create_stream(None)
+
+ def get_offset(item: ExecutionUpdateChunk) -> str | None:
+ value = getattr(item, "offset", None)
+ if value is None:
+ return None
+ return str(value)
+
+ return cast(
+ AsyncStream[ExecutionUpdateChunk],
+ AsyncReconnectingStream(current_stream=initial_stream, stream_creator=create_stream, get_offset=get_offset),
)
diff --git a/tests/api_resources/devboxes/test_executions.py b/tests/api_resources/devboxes/test_executions.py
index 0dd236647..9b932b160 100755
--- a/tests/api_resources/devboxes/test_executions.py
+++ b/tests/api_resources/devboxes/test_executions.py
@@ -239,16 +239,22 @@ def test_path_params_kill(self, client: Runloop) -> None:
)
@parametrize
- def test_method_stream_stderr_updates(self, client: Runloop) -> None:
- execution_stream = client.devboxes.executions.stream_stderr_updates(
+ def test_method_stream_stdout_updates(self, client: Runloop, respx_mock: MockRouter) -> None:
+ respx_mock.get("/v1/devboxes/devbox_id/executions/execution_id/stream_stdout_updates").mock(
+ return_value=httpx.Response(200)
+ )
+ execution_stream = client.devboxes.executions.stream_stdout_updates(
execution_id="execution_id",
devbox_id="devbox_id",
)
execution_stream.response.close()
@parametrize
- def test_method_stream_stderr_updates_with_all_params(self, client: Runloop) -> None:
- execution_stream = client.devboxes.executions.stream_stderr_updates(
+ def test_method_stream_stdout_updates_with_all_params(self, client: Runloop, respx_mock: MockRouter) -> None:
+ respx_mock.get("/v1/devboxes/devbox_id/executions/execution_id/stream_stdout_updates").mock(
+ return_value=httpx.Response(200)
+ )
+ execution_stream = client.devboxes.executions.stream_stdout_updates(
execution_id="execution_id",
devbox_id="devbox_id",
offset="offset",
@@ -256,8 +262,11 @@ def test_method_stream_stderr_updates_with_all_params(self, client: Runloop) ->
execution_stream.response.close()
@parametrize
- def test_raw_response_stream_stderr_updates(self, client: Runloop) -> None:
- response = client.devboxes.executions.with_raw_response.stream_stderr_updates(
+ def test_raw_response_stream_stdout_updates(self, client: Runloop, respx_mock: MockRouter) -> None:
+ respx_mock.get("/v1/devboxes/devbox_id/executions/execution_id/stream_stdout_updates").mock(
+ return_value=httpx.Response(200)
+ )
+ response = client.devboxes.executions.with_raw_response.stream_stdout_updates(
execution_id="execution_id",
devbox_id="devbox_id",
)
@@ -267,8 +276,11 @@ def test_raw_response_stream_stderr_updates(self, client: Runloop) -> None:
stream.close()
@parametrize
- def test_streaming_response_stream_stderr_updates(self, client: Runloop) -> None:
- with client.devboxes.executions.with_streaming_response.stream_stderr_updates(
+ def test_streaming_response_stream_stdout_updates(self, client: Runloop, respx_mock: MockRouter) -> None:
+ respx_mock.get("/v1/devboxes/devbox_id/executions/execution_id/stream_stdout_updates").mock(
+ return_value=httpx.Response(200)
+ )
+ with client.devboxes.executions.with_streaming_response.stream_stdout_updates(
execution_id="execution_id",
devbox_id="devbox_id",
) as response:
@@ -281,9 +293,9 @@ def test_streaming_response_stream_stderr_updates(self, client: Runloop) -> None
assert cast(Any, response.is_closed) is True
@parametrize
- def test_path_params_stream_stderr_updates(self, client: Runloop) -> None:
+ def test_path_params_stream_stdout_updates(self, client: Runloop) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `devbox_id` but received ''"):
- client.devboxes.executions.with_raw_response.stream_stderr_updates(
+ client.devboxes.executions.with_raw_response.stream_stdout_updates(
execution_id="execution_id",
devbox_id="",
)
@@ -350,62 +362,6 @@ def fake_get(_path: str, *, options: Any, **_kwargs: Any):
assert calls[1] is not None
assert seen_offsets == ["5", "9", "10"]
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `execution_id` but received ''"):
- client.devboxes.executions.with_raw_response.stream_stderr_updates(
- execution_id="",
- devbox_id="devbox_id",
- )
-
- @parametrize
- def test_method_stream_stdout_updates(self, client: Runloop) -> None:
- execution_stream = client.devboxes.executions.stream_stdout_updates(
- execution_id="execution_id",
- devbox_id="devbox_id",
- )
- execution_stream.response.close()
-
- @parametrize
- def test_method_stream_stdout_updates_with_all_params(self, client: Runloop) -> None:
- execution_stream = client.devboxes.executions.stream_stdout_updates(
- execution_id="execution_id",
- devbox_id="devbox_id",
- offset="offset",
- )
- execution_stream.response.close()
-
- @parametrize
- def test_raw_response_stream_stdout_updates(self, client: Runloop) -> None:
- response = client.devboxes.executions.with_raw_response.stream_stdout_updates(
- execution_id="execution_id",
- devbox_id="devbox_id",
- )
-
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- stream = response.parse()
- stream.close()
-
- @parametrize
- def test_streaming_response_stream_stdout_updates(self, client: Runloop) -> None:
- with client.devboxes.executions.with_streaming_response.stream_stdout_updates(
- execution_id="execution_id",
- devbox_id="devbox_id",
- ) 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_path_params_stream_stdout_updates(self, client: Runloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `devbox_id` but received ''"):
- client.devboxes.executions.with_raw_response.stream_stdout_updates(
- execution_id="execution_id",
- devbox_id="",
- )
-
with pytest.raises(ValueError, match=r"Expected a non-empty value for `execution_id` but received ''"):
client.devboxes.executions.with_raw_response.stream_stdout_updates(
execution_id="",
@@ -799,16 +755,24 @@ async def test_path_params_kill(self, async_client: AsyncRunloop) -> None:
)
@parametrize
- async def test_method_stream_stderr_updates(self, async_client: AsyncRunloop) -> None:
- execution_stream = await async_client.devboxes.executions.stream_stderr_updates(
+ async def test_method_stream_stdout_updates(self, async_client: AsyncRunloop, respx_mock: MockRouter) -> None:
+ respx_mock.get("/v1/devboxes/devbox_id/executions/execution_id/stream_stdout_updates").mock(
+ return_value=httpx.Response(200)
+ )
+ execution_stream = await async_client.devboxes.executions.stream_stdout_updates(
execution_id="execution_id",
devbox_id="devbox_id",
)
await execution_stream.response.aclose()
@parametrize
- async def test_method_stream_stderr_updates_with_all_params(self, async_client: AsyncRunloop) -> None:
- execution_stream = await async_client.devboxes.executions.stream_stderr_updates(
+ async def test_method_stream_stdout_updates_with_all_params(
+ self, async_client: AsyncRunloop, respx_mock: MockRouter
+ ) -> None:
+ respx_mock.get("/v1/devboxes/devbox_id/executions/execution_id/stream_stdout_updates").mock(
+ return_value=httpx.Response(200)
+ )
+ execution_stream = await async_client.devboxes.executions.stream_stdout_updates(
execution_id="execution_id",
devbox_id="devbox_id",
offset="offset",
@@ -816,8 +780,11 @@ async def test_method_stream_stderr_updates_with_all_params(self, async_client:
await execution_stream.response.aclose()
@parametrize
- async def test_raw_response_stream_stderr_updates(self, async_client: AsyncRunloop) -> None:
- response = await async_client.devboxes.executions.with_raw_response.stream_stderr_updates(
+ async def test_raw_response_stream_stdout_updates(self, async_client: AsyncRunloop, respx_mock: MockRouter) -> None:
+ respx_mock.get("/v1/devboxes/devbox_id/executions/execution_id/stream_stdout_updates").mock(
+ return_value=httpx.Response(200)
+ )
+ response = await async_client.devboxes.executions.with_raw_response.stream_stdout_updates(
execution_id="execution_id",
devbox_id="devbox_id",
)
@@ -827,8 +794,13 @@ async def test_raw_response_stream_stderr_updates(self, async_client: AsyncRunlo
await stream.close()
@parametrize
- async def test_streaming_response_stream_stderr_updates(self, async_client: AsyncRunloop) -> None:
- async with async_client.devboxes.executions.with_streaming_response.stream_stderr_updates(
+ async def test_streaming_response_stream_stdout_updates(
+ self, async_client: AsyncRunloop, respx_mock: MockRouter
+ ) -> None:
+ respx_mock.get("/v1/devboxes/devbox_id/executions/execution_id/stream_stdout_updates").mock(
+ return_value=httpx.Response(200)
+ )
+ async with async_client.devboxes.executions.with_streaming_response.stream_stdout_updates(
execution_id="execution_id",
devbox_id="devbox_id",
) as response:
@@ -841,9 +813,9 @@ async def test_streaming_response_stream_stderr_updates(self, async_client: Asyn
assert cast(Any, response.is_closed) is True
@parametrize
- async def test_path_params_stream_stderr_updates(self, async_client: AsyncRunloop) -> None:
+ async def test_path_params_stream_stdout_updates(self, async_client: AsyncRunloop) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `devbox_id` but received ''"):
- await async_client.devboxes.executions.with_raw_response.stream_stderr_updates(
+ await async_client.devboxes.executions.with_raw_response.stream_stdout_updates(
execution_id="execution_id",
devbox_id="",
)
@@ -911,62 +883,6 @@ async def fake_get(_path: str, *, options: Any, **_kwargs: Any):
assert calls[1] is not None
assert seen_offsets == ["5", "9", "10"]
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `execution_id` but received ''"):
- await async_client.devboxes.executions.with_raw_response.stream_stderr_updates(
- execution_id="",
- devbox_id="devbox_id",
- )
-
- @parametrize
- async def test_method_stream_stdout_updates(self, async_client: AsyncRunloop) -> None:
- execution_stream = await async_client.devboxes.executions.stream_stdout_updates(
- execution_id="execution_id",
- devbox_id="devbox_id",
- )
- await execution_stream.response.aclose()
-
- @parametrize
- async def test_method_stream_stdout_updates_with_all_params(self, async_client: AsyncRunloop) -> None:
- execution_stream = await async_client.devboxes.executions.stream_stdout_updates(
- execution_id="execution_id",
- devbox_id="devbox_id",
- offset="offset",
- )
- await execution_stream.response.aclose()
-
- @parametrize
- async def test_raw_response_stream_stdout_updates(self, async_client: AsyncRunloop) -> None:
- response = await async_client.devboxes.executions.with_raw_response.stream_stdout_updates(
- execution_id="execution_id",
- devbox_id="devbox_id",
- )
-
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- stream = await response.parse()
- await stream.close()
-
- @parametrize
- async def test_streaming_response_stream_stdout_updates(self, async_client: AsyncRunloop) -> None:
- async with async_client.devboxes.executions.with_streaming_response.stream_stdout_updates(
- execution_id="execution_id",
- devbox_id="devbox_id",
- ) 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_path_params_stream_stdout_updates(self, async_client: AsyncRunloop) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `devbox_id` but received ''"):
- await async_client.devboxes.executions.with_raw_response.stream_stdout_updates(
- execution_id="execution_id",
- devbox_id="",
- )
-
with pytest.raises(ValueError, match=r"Expected a non-empty value for `execution_id` but received ''"):
await async_client.devboxes.executions.with_raw_response.stream_stdout_updates(
execution_id="",