Skip to content

Commit d5303c2

Browse files
release: 1.21.0 (#799)
Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com>
1 parent 54a82b3 commit d5303c2

22 files changed

Lines changed: 1150 additions & 12 deletions

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "1.20.3"
2+
".": "1.21.0"
33
}

.stats.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
configured_endpoints: 116
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai/runloop-6cf4d9a6afac92d72787088b3aefa941f5240ee522d9e98e1160eea2e29f87f4.yml
3-
openapi_spec_hash: e07fc8349cf507b083830b4e2b0caca0
4-
config_hash: 436c8d4e665915db22b5d98fe58382c1
1+
configured_endpoints: 119
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai/runloop-6ec03cfc156036a0758aebc523b469f3007de431312c536c434efe795e1892e7.yml
3+
openapi_spec_hash: 092761a0209e0950cfd8f262a981adb1
4+
config_hash: 06faf3176c48bf2b258730ce50809f0f

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
# Changelog
22

3+
## 1.21.0 (2026-05-13)
4+
5+
Full Changelog: [v1.20.3...v1.21.0](https://github.com/runloopai/api-client-python/compare/v1.20.3...v1.21.0)
6+
7+
### Features
8+
9+
* **internal/types:** support eagerly validating pydantic iterators ([f089d05](https://github.com/runloopai/api-client-python/commit/f089d05c5e00c2ba980d0afdea79ed026242a174))
10+
* **metadata:** add object metadata viewing, discovery, and validation ([#9124](https://github.com/runloopai/api-client-python/issues/9124)) ([ba0b5dc](https://github.com/runloopai/api-client-python/commit/ba0b5dc873a47073c453b168fe4d966754cccc0e))
11+
12+
13+
### Bug Fixes
14+
15+
* **client:** add missing f-string prefix in file type error message ([930b1e2](https://github.com/runloopai/api-client-python/commit/930b1e2d169a291415007f2897fec5a410ecf993))
16+
* update openapi.stainless.yaml for PTY server methods ([#9218](https://github.com/runloopai/api-client-python/issues/9218)) ([ac1cb22](https://github.com/runloopai/api-client-python/commit/ac1cb2264b8e39da8aa65d8fdcd4595bce55def6))
17+
* update types for pty control, use int instead of str ([#9235](https://github.com/runloopai/api-client-python/issues/9235)) ([7ceb2bf](https://github.com/runloopai/api-client-python/commit/7ceb2bf09c3241a27892fbab2272d63ef76b1a8a))
18+
319
## 1.20.3 (2026-05-08)
420

521
Full Changelog: [v1.20.2...v1.20.3](https://github.com/runloopai/api-client-python/compare/v1.20.2...v1.20.3)

api.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ from runloop_api_client.types import (
198198
DevboxSnapshotListView,
199199
DevboxSnapshotView,
200200
DevboxView,
201+
PtyTunnelView,
201202
TunnelView,
202203
DevboxCreateSSHKeyResponse,
203204
DevboxReadFileContentsResponse,
@@ -211,6 +212,7 @@ Methods:
211212
- <code title="get /v1/devboxes/{id}">client.devboxes.<a href="./src/runloop_api_client/resources/devboxes/devboxes.py">retrieve</a>(id) -> <a href="./src/runloop_api_client/types/devbox_view.py">DevboxView</a></code>
212213
- <code title="post /v1/devboxes/{id}">client.devboxes.<a href="./src/runloop_api_client/resources/devboxes/devboxes.py">update</a>(id, \*\*<a href="src/runloop_api_client/types/devbox_update_params.py">params</a>) -> <a href="./src/runloop_api_client/types/devbox_view.py">DevboxView</a></code>
213214
- <code title="get /v1/devboxes">client.devboxes.<a href="./src/runloop_api_client/resources/devboxes/devboxes.py">list</a>(\*\*<a href="src/runloop_api_client/types/devbox_list_params.py">params</a>) -> <a href="./src/runloop_api_client/types/devbox_view.py">SyncDevboxesCursorIDPage[DevboxView]</a></code>
215+
- <code title="post /v1/devboxes/{id}/create_pty_tunnel">client.devboxes.<a href="./src/runloop_api_client/resources/devboxes/devboxes.py">create_pty_tunnel</a>(id) -> <a href="./src/runloop_api_client/types/pty_tunnel_view.py">PtyTunnelView</a></code>
214216
- <code title="post /v1/devboxes/{id}/create_ssh_key">client.devboxes.<a href="./src/runloop_api_client/resources/devboxes/devboxes.py">create_ssh_key</a>(id) -> <a href="./src/runloop_api_client/types/devbox_create_ssh_key_response.py">DevboxCreateSSHKeyResponse</a></code>
215217
- <code title="post /v1/devboxes/disk_snapshots/{id}/delete">client.devboxes.<a href="./src/runloop_api_client/resources/devboxes/devboxes.py">delete_disk_snapshot</a>(id) -> object</code>
216218
- <code title="post /v1/devboxes/{id}/download_file">client.devboxes.<a href="./src/runloop_api_client/resources/devboxes/devboxes.py">download_file</a>(id, \*\*<a href="src/runloop_api_client/types/devbox_download_file_params.py">params</a>) -> BinaryAPIResponse</code>
@@ -278,6 +280,19 @@ Methods:
278280
- <code title="get /v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates">client.devboxes.executions.<a href="./src/runloop_api_client/resources/devboxes/executions.py">stream_stderr_updates</a>(execution_id, \*, devbox_id, \*\*<a href="src/runloop_api_client/types/devboxes/execution_stream_stderr_updates_params.py">params</a>) -> <a href="./src/runloop_api_client/types/devboxes/execution_update_chunk.py">ExecutionUpdateChunk</a></code>
279281
- <code title="get /v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stdout_updates">client.devboxes.executions.<a href="./src/runloop_api_client/resources/devboxes/executions.py">stream_stdout_updates</a>(execution_id, \*, devbox_id, \*\*<a href="src/runloop_api_client/types/devboxes/execution_stream_stdout_updates_params.py">params</a>) -> <a href="./src/runloop_api_client/types/devboxes/execution_update_chunk.py">ExecutionUpdateChunk</a></code>
280282

283+
# Pty
284+
285+
Types:
286+
287+
```python
288+
from runloop_api_client.types import PtyConnectView, PtyControlParams, PtyControlResultView
289+
```
290+
291+
Methods:
292+
293+
- <code title="get /pty/{session_name}">client.pty.<a href="./src/runloop_api_client/resources/pty.py">connect</a>(session_name, \*\*<a href="src/runloop_api_client/types/pty_connect_params.py">params</a>) -> <a href="./src/runloop_api_client/types/pty_connect_view.py">PtyConnectView</a></code>
294+
- <code title="post /pty/{session_name}/control">client.pty.<a href="./src/runloop_api_client/resources/pty.py">control</a>(session_name, \*\*<a href="src/runloop_api_client/types/pty_control_params.py">params</a>) -> <a href="./src/runloop_api_client/types/pty_control_result_view.py">PtyControlResultView</a></code>
295+
281296
# Scenarios
282297

283298
Types:

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "runloop_api_client"
3-
version = "1.20.3"
3+
version = "1.21.0"
44
description = "The official Python library for the runloop API"
55
dynamic = ["readme"]
66
license = "MIT"

src/runloop_api_client/_client.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636

3737
if TYPE_CHECKING:
3838
from .resources import (
39+
pty,
3940
axons,
4041
agents,
4142
apikeys,
@@ -52,6 +53,7 @@
5253
restricted_keys,
5354
network_policies,
5455
)
56+
from .resources.pty import PtyResource, AsyncPtyResource
5557
from .resources.agents import AgentsResource, AsyncAgentsResource
5658
from .resources.apikeys import ApikeysResource, AsyncApikeysResource
5759
from .resources.objects import ObjectsResource, AsyncObjectsResource
@@ -184,6 +186,12 @@ def devboxes(self) -> DevboxesResource:
184186

185187
return DevboxesResource(self)
186188

189+
@cached_property
190+
def pty(self) -> PtyResource:
191+
from .resources.pty import PtyResource
192+
193+
return PtyResource(self)
194+
187195
@cached_property
188196
def scenarios(self) -> ScenariosResource:
189197
from .resources.scenarios import ScenariosResource
@@ -466,6 +474,12 @@ def devboxes(self) -> AsyncDevboxesResource:
466474

467475
return AsyncDevboxesResource(self)
468476

477+
@cached_property
478+
def pty(self) -> AsyncPtyResource:
479+
from .resources.pty import AsyncPtyResource
480+
481+
return AsyncPtyResource(self)
482+
469483
@cached_property
470484
def scenarios(self) -> AsyncScenariosResource:
471485
from .resources.scenarios import AsyncScenariosResource
@@ -683,6 +697,12 @@ def devboxes(self) -> devboxes.DevboxesResourceWithRawResponse:
683697

684698
return DevboxesResourceWithRawResponse(self._client.devboxes)
685699

700+
@cached_property
701+
def pty(self) -> pty.PtyResourceWithRawResponse:
702+
from .resources.pty import PtyResourceWithRawResponse
703+
704+
return PtyResourceWithRawResponse(self._client.pty)
705+
686706
@cached_property
687707
def scenarios(self) -> scenarios.ScenariosResourceWithRawResponse:
688708
from .resources.scenarios import ScenariosResourceWithRawResponse
@@ -780,6 +800,12 @@ def devboxes(self) -> devboxes.AsyncDevboxesResourceWithRawResponse:
780800

781801
return AsyncDevboxesResourceWithRawResponse(self._client.devboxes)
782802

803+
@cached_property
804+
def pty(self) -> pty.AsyncPtyResourceWithRawResponse:
805+
from .resources.pty import AsyncPtyResourceWithRawResponse
806+
807+
return AsyncPtyResourceWithRawResponse(self._client.pty)
808+
783809
@cached_property
784810
def scenarios(self) -> scenarios.AsyncScenariosResourceWithRawResponse:
785811
from .resources.scenarios import AsyncScenariosResourceWithRawResponse
@@ -877,6 +903,12 @@ def devboxes(self) -> devboxes.DevboxesResourceWithStreamingResponse:
877903

878904
return DevboxesResourceWithStreamingResponse(self._client.devboxes)
879905

906+
@cached_property
907+
def pty(self) -> pty.PtyResourceWithStreamingResponse:
908+
from .resources.pty import PtyResourceWithStreamingResponse
909+
910+
return PtyResourceWithStreamingResponse(self._client.pty)
911+
880912
@cached_property
881913
def scenarios(self) -> scenarios.ScenariosResourceWithStreamingResponse:
882914
from .resources.scenarios import ScenariosResourceWithStreamingResponse
@@ -974,6 +1006,12 @@ def devboxes(self) -> devboxes.AsyncDevboxesResourceWithStreamingResponse:
9741006

9751007
return AsyncDevboxesResourceWithStreamingResponse(self._client.devboxes)
9761008

1009+
@cached_property
1010+
def pty(self) -> pty.AsyncPtyResourceWithStreamingResponse:
1011+
from .resources.pty import AsyncPtyResourceWithStreamingResponse
1012+
1013+
return AsyncPtyResourceWithStreamingResponse(self._client.pty)
1014+
9771015
@cached_property
9781016
def scenarios(self) -> scenarios.AsyncScenariosResourceWithStreamingResponse:
9791017
from .resources.scenarios import AsyncScenariosResourceWithStreamingResponse

src/runloop_api_client/_files.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ async def async_to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles
9999
elif is_sequence_t(files):
100100
files = [(key, await _async_transform_file(file)) for key, file in files]
101101
else:
102-
raise TypeError("Unexpected file type input {type(files)}, expected mapping or sequence")
102+
raise TypeError(f"Unexpected file type input {type(files)}, expected mapping or sequence")
103103

104104
return files
105105

src/runloop_api_client/_models.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525
ClassVar,
2626
Protocol,
2727
Required,
28+
Annotated,
2829
ParamSpec,
30+
TypeAlias,
2931
TypedDict,
3032
TypeGuard,
3133
final,
@@ -79,7 +81,15 @@
7981
from ._constants import RAW_RESPONSE_HEADER
8082

8183
if TYPE_CHECKING:
84+
from pydantic import GetCoreSchemaHandler, ValidatorFunctionWrapHandler
85+
from pydantic_core import CoreSchema, core_schema
8286
from pydantic_core.core_schema import ModelField, ModelSchema, LiteralSchema, ModelFieldsSchema
87+
else:
88+
try:
89+
from pydantic_core import CoreSchema, core_schema
90+
except ImportError:
91+
CoreSchema = None
92+
core_schema = None
8393

8494
__all__ = ["BaseModel", "GenericModel"]
8595

@@ -396,6 +406,76 @@ def model_dump_json(
396406
)
397407

398408

409+
class _EagerIterable(list[_T], Generic[_T]):
410+
"""
411+
Accepts any Iterable[T] input (including generators), consumes it
412+
eagerly, and validates all items upfront.
413+
414+
Validation preserves the original container type where possible
415+
(e.g. a set[T] stays a set[T]). Serialization (model_dump / JSON)
416+
always emits a list — round-tripping through model_dump() will not
417+
restore the original container type.
418+
"""
419+
420+
@classmethod
421+
def __get_pydantic_core_schema__(
422+
cls,
423+
source_type: Any,
424+
handler: GetCoreSchemaHandler,
425+
) -> CoreSchema:
426+
(item_type,) = get_args(source_type) or (Any,)
427+
item_schema: CoreSchema = handler.generate_schema(item_type)
428+
list_of_items_schema: CoreSchema = core_schema.list_schema(item_schema)
429+
430+
return core_schema.no_info_wrap_validator_function(
431+
cls._validate,
432+
list_of_items_schema,
433+
serialization=core_schema.plain_serializer_function_ser_schema(
434+
cls._serialize,
435+
info_arg=False,
436+
),
437+
)
438+
439+
@staticmethod
440+
def _validate(v: Iterable[_T], handler: "ValidatorFunctionWrapHandler") -> Any:
441+
original_type: type[Any] = type(v)
442+
443+
# Normalize to list so list_schema can validate each item
444+
if isinstance(v, list):
445+
items: list[_T] = v
446+
else:
447+
try:
448+
items = list(v)
449+
except TypeError as e:
450+
raise TypeError("Value is not iterable") from e
451+
452+
# Validate items against the inner schema
453+
validated: list[_T] = handler(items)
454+
455+
# Reconstruct original container type
456+
if original_type is list:
457+
return validated
458+
# str(list) produces the list's repr, not a string built from items,
459+
# so skip reconstruction for str and its subclasses.
460+
if issubclass(original_type, str):
461+
return validated
462+
try:
463+
return original_type(validated)
464+
except (TypeError, ValueError):
465+
# If the type cannot be reconstructed, just return the validated list
466+
return validated
467+
468+
@staticmethod
469+
def _serialize(v: Iterable[_T]) -> list[_T]:
470+
"""Always serialize as a list so Pydantic's JSON encoder is happy."""
471+
if isinstance(v, list):
472+
return v
473+
return list(v)
474+
475+
476+
EagerIterable: TypeAlias = Annotated[Iterable[_T], _EagerIterable]
477+
478+
399479
def _construct_field(value: object, field: FieldInfo, key: str) -> object:
400480
if value is None:
401481
return field_get_default(field)

src/runloop_api_client/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
22

33
__title__ = "runloop_api_client"
4-
__version__ = "1.20.3" # x-release-please-version
4+
__version__ = "1.21.0" # x-release-please-version

src/runloop_api_client/resources/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
22

3+
from .pty import (
4+
PtyResource,
5+
AsyncPtyResource,
6+
PtyResourceWithRawResponse,
7+
AsyncPtyResourceWithRawResponse,
8+
PtyResourceWithStreamingResponse,
9+
AsyncPtyResourceWithStreamingResponse,
10+
)
311
from .axons import (
412
AxonsResource,
513
AsyncAxonsResource,
@@ -164,6 +172,12 @@
164172
"AsyncDevboxesResourceWithRawResponse",
165173
"DevboxesResourceWithStreamingResponse",
166174
"AsyncDevboxesResourceWithStreamingResponse",
175+
"PtyResource",
176+
"AsyncPtyResource",
177+
"PtyResourceWithRawResponse",
178+
"AsyncPtyResourceWithRawResponse",
179+
"PtyResourceWithStreamingResponse",
180+
"AsyncPtyResourceWithStreamingResponse",
167181
"ScenariosResource",
168182
"AsyncScenariosResource",
169183
"ScenariosResourceWithRawResponse",

0 commit comments

Comments
 (0)