From e5653245b65f7afd6210abe1b507ae64151da9b7 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 19 Mar 2026 21:48:31 +0000 Subject: [PATCH 01/13] chore: remove dead port configuration code, mark deprecated / ignored in the API (#8195) --- .stats.yml | 4 ++-- src/runloop_api_client/types/shared/launch_parameters.py | 7 ------- .../types/shared_params/launch_parameters.py | 9 +-------- tests/api_resources/test_benchmarks.py | 2 -- tests/api_resources/test_blueprints.py | 6 ------ tests/api_resources/test_devboxes.py | 2 -- tests/api_resources/test_scenarios.py | 6 ------ 7 files changed, 3 insertions(+), 33 deletions(-) diff --git a/.stats.yml b/.stats.yml index ab8cc33c0..57fda9e71 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 117 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-0568973e19e8af9fa953b2ded109ab2b69e76e90e2b74f33617dbf7092e26274.yml -openapi_spec_hash: 10ba804ce69510d7985e05c77d0ffcf6 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-b56067b06cdf0eb4150f81d8f53e46d9c79b8cbecba6f8c0ee82f798d8cd2447.yml +openapi_spec_hash: 4e0020f255cd31baa79227a5888e1eac config_hash: de99cfce88e2d1f02246dc6c2f43bc6c diff --git a/src/runloop_api_client/types/shared/launch_parameters.py b/src/runloop_api_client/types/shared/launch_parameters.py index 0264fa5c8..c96996c72 100644 --- a/src/runloop_api_client/types/shared/launch_parameters.py +++ b/src/runloop_api_client/types/shared/launch_parameters.py @@ -36,13 +36,6 @@ class LaunchParameters(BaseModel): architecture: Optional[Literal["x86_64", "arm64"]] = None """The target architecture for the Devbox. If unset, defaults to x86_64.""" - available_ports: Optional[List[int]] = None - """A list of ports to make available on the Devbox. - - Only ports made available will be surfaced to create tunnels via the - 'createTunnel' API. - """ - custom_cpu_cores: Optional[int] = None """Custom CPU cores. Must be 0.5, 1, or a multiple of 2. Max is 16.""" diff --git a/src/runloop_api_client/types/shared_params/launch_parameters.py b/src/runloop_api_client/types/shared_params/launch_parameters.py index 5c785b9f9..dc0a54f9b 100644 --- a/src/runloop_api_client/types/shared_params/launch_parameters.py +++ b/src/runloop_api_client/types/shared_params/launch_parameters.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Iterable, Optional +from typing import Optional from typing_extensions import Literal, Required, TypedDict from ..._types import SequenceNotStr @@ -38,13 +38,6 @@ class LaunchParameters(TypedDict, total=False): architecture: Optional[Literal["x86_64", "arm64"]] """The target architecture for the Devbox. If unset, defaults to x86_64.""" - available_ports: Optional[Iterable[int]] - """A list of ports to make available on the Devbox. - - Only ports made available will be surfaced to create tunnels via the - 'createTunnel' API. - """ - custom_cpu_cores: Optional[int] """Custom CPU cores. Must be 0.5, 1, or a multiple of 2. Max is 16.""" diff --git a/tests/api_resources/test_benchmarks.py b/tests/api_resources/test_benchmarks.py index 803c75c60..f25be7420 100644 --- a/tests/api_resources/test_benchmarks.py +++ b/tests/api_resources/test_benchmarks.py @@ -291,7 +291,6 @@ def test_method_start_run_with_all_params(self, client: Runloop) -> None: "on_idle": "shutdown", }, "architecture": "x86_64", - "available_ports": [0], "custom_cpu_cores": 0, "custom_disk_size": 0, "custom_gb_memory": 0, @@ -664,7 +663,6 @@ async def test_method_start_run_with_all_params(self, async_client: AsyncRunloop "on_idle": "shutdown", }, "architecture": "x86_64", - "available_ports": [0], "custom_cpu_cores": 0, "custom_disk_size": 0, "custom_gb_memory": 0, diff --git a/tests/api_resources/test_blueprints.py b/tests/api_resources/test_blueprints.py index d325ea2db..460fb9768 100644 --- a/tests/api_resources/test_blueprints.py +++ b/tests/api_resources/test_blueprints.py @@ -56,7 +56,6 @@ def test_method_create_with_all_params(self, client: Runloop) -> None: "on_idle": "shutdown", }, "architecture": "x86_64", - "available_ports": [0], "custom_cpu_cores": 0, "custom_disk_size": 0, "custom_gb_memory": 0, @@ -270,7 +269,6 @@ def test_method_create_from_inspection_with_all_params(self, client: Runloop) -> "on_idle": "shutdown", }, "architecture": "x86_64", - "available_ports": [0], "custom_cpu_cores": 0, "custom_disk_size": 0, "custom_gb_memory": 0, @@ -424,7 +422,6 @@ def test_method_preview_with_all_params(self, client: Runloop) -> None: "on_idle": "shutdown", }, "architecture": "x86_64", - "available_ports": [0], "custom_cpu_cores": 0, "custom_disk_size": 0, "custom_gb_memory": 0, @@ -522,7 +519,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncRunloop) - "on_idle": "shutdown", }, "architecture": "x86_64", - "available_ports": [0], "custom_cpu_cores": 0, "custom_disk_size": 0, "custom_gb_memory": 0, @@ -736,7 +732,6 @@ async def test_method_create_from_inspection_with_all_params(self, async_client: "on_idle": "shutdown", }, "architecture": "x86_64", - "available_ports": [0], "custom_cpu_cores": 0, "custom_disk_size": 0, "custom_gb_memory": 0, @@ -890,7 +885,6 @@ async def test_method_preview_with_all_params(self, async_client: AsyncRunloop) "on_idle": "shutdown", }, "architecture": "x86_64", - "available_ports": [0], "custom_cpu_cores": 0, "custom_disk_size": 0, "custom_gb_memory": 0, diff --git a/tests/api_resources/test_devboxes.py b/tests/api_resources/test_devboxes.py index b7eaeb2f0..209900587 100644 --- a/tests/api_resources/test_devboxes.py +++ b/tests/api_resources/test_devboxes.py @@ -78,7 +78,6 @@ def test_method_create_with_all_params(self, client: Runloop) -> None: "on_idle": "shutdown", }, "architecture": "x86_64", - "available_ports": [0], "custom_cpu_cores": 0, "custom_disk_size": 0, "custom_gb_memory": 0, @@ -1699,7 +1698,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncRunloop) - "on_idle": "shutdown", }, "architecture": "x86_64", - "available_ports": [0], "custom_cpu_cores": 0, "custom_disk_size": 0, "custom_gb_memory": 0, diff --git a/tests/api_resources/test_scenarios.py b/tests/api_resources/test_scenarios.py index 736a8395c..e212f6e81 100644 --- a/tests/api_resources/test_scenarios.py +++ b/tests/api_resources/test_scenarios.py @@ -72,7 +72,6 @@ def test_method_create_with_all_params(self, client: Runloop) -> None: "on_idle": "shutdown", }, "architecture": "x86_64", - "available_ports": [0], "custom_cpu_cores": 0, "custom_disk_size": 0, "custom_gb_memory": 0, @@ -207,7 +206,6 @@ def test_method_update_with_all_params(self, client: Runloop) -> None: "on_idle": "shutdown", }, "architecture": "x86_64", - "available_ports": [0], "custom_cpu_cores": 0, "custom_disk_size": 0, "custom_gb_memory": 0, @@ -413,7 +411,6 @@ def test_method_start_run_with_all_params(self, client: Runloop) -> None: "on_idle": "shutdown", }, "architecture": "x86_64", - "available_ports": [0], "custom_cpu_cores": 0, "custom_disk_size": 0, "custom_gb_memory": 0, @@ -521,7 +518,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncRunloop) - "on_idle": "shutdown", }, "architecture": "x86_64", - "available_ports": [0], "custom_cpu_cores": 0, "custom_disk_size": 0, "custom_gb_memory": 0, @@ -656,7 +652,6 @@ async def test_method_update_with_all_params(self, async_client: AsyncRunloop) - "on_idle": "shutdown", }, "architecture": "x86_64", - "available_ports": [0], "custom_cpu_cores": 0, "custom_disk_size": 0, "custom_gb_memory": 0, @@ -862,7 +857,6 @@ async def test_method_start_run_with_all_params(self, async_client: AsyncRunloop "on_idle": "shutdown", }, "architecture": "x86_64", - "available_ports": [0], "custom_cpu_cores": 0, "custom_disk_size": 0, "custom_gb_memory": 0, From 0993026b7dd67b1f7a9a81036c7fc93ab638112d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 02:13:19 +0000 Subject: [PATCH 02/13] fix: sanitize endpoint path params --- src/runloop_api_client/_utils/_path.py | 127 ++++++++++++++++++ src/runloop_api_client/resources/agents.py | 6 +- .../resources/benchmark_jobs.py | 6 +- .../resources/benchmark_runs.py | 18 +-- .../resources/benchmarks.py | 18 +-- .../resources/blueprints.py | 14 +- .../resources/devboxes/browsers.py | 6 +- .../resources/devboxes/computers.py | 18 +-- .../resources/devboxes/devboxes.py | 94 +++++++------ .../resources/devboxes/disk_snapshots.py | 14 +- .../resources/devboxes/logs.py | 6 +- .../resources/gateway_configs.py | 14 +- .../resources/mcp_configs.py | 14 +- .../resources/network_policies.py | 14 +- src/runloop_api_client/resources/objects.py | 18 +-- .../resources/repositories.py | 26 ++-- .../resources/scenarios/runs.py | 22 +-- .../resources/scenarios/scenarios.py | 14 +- .../resources/scenarios/scorers.py | 10 +- src/runloop_api_client/resources/secrets.py | 10 +- tests/test_utils/test_path.py | 89 ++++++++++++ 21 files changed, 391 insertions(+), 167 deletions(-) create mode 100644 src/runloop_api_client/_utils/_path.py create mode 100644 tests/test_utils/test_path.py diff --git a/src/runloop_api_client/_utils/_path.py b/src/runloop_api_client/_utils/_path.py new file mode 100644 index 000000000..4d6e1e4cb --- /dev/null +++ b/src/runloop_api_client/_utils/_path.py @@ -0,0 +1,127 @@ +from __future__ import annotations + +import re +from typing import ( + Any, + Mapping, + Callable, +) +from urllib.parse import quote + +# Matches '.' or '..' where each dot is either literal or percent-encoded (%2e / %2E). +_DOT_SEGMENT_RE = re.compile(r"^(?:\.|%2[eE]){1,2}$") + +_PLACEHOLDER_RE = re.compile(r"\{(\w+)\}") + + +def _quote_path_segment_part(value: str) -> str: + """Percent-encode `value` for use in a URI path segment. + + Considers characters not in `pchar` set from RFC 3986 §3.3 to be unsafe. + https://datatracker.ietf.org/doc/html/rfc3986#section-3.3 + """ + # quote() already treats unreserved characters (letters, digits, and -._~) + # as safe, so we only need to add sub-delims, ':', and '@'. + # Notably, unlike the default `safe` for quote(), / is unsafe and must be quoted. + return quote(value, safe="!$&'()*+,;=:@") + + +def _quote_query_part(value: str) -> str: + """Percent-encode `value` for use in a URI query string. + + Considers &, = and characters not in `query` set from RFC 3986 §3.4 to be unsafe. + https://datatracker.ietf.org/doc/html/rfc3986#section-3.4 + """ + return quote(value, safe="!$'()*+,;:@/?") + + +def _quote_fragment_part(value: str) -> str: + """Percent-encode `value` for use in a URI fragment. + + Considers characters not in `fragment` set from RFC 3986 §3.5 to be unsafe. + https://datatracker.ietf.org/doc/html/rfc3986#section-3.5 + """ + return quote(value, safe="!$&'()*+,;=:@/?") + + +def _interpolate( + template: str, + values: Mapping[str, Any], + quoter: Callable[[str], str], +) -> str: + """Replace {name} placeholders in `template`, quoting each value with `quoter`. + + Placeholder names are looked up in `values`. + + Raises: + KeyError: If a placeholder is not found in `values`. + """ + # re.split with a capturing group returns alternating + # [text, name, text, name, ..., text] elements. + parts = _PLACEHOLDER_RE.split(template) + + for i in range(1, len(parts), 2): + name = parts[i] + if name not in values: + raise KeyError(f"a value for placeholder {{{name}}} was not provided") + val = values[name] + if val is None: + parts[i] = "null" + elif isinstance(val, bool): + parts[i] = "true" if val else "false" + else: + parts[i] = quoter(str(values[name])) + + return "".join(parts) + + +def path_template(template: str, /, **kwargs: Any) -> str: + """Interpolate {name} placeholders in `template` from keyword arguments. + + Args: + template: The template string containing {name} placeholders. + **kwargs: Keyword arguments to interpolate into the template. + + Returns: + The template with placeholders interpolated and percent-encoded. + + Safe characters for percent-encoding are dependent on the URI component. + Placeholders in path and fragment portions are percent-encoded where the `segment` + and `fragment` sets from RFC 3986 respectively are considered safe. + Placeholders in the query portion are percent-encoded where the `query` set from + RFC 3986 §3.3 is considered safe except for = and & characters. + + Raises: + KeyError: If a placeholder is not found in `kwargs`. + ValueError: If resulting path contains /./ or /../ segments (including percent-encoded dot-segments). + """ + # Split the template into path, query, and fragment portions. + fragment_template: str | None = None + query_template: str | None = None + + rest = template + if "#" in rest: + rest, fragment_template = rest.split("#", 1) + if "?" in rest: + rest, query_template = rest.split("?", 1) + path_template = rest + + # Interpolate each portion with the appropriate quoting rules. + path_result = _interpolate(path_template, kwargs, _quote_path_segment_part) + + # Reject dot-segments (. and ..) in the final assembled path. The check + # runs after interpolation so that adjacent placeholders or a mix of static + # text and placeholders that together form a dot-segment are caught. + # Also reject percent-encoded dot-segments to protect against incorrectly + # implemented normalization in servers/proxies. + for segment in path_result.split("/"): + if _DOT_SEGMENT_RE.match(segment): + raise ValueError(f"Constructed path {path_result!r} contains dot-segment {segment!r} which is not allowed") + + result = path_result + if query_template is not None: + result += "?" + _interpolate(query_template, kwargs, _quote_query_part) + if fragment_template is not None: + result += "#" + _interpolate(fragment_template, kwargs, _quote_fragment_part) + + return result diff --git a/src/runloop_api_client/resources/agents.py b/src/runloop_api_client/resources/agents.py index 9ac9f8c02..8c4d108ea 100644 --- a/src/runloop_api_client/resources/agents.py +++ b/src/runloop_api_client/resources/agents.py @@ -8,7 +8,7 @@ from ..types import agent_list_params, agent_create_params from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from .._utils import maybe_transform, async_maybe_transform +from .._utils import path_template, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -127,7 +127,7 @@ def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/v1/agents/{id}", + path_template("/v1/agents/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -300,7 +300,7 @@ async def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/v1/agents/{id}", + path_template("/v1/agents/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/runloop_api_client/resources/benchmark_jobs.py b/src/runloop_api_client/resources/benchmark_jobs.py index f6172d118..2841df6f9 100644 --- a/src/runloop_api_client/resources/benchmark_jobs.py +++ b/src/runloop_api_client/resources/benchmark_jobs.py @@ -8,7 +8,7 @@ from ..types import benchmark_job_list_params, benchmark_job_create_params from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from .._utils import maybe_transform, async_maybe_transform +from .._utils import path_template, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -121,7 +121,7 @@ def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/v1/benchmark_jobs/{id}", + path_template("/v1/benchmark_jobs/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -276,7 +276,7 @@ async def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/v1/benchmark_jobs/{id}", + path_template("/v1/benchmark_jobs/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/runloop_api_client/resources/benchmark_runs.py b/src/runloop_api_client/resources/benchmark_runs.py index 964497cde..b889244a7 100644 --- a/src/runloop_api_client/resources/benchmark_runs.py +++ b/src/runloop_api_client/resources/benchmark_runs.py @@ -8,7 +8,7 @@ from ..types import benchmark_run_list_params, benchmark_run_list_scenario_runs_params from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from .._utils import maybe_transform +from .._utils import path_template, maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -71,7 +71,7 @@ def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/v1/benchmark_runs/{id}", + path_template("/v1/benchmark_runs/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -165,7 +165,7 @@ def cancel( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/benchmark_runs/{id}/cancel", + path_template("/v1/benchmark_runs/{id}/cancel", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -205,7 +205,7 @@ def complete( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/benchmark_runs/{id}/complete", + path_template("/v1/benchmark_runs/{id}/complete", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -251,7 +251,7 @@ def list_scenario_runs( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get_api_list( - f"/v1/benchmark_runs/{id}/scenario_runs", + path_template("/v1/benchmark_runs/{id}/scenario_runs", id=id), page=SyncBenchmarkRunsCursorIDPage[ScenarioRunView], options=make_request_options( extra_headers=extra_headers, @@ -317,7 +317,7 @@ async def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/v1/benchmark_runs/{id}", + path_template("/v1/benchmark_runs/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -411,7 +411,7 @@ async def cancel( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/benchmark_runs/{id}/cancel", + path_template("/v1/benchmark_runs/{id}/cancel", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -451,7 +451,7 @@ async def complete( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/benchmark_runs/{id}/complete", + path_template("/v1/benchmark_runs/{id}/complete", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -497,7 +497,7 @@ def list_scenario_runs( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get_api_list( - f"/v1/benchmark_runs/{id}/scenario_runs", + path_template("/v1/benchmark_runs/{id}/scenario_runs", id=id), page=AsyncBenchmarkRunsCursorIDPage[ScenarioRunView], options=make_request_options( extra_headers=extra_headers, diff --git a/src/runloop_api_client/resources/benchmarks.py b/src/runloop_api_client/resources/benchmarks.py index d23992bd2..fecf3e422 100644 --- a/src/runloop_api_client/resources/benchmarks.py +++ b/src/runloop_api_client/resources/benchmarks.py @@ -16,7 +16,7 @@ benchmark_update_scenarios_params, ) from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given -from .._utils import maybe_transform, async_maybe_transform +from .._utils import path_template, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -154,7 +154,7 @@ def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/v1/benchmarks/{id}", + path_template("/v1/benchmarks/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -218,7 +218,7 @@ def update( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/benchmarks/{id}", + path_template("/v1/benchmarks/{id}", id=id), body=maybe_transform( { "attribution": attribution, @@ -324,7 +324,7 @@ def definitions( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/v1/benchmarks/{id}/definitions", + path_template("/v1/benchmarks/{id}/definitions", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -481,7 +481,7 @@ def update_scenarios( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/benchmarks/{id}/scenarios", + path_template("/v1/benchmarks/{id}/scenarios", id=id), body=maybe_transform( { "scenarios_to_add": scenarios_to_add, @@ -619,7 +619,7 @@ async def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/v1/benchmarks/{id}", + path_template("/v1/benchmarks/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -683,7 +683,7 @@ async def update( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/benchmarks/{id}", + path_template("/v1/benchmarks/{id}", id=id), body=await async_maybe_transform( { "attribution": attribution, @@ -789,7 +789,7 @@ async def definitions( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/v1/benchmarks/{id}/definitions", + path_template("/v1/benchmarks/{id}/definitions", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -946,7 +946,7 @@ async def update_scenarios( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/benchmarks/{id}/scenarios", + path_template("/v1/benchmarks/{id}/scenarios", id=id), body=await async_maybe_transform( { "scenarios_to_add": scenarios_to_add, diff --git a/src/runloop_api_client/resources/blueprints.py b/src/runloop_api_client/resources/blueprints.py index 39eae98aa..6c6082054 100644 --- a/src/runloop_api_client/resources/blueprints.py +++ b/src/runloop_api_client/resources/blueprints.py @@ -14,7 +14,7 @@ blueprint_create_from_inspection_params, ) from .._types import NOT_GIVEN, Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given -from .._utils import is_given, maybe_transform, async_maybe_transform +from .._utils import is_given, path_template, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -268,7 +268,7 @@ def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/v1/blueprints/{id}", + path_template("/v1/blueprints/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -480,7 +480,7 @@ def delete( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/blueprints/{id}/delete", + path_template("/v1/blueprints/{id}/delete", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -652,7 +652,7 @@ def logs( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/v1/blueprints/{id}/logs", + path_template("/v1/blueprints/{id}/logs", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -938,7 +938,7 @@ async def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/v1/blueprints/{id}", + path_template("/v1/blueprints/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -1150,7 +1150,7 @@ async def delete( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/blueprints/{id}/delete", + path_template("/v1/blueprints/{id}/delete", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -1322,7 +1322,7 @@ async def logs( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/v1/blueprints/{id}/logs", + path_template("/v1/blueprints/{id}/logs", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/runloop_api_client/resources/devboxes/browsers.py b/src/runloop_api_client/resources/devboxes/browsers.py index 517857f56..d2977464b 100644 --- a/src/runloop_api_client/resources/devboxes/browsers.py +++ b/src/runloop_api_client/resources/devboxes/browsers.py @@ -7,7 +7,7 @@ import httpx from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from ..._utils import maybe_transform, async_maybe_transform +from ..._utils import path_template, maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -113,7 +113,7 @@ def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/v1/devboxes/browsers/{id}", + path_template("/v1/devboxes/browsers/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -211,7 +211,7 @@ async def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/v1/devboxes/browsers/{id}", + path_template("/v1/devboxes/browsers/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/runloop_api_client/resources/devboxes/computers.py b/src/runloop_api_client/resources/devboxes/computers.py index be051db2e..4eddfccc8 100644 --- a/src/runloop_api_client/resources/devboxes/computers.py +++ b/src/runloop_api_client/resources/devboxes/computers.py @@ -8,7 +8,7 @@ import httpx from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from ..._utils import maybe_transform, async_maybe_transform +from ..._utils import path_template, maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -133,7 +133,7 @@ def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/v1/devboxes/computers/{id}", + path_template("/v1/devboxes/computers/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -176,7 +176,7 @@ def keyboard_interaction( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/devboxes/computers/{id}/keyboard_interaction", + path_template("/v1/devboxes/computers/{id}/keyboard_interaction", id=id), body=maybe_transform( { "action": action, @@ -232,7 +232,7 @@ def mouse_interaction( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/devboxes/computers/{id}/mouse_interaction", + path_template("/v1/devboxes/computers/{id}/mouse_interaction", id=id), body=maybe_transform( { "action": action, @@ -283,7 +283,7 @@ def screen_interaction( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/devboxes/computers/{id}/screen_interaction", + path_template("/v1/devboxes/computers/{id}/screen_interaction", id=id), body=maybe_transform( {"action": action}, computer_screen_interaction_params.ComputerScreenInteractionParams ), @@ -399,7 +399,7 @@ async def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/v1/devboxes/computers/{id}", + path_template("/v1/devboxes/computers/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -442,7 +442,7 @@ async def keyboard_interaction( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/devboxes/computers/{id}/keyboard_interaction", + path_template("/v1/devboxes/computers/{id}/keyboard_interaction", id=id), body=await async_maybe_transform( { "action": action, @@ -498,7 +498,7 @@ async def mouse_interaction( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/devboxes/computers/{id}/mouse_interaction", + path_template("/v1/devboxes/computers/{id}/mouse_interaction", id=id), body=await async_maybe_transform( { "action": action, @@ -549,7 +549,7 @@ async def screen_interaction( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/devboxes/computers/{id}/screen_interaction", + path_template("/v1/devboxes/computers/{id}/screen_interaction", id=id), body=await async_maybe_transform( {"action": action}, computer_screen_interaction_params.ComputerScreenInteractionParams ), diff --git a/src/runloop_api_client/resources/devboxes/devboxes.py b/src/runloop_api_client/resources/devboxes/devboxes.py index 406af8626..6e9f52d92 100644 --- a/src/runloop_api_client/resources/devboxes/devboxes.py +++ b/src/runloop_api_client/resources/devboxes/devboxes.py @@ -39,7 +39,7 @@ devbox_write_file_contents_params, ) from ..._types import Body, Omit, Query, Headers, NotGiven, FileTypes, omit, not_given -from ..._utils import is_given, extract_files, maybe_transform, deepcopy_minimal, async_maybe_transform +from ..._utils import is_given, extract_files, path_template, maybe_transform, deepcopy_minimal, async_maybe_transform from .browsers import ( BrowsersResource, AsyncBrowsersResource, @@ -335,7 +335,7 @@ def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/v1/devboxes/{id}", + path_template("/v1/devboxes/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -378,7 +378,7 @@ def update( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/devboxes/{id}", + path_template("/v1/devboxes/{id}", id=id), body=maybe_transform( { "metadata": metadata, @@ -651,7 +651,7 @@ def create_ssh_key( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/devboxes/{id}/create_ssh_key", + path_template("/v1/devboxes/{id}/create_ssh_key", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -691,7 +691,7 @@ def delete_disk_snapshot( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/devboxes/disk_snapshots/{id}/delete", + path_template("/v1/devboxes/disk_snapshots/{id}/delete", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -739,7 +739,7 @@ def download_file( timeout = 600 extra_headers = {"Accept": "application/octet-stream", **(extra_headers or {})} return self._post( - f"/v1/devboxes/{id}/download_file", + path_template("/v1/devboxes/{id}/download_file", id=id), body=maybe_transform({"path": path}, devbox_download_file_params.DevboxDownloadFileParams), options=make_request_options( extra_headers=extra_headers, @@ -792,7 +792,7 @@ def enable_tunnel( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/devboxes/{id}/enable_tunnel", + path_template("/v1/devboxes/{id}/enable_tunnel", id=id), body=maybe_transform( { "auth_mode": auth_mode, @@ -869,7 +869,7 @@ def execute( if not is_given(timeout) and self._client.timeout == DEFAULT_TIMEOUT: timeout = 600 return self._post( - f"/v1/devboxes/{id}/execute", + path_template("/v1/devboxes/{id}/execute", id=id), body=maybe_transform( { "command": command, @@ -997,7 +997,7 @@ def execute_async( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/devboxes/{id}/execute_async", + path_template("/v1/devboxes/{id}/execute_async", id=id), body=maybe_transform( { "command": command, @@ -1069,7 +1069,7 @@ def execute_sync( if not is_given(timeout) and self._client.timeout == DEFAULT_TIMEOUT: timeout = 600 return self._post( - f"/v1/devboxes/{id}/execute_sync", + path_template("/v1/devboxes/{id}/execute_sync", id=id), body=maybe_transform( { "command": command, @@ -1118,7 +1118,7 @@ def keep_alive( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/devboxes/{id}/keep_alive", + path_template("/v1/devboxes/{id}/keep_alive", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -1233,7 +1233,7 @@ def read_file_contents( timeout = 600 extra_headers = {"Accept": "text/plain", **(extra_headers or {})} return self._post( - f"/v1/devboxes/{id}/read_file_contents", + path_template("/v1/devboxes/{id}/read_file_contents", id=id), body=maybe_transform( {"file_path": file_path}, devbox_read_file_contents_params.DevboxReadFileContentsParams ), @@ -1284,7 +1284,7 @@ def remove_tunnel( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/devboxes/{id}/remove_tunnel", + path_template("/v1/devboxes/{id}/remove_tunnel", id=id), body=maybe_transform({"port": port}, devbox_remove_tunnel_params.DevboxRemoveTunnelParams), options=make_request_options( extra_headers=extra_headers, @@ -1328,7 +1328,7 @@ def resume( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/devboxes/{id}/resume", + path_template("/v1/devboxes/{id}/resume", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -1369,7 +1369,7 @@ def retrieve_resource_usage( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/v1/devboxes/{id}/usage", + path_template("/v1/devboxes/{id}/usage", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -1413,7 +1413,7 @@ def shutdown( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/devboxes/{id}/shutdown", + path_template("/v1/devboxes/{id}/shutdown", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -1466,7 +1466,7 @@ def snapshot_disk( if not is_given(timeout) and self._client.timeout == DEFAULT_TIMEOUT: timeout = 600 return self._post( - f"/v1/devboxes/{id}/snapshot_disk", + path_template("/v1/devboxes/{id}/snapshot_disk", id=id), body=maybe_transform( { "commit_message": commit_message, @@ -1525,7 +1525,7 @@ def snapshot_disk_async( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/devboxes/{id}/snapshot_disk_async", + path_template("/v1/devboxes/{id}/snapshot_disk_async", id=id), body=maybe_transform( { "commit_message": commit_message, @@ -1575,7 +1575,7 @@ def suspend( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/devboxes/{id}/suspend", + path_template("/v1/devboxes/{id}/suspend", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -1636,7 +1636,7 @@ def upload_file( # multipart/form-data; boundary=---abc-- extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} return self._post( - f"/v1/devboxes/{id}/upload_file", + path_template("/v1/devboxes/{id}/upload_file", id=id), body=maybe_transform(body, devbox_upload_file_params.DevboxUploadFileParams), files=files, options=make_request_options( @@ -1694,7 +1694,11 @@ def wait_for_command( if not execution_id: raise ValueError(f"Expected a non-empty value for `execution_id` but received {execution_id!r}") return self._post( - f"/v1/devboxes/{devbox_id}/executions/{execution_id}/wait_for_status", + path_template( + "/v1/devboxes/{devbox_id}/executions/{execution_id}/wait_for_status", + devbox_id=devbox_id, + execution_id=execution_id, + ), body=maybe_transform( { "statuses": statuses, @@ -1753,7 +1757,7 @@ def write_file_contents( if not is_given(timeout) and self._client.timeout == DEFAULT_TIMEOUT: timeout = 600 return self._post( - f"/v1/devboxes/{id}/write_file_contents", + path_template("/v1/devboxes/{id}/write_file_contents", id=id), body=maybe_transform( { "contents": contents, @@ -1969,7 +1973,7 @@ async def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/v1/devboxes/{id}", + path_template("/v1/devboxes/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -2181,7 +2185,7 @@ async def update( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/devboxes/{id}", + path_template("/v1/devboxes/{id}", id=id), body=await async_maybe_transform( { "metadata": metadata, @@ -2282,7 +2286,7 @@ async def create_ssh_key( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/devboxes/{id}/create_ssh_key", + path_template("/v1/devboxes/{id}/create_ssh_key", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -2322,7 +2326,7 @@ async def delete_disk_snapshot( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/devboxes/disk_snapshots/{id}/delete", + path_template("/v1/devboxes/disk_snapshots/{id}/delete", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -2370,7 +2374,7 @@ async def download_file( timeout = 600 extra_headers = {"Accept": "application/octet-stream", **(extra_headers or {})} return await self._post( - f"/v1/devboxes/{id}/download_file", + path_template("/v1/devboxes/{id}/download_file", id=id), body=await async_maybe_transform({"path": path}, devbox_download_file_params.DevboxDownloadFileParams), options=make_request_options( extra_headers=extra_headers, @@ -2423,7 +2427,7 @@ async def enable_tunnel( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/devboxes/{id}/enable_tunnel", + path_template("/v1/devboxes/{id}/enable_tunnel", id=id), body=await async_maybe_transform( { "auth_mode": auth_mode, @@ -2500,7 +2504,7 @@ async def execute( if not is_given(timeout) and self._client.timeout == DEFAULT_TIMEOUT: timeout = 600 return await self._post( - f"/v1/devboxes/{id}/execute", + path_template("/v1/devboxes/{id}/execute", id=id), body=await async_maybe_transform( { "command": command, @@ -2627,7 +2631,7 @@ async def execute_async( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/devboxes/{id}/execute_async", + path_template("/v1/devboxes/{id}/execute_async", id=id), body=await async_maybe_transform( { "command": command, @@ -2699,7 +2703,7 @@ async def execute_sync( if not is_given(timeout) and self._client.timeout == DEFAULT_TIMEOUT: timeout = 600 return await self._post( - f"/v1/devboxes/{id}/execute_sync", + path_template("/v1/devboxes/{id}/execute_sync", id=id), body=await async_maybe_transform( { "command": command, @@ -2748,7 +2752,7 @@ async def keep_alive( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/devboxes/{id}/keep_alive", + path_template("/v1/devboxes/{id}/keep_alive", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -2863,7 +2867,7 @@ async def read_file_contents( timeout = 600 extra_headers = {"Accept": "text/plain", **(extra_headers or {})} return await self._post( - f"/v1/devboxes/{id}/read_file_contents", + path_template("/v1/devboxes/{id}/read_file_contents", id=id), body=await async_maybe_transform( {"file_path": file_path}, devbox_read_file_contents_params.DevboxReadFileContentsParams ), @@ -2914,7 +2918,7 @@ async def remove_tunnel( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/devboxes/{id}/remove_tunnel", + path_template("/v1/devboxes/{id}/remove_tunnel", id=id), body=await async_maybe_transform({"port": port}, devbox_remove_tunnel_params.DevboxRemoveTunnelParams), options=make_request_options( extra_headers=extra_headers, @@ -2958,7 +2962,7 @@ async def resume( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/devboxes/{id}/resume", + path_template("/v1/devboxes/{id}/resume", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -2999,7 +3003,7 @@ async def retrieve_resource_usage( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/v1/devboxes/{id}/usage", + path_template("/v1/devboxes/{id}/usage", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -3043,7 +3047,7 @@ async def shutdown( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/devboxes/{id}/shutdown", + path_template("/v1/devboxes/{id}/shutdown", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -3096,7 +3100,7 @@ async def snapshot_disk( if not is_given(timeout) and self._client.timeout == DEFAULT_TIMEOUT: timeout = 600 return await self._post( - f"/v1/devboxes/{id}/snapshot_disk", + path_template("/v1/devboxes/{id}/snapshot_disk", id=id), body=await async_maybe_transform( { "commit_message": commit_message, @@ -3155,7 +3159,7 @@ async def snapshot_disk_async( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/devboxes/{id}/snapshot_disk_async", + path_template("/v1/devboxes/{id}/snapshot_disk_async", id=id), body=await async_maybe_transform( { "commit_message": commit_message, @@ -3205,7 +3209,7 @@ async def suspend( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/devboxes/{id}/suspend", + path_template("/v1/devboxes/{id}/suspend", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -3266,7 +3270,7 @@ async def upload_file( # multipart/form-data; boundary=---abc-- extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} return await self._post( - f"/v1/devboxes/{id}/upload_file", + path_template("/v1/devboxes/{id}/upload_file", id=id), body=await async_maybe_transform(body, devbox_upload_file_params.DevboxUploadFileParams), files=files, options=make_request_options( @@ -3324,7 +3328,11 @@ async def wait_for_command( if not execution_id: raise ValueError(f"Expected a non-empty value for `execution_id` but received {execution_id!r}") return await self._post( - f"/v1/devboxes/{devbox_id}/executions/{execution_id}/wait_for_status", + path_template( + "/v1/devboxes/{devbox_id}/executions/{execution_id}/wait_for_status", + devbox_id=devbox_id, + execution_id=execution_id, + ), body=await async_maybe_transform( { "statuses": statuses, @@ -3385,7 +3393,7 @@ async def write_file_contents( if not is_given(timeout) and self._client.timeout == DEFAULT_TIMEOUT: timeout = 600 return await self._post( - f"/v1/devboxes/{id}/write_file_contents", + path_template("/v1/devboxes/{id}/write_file_contents", id=id), body=await async_maybe_transform( { "contents": contents, diff --git a/src/runloop_api_client/resources/devboxes/disk_snapshots.py b/src/runloop_api_client/resources/devboxes/disk_snapshots.py index b896adbb6..d6d43c26f 100644 --- a/src/runloop_api_client/resources/devboxes/disk_snapshots.py +++ b/src/runloop_api_client/resources/devboxes/disk_snapshots.py @@ -7,7 +7,7 @@ import httpx from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from ..._utils import maybe_transform, async_maybe_transform +from ..._utils import path_template, maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -88,7 +88,7 @@ def update( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/devboxes/disk_snapshots/{id}", + path_template("/v1/devboxes/disk_snapshots/{id}", id=id), body=maybe_transform( { "commit_message": commit_message, @@ -201,7 +201,7 @@ def delete( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/devboxes/disk_snapshots/{id}/delete", + path_template("/v1/devboxes/disk_snapshots/{id}/delete", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -239,7 +239,7 @@ def query_status( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/v1/devboxes/disk_snapshots/{id}/status", + path_template("/v1/devboxes/disk_snapshots/{id}/status", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -339,7 +339,7 @@ async def update( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/devboxes/disk_snapshots/{id}", + path_template("/v1/devboxes/disk_snapshots/{id}", id=id), body=await async_maybe_transform( { "commit_message": commit_message, @@ -452,7 +452,7 @@ async def delete( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/devboxes/disk_snapshots/{id}/delete", + path_template("/v1/devboxes/disk_snapshots/{id}/delete", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -490,7 +490,7 @@ async def query_status( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/v1/devboxes/disk_snapshots/{id}/status", + path_template("/v1/devboxes/disk_snapshots/{id}/status", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/runloop_api_client/resources/devboxes/logs.py b/src/runloop_api_client/resources/devboxes/logs.py index 19d2c06e1..1e7383914 100644 --- a/src/runloop_api_client/resources/devboxes/logs.py +++ b/src/runloop_api_client/resources/devboxes/logs.py @@ -5,7 +5,7 @@ import httpx from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from ..._utils import maybe_transform, async_maybe_transform +from ..._utils import path_template, maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -73,7 +73,7 @@ def list( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/v1/devboxes/{id}/logs", + path_template("/v1/devboxes/{id}/logs", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -143,7 +143,7 @@ async def list( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/v1/devboxes/{id}/logs", + path_template("/v1/devboxes/{id}/logs", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, diff --git a/src/runloop_api_client/resources/gateway_configs.py b/src/runloop_api_client/resources/gateway_configs.py index 86ec22ce6..a521b871e 100644 --- a/src/runloop_api_client/resources/gateway_configs.py +++ b/src/runloop_api_client/resources/gateway_configs.py @@ -8,7 +8,7 @@ from ..types import gateway_config_list_params, gateway_config_create_params, gateway_config_update_params from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from .._utils import maybe_transform, async_maybe_transform +from .._utils import path_template, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -132,7 +132,7 @@ def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/v1/gateway-configs/{id}", + path_template("/v1/gateway-configs/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -181,7 +181,7 @@ def update( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/gateway-configs/{id}", + path_template("/v1/gateway-configs/{id}", id=id), body=maybe_transform( { "auth_mechanism": auth_mechanism, @@ -287,7 +287,7 @@ def delete( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/gateway-configs/{id}/delete", + path_template("/v1/gateway-configs/{id}/delete", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -407,7 +407,7 @@ async def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/v1/gateway-configs/{id}", + path_template("/v1/gateway-configs/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -456,7 +456,7 @@ async def update( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/gateway-configs/{id}", + path_template("/v1/gateway-configs/{id}", id=id), body=await async_maybe_transform( { "auth_mechanism": auth_mechanism, @@ -562,7 +562,7 @@ async def delete( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/gateway-configs/{id}/delete", + path_template("/v1/gateway-configs/{id}/delete", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, diff --git a/src/runloop_api_client/resources/mcp_configs.py b/src/runloop_api_client/resources/mcp_configs.py index 24b05cc2d..9c1cabc0c 100644 --- a/src/runloop_api_client/resources/mcp_configs.py +++ b/src/runloop_api_client/resources/mcp_configs.py @@ -8,7 +8,7 @@ from ..types import mcp_config_list_params, mcp_config_create_params, mcp_config_update_params from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given -from .._utils import maybe_transform, async_maybe_transform +from .._utils import path_template, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -134,7 +134,7 @@ def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/v1/mcp-configs/{id}", + path_template("/v1/mcp-configs/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -184,7 +184,7 @@ def update( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/mcp-configs/{id}", + path_template("/v1/mcp-configs/{id}", id=id), body=maybe_transform( { "allowed_tools": allowed_tools, @@ -289,7 +289,7 @@ def delete( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/mcp-configs/{id}/delete", + path_template("/v1/mcp-configs/{id}/delete", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -411,7 +411,7 @@ async def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/v1/mcp-configs/{id}", + path_template("/v1/mcp-configs/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -461,7 +461,7 @@ async def update( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/mcp-configs/{id}", + path_template("/v1/mcp-configs/{id}", id=id), body=await async_maybe_transform( { "allowed_tools": allowed_tools, @@ -566,7 +566,7 @@ async def delete( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/mcp-configs/{id}/delete", + path_template("/v1/mcp-configs/{id}/delete", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, diff --git a/src/runloop_api_client/resources/network_policies.py b/src/runloop_api_client/resources/network_policies.py index f50d4fe6d..d472b54a8 100644 --- a/src/runloop_api_client/resources/network_policies.py +++ b/src/runloop_api_client/resources/network_policies.py @@ -8,7 +8,7 @@ from ..types import network_policy_list_params, network_policy_create_params, network_policy_update_params from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given -from .._utils import maybe_transform, async_maybe_transform +from .._utils import path_template, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -149,7 +149,7 @@ def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/v1/network-policies/{id}", + path_template("/v1/network-policies/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -208,7 +208,7 @@ def update( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/network-policies/{id}", + path_template("/v1/network-policies/{id}", id=id), body=maybe_transform( { "allow_agent_gateway": allow_agent_gateway, @@ -316,7 +316,7 @@ def delete( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/network-policies/{id}/delete", + path_template("/v1/network-policies/{id}/delete", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -453,7 +453,7 @@ async def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/v1/network-policies/{id}", + path_template("/v1/network-policies/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -512,7 +512,7 @@ async def update( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/network-policies/{id}", + path_template("/v1/network-policies/{id}", id=id), body=await async_maybe_transform( { "allow_agent_gateway": allow_agent_gateway, @@ -620,7 +620,7 @@ async def delete( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/network-policies/{id}/delete", + path_template("/v1/network-policies/{id}/delete", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, diff --git a/src/runloop_api_client/resources/objects.py b/src/runloop_api_client/resources/objects.py index 409d5f6f3..78ab27aa3 100644 --- a/src/runloop_api_client/resources/objects.py +++ b/src/runloop_api_client/resources/objects.py @@ -9,7 +9,7 @@ from ..types import object_list_params, object_create_params, object_download_params, object_list_public_params from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from .._utils import maybe_transform, async_maybe_transform +from .._utils import path_template, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -133,7 +133,7 @@ def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/v1/objects/{id}", + path_template("/v1/objects/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -234,7 +234,7 @@ def delete( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/objects/{id}/delete", + path_template("/v1/objects/{id}/delete", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -275,7 +275,7 @@ def complete( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/objects/{id}/complete", + path_template("/v1/objects/{id}/complete", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -317,7 +317,7 @@ def download( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/v1/objects/{id}/download", + path_template("/v1/objects/{id}/download", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -501,7 +501,7 @@ async def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/v1/objects/{id}", + path_template("/v1/objects/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -602,7 +602,7 @@ async def delete( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/objects/{id}/delete", + path_template("/v1/objects/{id}/delete", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -643,7 +643,7 @@ async def complete( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/objects/{id}/complete", + path_template("/v1/objects/{id}/complete", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -685,7 +685,7 @@ async def download( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/v1/objects/{id}/download", + path_template("/v1/objects/{id}/download", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, diff --git a/src/runloop_api_client/resources/repositories.py b/src/runloop_api_client/resources/repositories.py index a22075540..58421b8d2 100644 --- a/src/runloop_api_client/resources/repositories.py +++ b/src/runloop_api_client/resources/repositories.py @@ -14,7 +14,7 @@ repository_refresh_params, ) from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from .._utils import maybe_transform, async_maybe_transform +from .._utils import path_template, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -138,7 +138,7 @@ def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/v1/repositories/{id}", + path_template("/v1/repositories/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -230,7 +230,7 @@ def delete( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/repositories/{id}/delete", + path_template("/v1/repositories/{id}/delete", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -274,7 +274,7 @@ def inspect( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/repositories/{id}/inspect", + path_template("/v1/repositories/{id}/inspect", id=id), body=maybe_transform( {"github_auth_token": github_auth_token}, repository_inspect_params.RepositoryInspectParams ), @@ -315,7 +315,7 @@ def list_inspections( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/v1/repositories/{id}/inspections", + path_template("/v1/repositories/{id}/inspections", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -359,7 +359,7 @@ def refresh( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/repositories/{id}/refresh", + path_template("/v1/repositories/{id}/refresh", id=id), body=maybe_transform( { "blueprint_id": blueprint_id, @@ -403,7 +403,7 @@ def retrieve_inspection( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/v1/repositories/inspections/{id}", + path_template("/v1/repositories/inspections/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -517,7 +517,7 @@ async def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/v1/repositories/{id}", + path_template("/v1/repositories/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -609,7 +609,7 @@ async def delete( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/repositories/{id}/delete", + path_template("/v1/repositories/{id}/delete", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -653,7 +653,7 @@ async def inspect( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/repositories/{id}/inspect", + path_template("/v1/repositories/{id}/inspect", id=id), body=await async_maybe_transform( {"github_auth_token": github_auth_token}, repository_inspect_params.RepositoryInspectParams ), @@ -694,7 +694,7 @@ async def list_inspections( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/v1/repositories/{id}/inspections", + path_template("/v1/repositories/{id}/inspections", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -738,7 +738,7 @@ async def refresh( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/repositories/{id}/refresh", + path_template("/v1/repositories/{id}/refresh", id=id), body=await async_maybe_transform( { "blueprint_id": blueprint_id, @@ -782,7 +782,7 @@ async def retrieve_inspection( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/v1/repositories/inspections/{id}", + path_template("/v1/repositories/inspections/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/runloop_api_client/resources/scenarios/runs.py b/src/runloop_api_client/resources/scenarios/runs.py index 3ea9a960f..f655fe565 100644 --- a/src/runloop_api_client/resources/scenarios/runs.py +++ b/src/runloop_api_client/resources/scenarios/runs.py @@ -5,7 +5,7 @@ import httpx from ..._types import NOT_GIVEN, Body, Omit, Query, Headers, NotGiven, omit, not_given -from ..._utils import maybe_transform +from ..._utils import path_template, maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -79,7 +79,7 @@ def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/v1/scenarios/runs/{id}", + path_template("/v1/scenarios/runs/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -180,7 +180,7 @@ def cancel( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/scenarios/runs/{id}/cancel", + path_template("/v1/scenarios/runs/{id}/cancel", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -222,7 +222,7 @@ def complete( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/scenarios/runs/{id}/complete", + path_template("/v1/scenarios/runs/{id}/complete", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -264,7 +264,7 @@ def download_logs( raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") extra_headers = {"Accept": "application/zip", **(extra_headers or {})} return self._post( - f"/v1/scenarios/runs/{id}/download_logs", + path_template("/v1/scenarios/runs/{id}/download_logs", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -304,7 +304,7 @@ def score( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/scenarios/runs/{id}/score", + path_template("/v1/scenarios/runs/{id}/score", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -499,7 +499,7 @@ async def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/v1/scenarios/runs/{id}", + path_template("/v1/scenarios/runs/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -600,7 +600,7 @@ async def cancel( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/scenarios/runs/{id}/cancel", + path_template("/v1/scenarios/runs/{id}/cancel", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -642,7 +642,7 @@ async def complete( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/scenarios/runs/{id}/complete", + path_template("/v1/scenarios/runs/{id}/complete", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -684,7 +684,7 @@ async def download_logs( raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") extra_headers = {"Accept": "application/zip", **(extra_headers or {})} return await self._post( - f"/v1/scenarios/runs/{id}/download_logs", + path_template("/v1/scenarios/runs/{id}/download_logs", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -724,7 +724,7 @@ async def score( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/scenarios/runs/{id}/score", + path_template("/v1/scenarios/runs/{id}/score", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, diff --git a/src/runloop_api_client/resources/scenarios/scenarios.py b/src/runloop_api_client/resources/scenarios/scenarios.py index 5c31e4282..65f282ed7 100644 --- a/src/runloop_api_client/resources/scenarios/scenarios.py +++ b/src/runloop_api_client/resources/scenarios/scenarios.py @@ -31,7 +31,7 @@ AsyncScorersResourceWithStreamingResponse, ) from ..._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given -from ..._utils import maybe_transform, async_maybe_transform +from ..._utils import path_template, maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -197,7 +197,7 @@ def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/v1/scenarios/{id}", + path_template("/v1/scenarios/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -268,7 +268,7 @@ def update( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/scenarios/{id}", + path_template("/v1/scenarios/{id}", id=id), body=maybe_transform( { "environment_parameters": environment_parameters, @@ -384,7 +384,7 @@ def archive( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/scenarios/{id}/archive", + path_template("/v1/scenarios/{id}/archive", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -709,7 +709,7 @@ async def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/v1/scenarios/{id}", + path_template("/v1/scenarios/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -780,7 +780,7 @@ async def update( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/scenarios/{id}", + path_template("/v1/scenarios/{id}", id=id), body=await async_maybe_transform( { "environment_parameters": environment_parameters, @@ -896,7 +896,7 @@ async def archive( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/scenarios/{id}/archive", + path_template("/v1/scenarios/{id}/archive", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, diff --git a/src/runloop_api_client/resources/scenarios/scorers.py b/src/runloop_api_client/resources/scenarios/scorers.py index cdb011dc7..118439745 100644 --- a/src/runloop_api_client/resources/scenarios/scorers.py +++ b/src/runloop_api_client/resources/scenarios/scorers.py @@ -5,7 +5,7 @@ import httpx from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from ..._utils import maybe_transform, async_maybe_transform +from ..._utils import path_template, maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -122,7 +122,7 @@ def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/v1/scenarios/scorers/{id}", + path_template("/v1/scenarios/scorers/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -165,7 +165,7 @@ def update( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/scenarios/scorers/{id}", + path_template("/v1/scenarios/scorers/{id}", id=id), body=maybe_transform( { "bash_script": bash_script, @@ -328,7 +328,7 @@ async def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/v1/scenarios/scorers/{id}", + path_template("/v1/scenarios/scorers/{id}", id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -371,7 +371,7 @@ async def update( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/scenarios/scorers/{id}", + path_template("/v1/scenarios/scorers/{id}", id=id), body=await async_maybe_transform( { "bash_script": bash_script, diff --git a/src/runloop_api_client/resources/secrets.py b/src/runloop_api_client/resources/secrets.py index fa7d45471..38a9d8fc0 100644 --- a/src/runloop_api_client/resources/secrets.py +++ b/src/runloop_api_client/resources/secrets.py @@ -6,7 +6,7 @@ from ..types import secret_list_params, secret_create_params, secret_update_params from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from .._utils import maybe_transform, async_maybe_transform +from .._utils import path_template, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -164,7 +164,7 @@ def update( if not name: raise ValueError(f"Expected a non-empty value for `name` but received {name!r}") return self._post( - f"/v1/secrets/{name}", + path_template("/v1/secrets/{name}", name=name), body=maybe_transform({"value": value}, secret_update_params.SecretUpdateParams), options=make_request_options( extra_headers=extra_headers, @@ -246,7 +246,7 @@ def delete( if not name: raise ValueError(f"Expected a non-empty value for `name` but received {name!r}") return self._post( - f"/v1/secrets/{name}/delete", + path_template("/v1/secrets/{name}/delete", name=name), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -400,7 +400,7 @@ async def update( if not name: raise ValueError(f"Expected a non-empty value for `name` but received {name!r}") return await self._post( - f"/v1/secrets/{name}", + path_template("/v1/secrets/{name}", name=name), body=await async_maybe_transform({"value": value}, secret_update_params.SecretUpdateParams), options=make_request_options( extra_headers=extra_headers, @@ -482,7 +482,7 @@ async def delete( if not name: raise ValueError(f"Expected a non-empty value for `name` but received {name!r}") return await self._post( - f"/v1/secrets/{name}/delete", + path_template("/v1/secrets/{name}/delete", name=name), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, diff --git a/tests/test_utils/test_path.py b/tests/test_utils/test_path.py new file mode 100644 index 000000000..f69d655a1 --- /dev/null +++ b/tests/test_utils/test_path.py @@ -0,0 +1,89 @@ +from __future__ import annotations + +from typing import Any + +import pytest + +from runloop_api_client._utils._path import path_template + + +@pytest.mark.parametrize( + "template, kwargs, expected", + [ + ("/v1/{id}", dict(id="abc"), "/v1/abc"), + ("/v1/{a}/{b}", dict(a="x", b="y"), "/v1/x/y"), + ("/v1/{a}{b}/path/{c}?val={d}#{e}", dict(a="x", b="y", c="z", d="u", e="v"), "/v1/xy/path/z?val=u#v"), + ("/{w}/{w}", dict(w="echo"), "/echo/echo"), + ("/v1/static", {}, "/v1/static"), + ("", {}, ""), + ("/v1/?q={n}&count=10", dict(n=42), "/v1/?q=42&count=10"), + ("/v1/{v}", dict(v=None), "/v1/null"), + ("/v1/{v}", dict(v=True), "/v1/true"), + ("/v1/{v}", dict(v=False), "/v1/false"), + ("/v1/{v}", dict(v=".hidden"), "/v1/.hidden"), # dot prefix ok + ("/v1/{v}", dict(v="file.txt"), "/v1/file.txt"), # dot in middle ok + ("/v1/{v}", dict(v="..."), "/v1/..."), # triple dot ok + ("/v1/{a}{b}", dict(a=".", b="txt"), "/v1/.txt"), # dot var combining with adjacent to be ok + ("/items?q={v}#{f}", dict(v=".", f=".."), "/items?q=.#.."), # dots in query/fragment are fine + ( + "/v1/{a}?query={b}", + dict(a="../../other/endpoint", b="a&bad=true"), + "/v1/..%2F..%2Fother%2Fendpoint?query=a%26bad%3Dtrue", + ), + ("/v1/{val}", dict(val="a/b/c"), "/v1/a%2Fb%2Fc"), + ("/v1/{val}", dict(val="a/b/c?query=value"), "/v1/a%2Fb%2Fc%3Fquery=value"), + ("/v1/{val}", dict(val="a/b/c?query=value&bad=true"), "/v1/a%2Fb%2Fc%3Fquery=value&bad=true"), + ("/v1/{val}", dict(val="%20"), "/v1/%2520"), # escapes escape sequences in input + # Query: slash and ? are safe, # is not + ("/items?q={v}", dict(v="a/b"), "/items?q=a/b"), + ("/items?q={v}", dict(v="a?b"), "/items?q=a?b"), + ("/items?q={v}", dict(v="a#b"), "/items?q=a%23b"), + ("/items?q={v}", dict(v="a b"), "/items?q=a%20b"), + # Fragment: slash and ? are safe + ("/docs#{v}", dict(v="a/b"), "/docs#a/b"), + ("/docs#{v}", dict(v="a?b"), "/docs#a?b"), + # Path: slash, ? and # are all encoded + ("/v1/{v}", dict(v="a/b"), "/v1/a%2Fb"), + ("/v1/{v}", dict(v="a?b"), "/v1/a%3Fb"), + ("/v1/{v}", dict(v="a#b"), "/v1/a%23b"), + # same var encoded differently by component + ( + "/v1/{v}?q={v}#{v}", + dict(v="a/b?c#d"), + "/v1/a%2Fb%3Fc%23d?q=a/b?c%23d#a/b?c%23d", + ), + ("/v1/{val}", dict(val="x?admin=true"), "/v1/x%3Fadmin=true"), # query injection + ("/v1/{val}", dict(val="x#admin"), "/v1/x%23admin"), # fragment injection + ], +) +def test_interpolation(template: str, kwargs: dict[str, Any], expected: str) -> None: + assert path_template(template, **kwargs) == expected + + +def test_missing_kwarg_raises_key_error() -> None: + with pytest.raises(KeyError, match="org_id"): + path_template("/v1/{org_id}") + + +@pytest.mark.parametrize( + "template, kwargs", + [ + ("{a}/path", dict(a=".")), + ("{a}/path", dict(a="..")), + ("/v1/{a}", dict(a=".")), + ("/v1/{a}", dict(a="..")), + ("/v1/{a}/path", dict(a=".")), + ("/v1/{a}/path", dict(a="..")), + ("/v1/{a}{b}", dict(a=".", b=".")), # adjacent vars → ".." + ("/v1/{a}.", dict(a=".")), # var + static → ".." + ("/v1/{a}{b}", dict(a="", b=".")), # empty + dot → "." + ("/v1/%2e/{x}", dict(x="ok")), # encoded dot in static text + ("/v1/%2e./{x}", dict(x="ok")), # mixed encoded ".." in static + ("/v1/.%2E/{x}", dict(x="ok")), # mixed encoded ".." in static + ("/v1/{v}?q=1", dict(v="..")), + ("/v1/{v}#frag", dict(v="..")), + ], +) +def test_dot_segment_rejected(template: str, kwargs: dict[str, Any]) -> None: + with pytest.raises(ValueError, match="dot-segment"): + path_template(template, **kwargs) From 5182d3666b20f4fe5b245201e0b992705443d024 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Mar 2026 19:45:00 +0000 Subject: [PATCH 03/13] fix: sanitize endpoint path params --- src/runloop_api_client/_utils/__init__.py | 1 + .../resources/devboxes/executions.py | 82 ++++++++++++++----- 2 files changed, 64 insertions(+), 19 deletions(-) diff --git a/src/runloop_api_client/_utils/__init__.py b/src/runloop_api_client/_utils/__init__.py index 80df24dba..64f8a6d9c 100644 --- a/src/runloop_api_client/_utils/__init__.py +++ b/src/runloop_api_client/_utils/__init__.py @@ -1,4 +1,5 @@ # isort: skip_file +from ._path import path_template as path_template from ._sync import asyncify as asyncify from ._proxy import LazyProxy as LazyProxy from ._utils import ( diff --git a/src/runloop_api_client/resources/devboxes/executions.py b/src/runloop_api_client/resources/devboxes/executions.py index 8304c038d..ff7638798 100755 --- a/src/runloop_api_client/resources/devboxes/executions.py +++ b/src/runloop_api_client/resources/devboxes/executions.py @@ -9,7 +9,7 @@ import httpx from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from ..._utils import is_given, maybe_transform, async_maybe_transform +from ..._utils import is_given, path_template, maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -104,7 +104,9 @@ def retrieve( 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}", + path_template( + "/v1/devboxes/{devbox_id}/executions/{execution_id}", devbox_id=devbox_id, execution_id=execution_id + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -210,7 +212,7 @@ def execute_async( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/v1/devboxes/{id}/execute_async", + path_template("/v1/devboxes/{id}/execute_async", id=id), body=maybe_transform( { "command": command, @@ -278,7 +280,7 @@ def execute_sync( if not is_given(timeout) and self._client.timeout == DEFAULT_TIMEOUT: timeout = 600 return self._post( - f"/v1/devboxes/{id}/execute_sync", + path_template("/v1/devboxes/{id}/execute_sync", id=id), body=maybe_transform( { "command": command, @@ -334,7 +336,11 @@ def kill( if not execution_id: raise ValueError(f"Expected a non-empty value for `execution_id` but received {execution_id!r}") return self._post( - f"/v1/devboxes/{devbox_id}/executions/{execution_id}/kill", + path_template( + "/v1/devboxes/{devbox_id}/executions/{execution_id}/kill", + devbox_id=devbox_id, + execution_id=execution_id, + ), body=maybe_transform({"kill_process_group": kill_process_group}, execution_kill_params.ExecutionKillParams), options=make_request_options( extra_headers=extra_headers, @@ -384,7 +390,11 @@ def send_std_in( if not execution_id: raise ValueError(f"Expected a non-empty value for `execution_id` but received {execution_id!r}") return self._post( - f"/v1/devboxes/{devbox_id}/executions/{execution_id}/send_std_in", + path_template( + "/v1/devboxes/{devbox_id}/executions/{execution_id}/send_std_in", + devbox_id=devbox_id, + execution_id=execution_id, + ), body=maybe_transform( { "signal": signal, @@ -435,12 +445,18 @@ def stream_stderr_updates( if not execution_id: raise ValueError(f"Expected a non-empty value for `execution_id` but received {execution_id!r}") + stream_path = path_template( + "/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates", + devbox_id=devbox_id, + execution_id=execution_id, + ) + default_headers: Headers = {"Accept": "text/event-stream"} merged_headers = default_headers if extra_headers is None else {**default_headers, **extra_headers} if merged_headers and merged_headers.get(RAW_RESPONSE_HEADER): return self._get( - f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates", + stream_path, options=make_request_options( extra_headers=merged_headers, extra_query=extra_query, @@ -458,7 +474,7 @@ def stream_stderr_updates( 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", + stream_path, options=make_request_options( extra_headers=merged_headers, extra_query=extra_query, @@ -520,12 +536,18 @@ def stream_stdout_updates( if not execution_id: raise ValueError(f"Expected a non-empty value for `execution_id` but received {execution_id!r}") + stream_path = path_template( + "/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stdout_updates", + devbox_id=devbox_id, + execution_id=execution_id, + ) + default_headers: Headers = {"Accept": "text/event-stream"} merged_headers = default_headers if extra_headers is None else {**default_headers, **extra_headers} if merged_headers and merged_headers.get(RAW_RESPONSE_HEADER): return self._get( - f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stdout_updates", + stream_path, options=make_request_options( extra_headers=merged_headers, extra_query=extra_query, @@ -543,7 +565,7 @@ def stream_stdout_updates( 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", + stream_path, options=make_request_options( extra_headers=merged_headers, extra_query=extra_query, @@ -626,7 +648,9 @@ async def retrieve( 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}", + path_template( + "/v1/devboxes/{devbox_id}/executions/{execution_id}", devbox_id=devbox_id, execution_id=execution_id + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -730,7 +754,7 @@ async def execute_async( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/v1/devboxes/{id}/execute_async", + path_template("/v1/devboxes/{id}/execute_async", id=id), body=await async_maybe_transform( { "command": command, @@ -798,7 +822,7 @@ async def execute_sync( if not is_given(timeout) and self._client.timeout == DEFAULT_TIMEOUT: timeout = 600 return await self._post( - f"/v1/devboxes/{id}/execute_sync", + path_template("/v1/devboxes/{id}/execute_sync", id=id), body=await async_maybe_transform( { "command": command, @@ -854,7 +878,11 @@ async def kill( if not execution_id: raise ValueError(f"Expected a non-empty value for `execution_id` but received {execution_id!r}") return await self._post( - f"/v1/devboxes/{devbox_id}/executions/{execution_id}/kill", + path_template( + "/v1/devboxes/{devbox_id}/executions/{execution_id}/kill", + devbox_id=devbox_id, + execution_id=execution_id, + ), body=await async_maybe_transform( {"kill_process_group": kill_process_group}, execution_kill_params.ExecutionKillParams ), @@ -906,7 +934,11 @@ async def send_std_in( if not execution_id: raise ValueError(f"Expected a non-empty value for `execution_id` but received {execution_id!r}") return await self._post( - f"/v1/devboxes/{devbox_id}/executions/{execution_id}/send_std_in", + path_template( + "/v1/devboxes/{devbox_id}/executions/{execution_id}/send_std_in", + devbox_id=devbox_id, + execution_id=execution_id, + ), body=await async_maybe_transform( { "signal": signal, @@ -957,12 +989,18 @@ async def stream_stderr_updates( if not execution_id: raise ValueError(f"Expected a non-empty value for `execution_id` but received {execution_id!r}") + stream_path = path_template( + "/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates", + devbox_id=devbox_id, + execution_id=execution_id, + ) + default_headers: Headers = {"Accept": "text/event-stream"} merged_headers = default_headers if extra_headers is None else {**default_headers, **extra_headers} if merged_headers and merged_headers.get(RAW_RESPONSE_HEADER): return await self._get( - f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stderr_updates", + stream_path, options=make_request_options( extra_headers=merged_headers, extra_query=extra_query, @@ -980,7 +1018,7 @@ async def stream_stderr_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_stderr_updates", + stream_path, options=make_request_options( extra_headers=merged_headers, extra_query=extra_query, @@ -1042,13 +1080,19 @@ async def stream_stdout_updates( if not execution_id: raise ValueError(f"Expected a non-empty value for `execution_id` but received {execution_id!r}") + stream_path = path_template( + "/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stdout_updates", + devbox_id=devbox_id, + execution_id=execution_id, + ) + default_headers: Headers = {"Accept": "text/event-stream"} merged_headers = default_headers if extra_headers is None else {**default_headers, **extra_headers} # If caller requested a raw or streaming response wrapper, return the underlying stream as-is if merged_headers and merged_headers.get(RAW_RESPONSE_HEADER): return await self._get( - f"/v1/devboxes/{devbox_id}/executions/{execution_id}/stream_stdout_updates", + stream_path, options=make_request_options( extra_headers=merged_headers, extra_query=extra_query, @@ -1066,7 +1110,7 @@ async def stream_stdout_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_stdout_updates", + stream_path, options=make_request_options( extra_headers=merged_headers, extra_query=extra_query, From dd8fe60922808bb0f001b49a99988004195d806f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 02:15:10 +0000 Subject: [PATCH 04/13] refactor(tests): switch from prism to steady --- CONTRIBUTING.md | 2 +- scripts/mock | 26 +++++++++++++------------- scripts/test | 16 ++++++++-------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cdf385047..e1abe960c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -85,7 +85,7 @@ $ pip install ./path-to-wheel-file.whl ## Running tests -Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests. +Most tests require you to [set up a mock server](https://github.com/dgellow/steady) against the OpenAPI spec to run the tests. ```sh $ ./scripts/mock diff --git a/scripts/mock b/scripts/mock index bcf3b392b..38201de83 100755 --- a/scripts/mock +++ b/scripts/mock @@ -19,34 +19,34 @@ fi echo "==> Starting mock server with URL ${URL}" -# Run prism mock on the given spec +# Run steady mock on the given spec if [ "$1" == "--daemon" ]; then # Pre-install the package so the download doesn't eat into the startup timeout - npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism --version + npm exec --package=@stdy/cli@0.19.3 -- steady --version - npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" &> .prism.log & + npm exec --package=@stdy/cli@0.19.3 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=comma --validator-query-object-format=brackets "$URL" &> .stdy.log & - # Wait for server to come online (max 30s) + # Wait for server to come online via health endpoint (max 30s) echo -n "Waiting for server" attempts=0 - while ! grep -q "✖ fatal\|Prism is listening" ".prism.log" ; do + while ! curl --silent --fail "http://127.0.0.1:4010/_x-steady/health" >/dev/null 2>&1; do + if ! kill -0 $! 2>/dev/null; then + echo + cat .stdy.log + exit 1 + fi attempts=$((attempts + 1)) if [ "$attempts" -ge 300 ]; then echo - echo "Timed out waiting for Prism server to start" - cat .prism.log + echo "Timed out waiting for Steady server to start" + cat .stdy.log exit 1 fi echo -n "." sleep 0.1 done - if grep -q "✖ fatal" ".prism.log"; then - cat .prism.log - exit 1 - fi - echo else - npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" + npm exec --package=@stdy/cli@0.19.3 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=comma --validator-query-object-format=brackets "$URL" fi diff --git a/scripts/test b/scripts/test index df5c29f74..59bf28128 100755 --- a/scripts/test +++ b/scripts/test @@ -9,8 +9,8 @@ GREEN='\033[0;32m' YELLOW='\033[0;33m' NC='\033[0m' # No Color -function prism_is_running() { - curl --silent "http://localhost:4010" >/dev/null 2>&1 +function steady_is_running() { + curl --silent "http://127.0.0.1:4010/_x-steady/health" >/dev/null 2>&1 } kill_server_on_port() { @@ -25,7 +25,7 @@ function is_overriding_api_base_url() { [ -n "$TEST_API_BASE_URL" ] } -if ! is_overriding_api_base_url && ! prism_is_running ; then +if ! is_overriding_api_base_url && ! steady_is_running ; then # When we exit this script, make sure to kill the background mock server process trap 'kill_server_on_port 4010' EXIT @@ -36,19 +36,19 @@ fi if is_overriding_api_base_url ; then echo -e "${GREEN}✔ Running tests against ${TEST_API_BASE_URL}${NC}" echo -elif ! prism_is_running ; then - echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Prism server" +elif ! steady_is_running ; then + echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Steady server" echo -e "running against your OpenAPI spec." echo echo -e "To run the server, pass in the path or url of your OpenAPI" - echo -e "spec to the prism command:" + echo -e "spec to the steady command:" echo - echo -e " \$ ${YELLOW}npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock path/to/your.openapi.yml${NC}" + echo -e " \$ ${YELLOW}npm exec --package=@stdy/cli@0.19.3 -- steady path/to/your.openapi.yml --host 127.0.0.1 -p 4010 --validator-query-array-format=comma --validator-query-object-format=brackets${NC}" echo exit 1 else - echo -e "${GREEN}✔ Mock prism server is running with your OpenAPI spec${NC}" + echo -e "${GREEN}✔ Mock steady server is running with your OpenAPI spec${NC}" echo fi From 4bd524fa32e6aad6efeeabd8a5bbef5e3c7ea742 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 19:25:52 +0000 Subject: [PATCH 05/13] refactor: undeprecate total_count from pagination API, remove remaining_count (#8084) --- .stats.yml | 4 ++-- src/runloop_api_client/resources/agents.py | 10 ++++++++++ .../resources/benchmark_jobs.py | 10 ++++++++++ .../resources/benchmark_runs.py | 20 +++++++++++++++++++ .../resources/benchmarks.py | 20 +++++++++++++++++++ .../resources/blueprints.py | 20 +++++++++++++++++++ .../resources/devboxes/devboxes.py | 20 +++++++++++++++++++ .../resources/devboxes/disk_snapshots.py | 10 ++++++++++ .../resources/gateway_configs.py | 10 ++++++++++ .../resources/mcp_configs.py | 10 ++++++++++ .../resources/network_policies.py | 10 ++++++++++ src/runloop_api_client/resources/objects.py | 20 +++++++++++++++++++ .../resources/repositories.py | 10 ++++++++++ .../resources/scenarios/runs.py | 10 ++++++++++ .../resources/scenarios/scenarios.py | 20 +++++++++++++++++++ .../resources/scenarios/scorers.py | 10 ++++++++++ .../types/agent_list_params.py | 6 ++++++ .../types/agent_list_view.py | 11 +--------- .../types/benchmark_job_list_params.py | 6 ++++++ .../types/benchmark_job_list_view.py | 2 -- .../types/benchmark_list_params.py | 6 ++++++ .../types/benchmark_list_public_params.py | 6 ++++++ .../types/benchmark_run_list_params.py | 6 ++++++ ...benchmark_run_list_scenario_runs_params.py | 6 ++++++ .../types/benchmark_run_list_view.py | 2 -- .../types/blueprint_list_params.py | 6 ++++++ .../types/blueprint_list_public_params.py | 6 ++++++ .../types/blueprint_list_view.py | 2 -- .../devbox_list_disk_snapshots_params.py | 6 ++++++ .../types/devbox_list_params.py | 6 ++++++ .../types/devbox_list_view.py | 2 -- .../types/devbox_snapshot_list_view.py | 2 -- .../devboxes/disk_snapshot_list_params.py | 6 ++++++ .../types/gateway_config_list_params.py | 6 ++++++ .../types/gateway_config_list_view.py | 5 +---- .../types/mcp_config_list_params.py | 6 ++++++ .../types/mcp_config_list_view.py | 5 +---- .../types/network_policy_list_params.py | 6 ++++++ .../types/network_policy_list_view.py | 5 +---- .../types/object_list_params.py | 6 ++++++ .../types/object_list_public_params.py | 6 ++++++ .../types/object_list_view.py | 11 +--------- .../types/repository_connection_list_view.py | 2 -- .../types/repository_list_params.py | 6 ++++++ .../types/scenario_definition_list_view.py | 2 -- .../types/scenario_list_params.py | 6 ++++++ .../types/scenario_list_public_params.py | 6 ++++++ .../types/scenario_run_list_view.py | 2 -- .../types/scenarios/run_list_params.py | 6 ++++++ .../types/scenarios/scorer_list_params.py | 6 ++++++ .../types/secret_list_view.py | 11 +--------- .../types/shared/launch_parameters.py | 6 ++++++ .../types/shared_params/launch_parameters.py | 8 +++++++- .../devboxes/test_disk_snapshots.py | 2 ++ tests/api_resources/scenarios/test_runs.py | 2 ++ tests/api_resources/scenarios/test_scorers.py | 2 ++ tests/api_resources/test_agents.py | 2 ++ tests/api_resources/test_benchmark_jobs.py | 2 ++ tests/api_resources/test_benchmark_runs.py | 4 ++++ tests/api_resources/test_benchmarks.py | 6 ++++++ tests/api_resources/test_blueprints.py | 10 ++++++++++ tests/api_resources/test_devboxes.py | 6 ++++++ tests/api_resources/test_gateway_configs.py | 2 ++ tests/api_resources/test_mcp_configs.py | 2 ++ tests/api_resources/test_network_policies.py | 2 ++ tests/api_resources/test_objects.py | 4 ++++ tests/api_resources/test_repositories.py | 2 ++ tests/api_resources/test_scenarios.py | 10 ++++++++++ 68 files changed, 415 insertions(+), 61 deletions(-) diff --git a/.stats.yml b/.stats.yml index 57fda9e71..96414b617 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 117 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-b56067b06cdf0eb4150f81d8f53e46d9c79b8cbecba6f8c0ee82f798d8cd2447.yml -openapi_spec_hash: 4e0020f255cd31baa79227a5888e1eac +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-3d441e9c2074aef7bd1e87b2ebfbe5c25e340084dbdfeed8a2e4dc9384561e3b.yml +openapi_spec_hash: 250bfdf2c2270ade53c058419e23c5dc config_hash: de99cfce88e2d1f02246dc6c2f43bc6c diff --git a/src/runloop_api_client/resources/agents.py b/src/runloop_api_client/resources/agents.py index 8c4d108ea..082d5725c 100644 --- a/src/runloop_api_client/resources/agents.py +++ b/src/runloop_api_client/resources/agents.py @@ -137,6 +137,7 @@ def retrieve( def list( self, *, + include_total_count: bool | Omit = omit, is_public: bool | Omit = omit, limit: int | Omit = omit, name: str | Omit = omit, @@ -154,6 +155,9 @@ def list( List all Agents for the authenticated account with pagination support. Args: + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + is_public: Filter agents by public visibility. limit: The limit of items to return. Default is 20. Max is 5000. @@ -184,6 +188,7 @@ def list( timeout=timeout, query=maybe_transform( { + "include_total_count": include_total_count, "is_public": is_public, "limit": limit, "name": name, @@ -310,6 +315,7 @@ async def retrieve( def list( self, *, + include_total_count: bool | Omit = omit, is_public: bool | Omit = omit, limit: int | Omit = omit, name: str | Omit = omit, @@ -327,6 +333,9 @@ def list( List all Agents for the authenticated account with pagination support. Args: + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + is_public: Filter agents by public visibility. limit: The limit of items to return. Default is 20. Max is 5000. @@ -357,6 +366,7 @@ def list( timeout=timeout, query=maybe_transform( { + "include_total_count": include_total_count, "is_public": is_public, "limit": limit, "name": name, diff --git a/src/runloop_api_client/resources/benchmark_jobs.py b/src/runloop_api_client/resources/benchmark_jobs.py index 2841df6f9..e57e58c33 100644 --- a/src/runloop_api_client/resources/benchmark_jobs.py +++ b/src/runloop_api_client/resources/benchmark_jobs.py @@ -131,6 +131,7 @@ def retrieve( def list( self, *, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, name: str | Omit = omit, starting_after: str | Omit = omit, @@ -145,6 +146,9 @@ def list( [Beta] List all BenchmarkJobs matching filter. Args: + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. name: Filter by name @@ -168,6 +172,7 @@ def list( timeout=timeout, query=maybe_transform( { + "include_total_count": include_total_count, "limit": limit, "name": name, "starting_after": starting_after, @@ -286,6 +291,7 @@ async def retrieve( async def list( self, *, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, name: str | Omit = omit, starting_after: str | Omit = omit, @@ -300,6 +306,9 @@ async def list( [Beta] List all BenchmarkJobs matching filter. Args: + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. name: Filter by name @@ -323,6 +332,7 @@ async def list( timeout=timeout, query=await async_maybe_transform( { + "include_total_count": include_total_count, "limit": limit, "name": name, "starting_after": starting_after, diff --git a/src/runloop_api_client/resources/benchmark_runs.py b/src/runloop_api_client/resources/benchmark_runs.py index b889244a7..27e327d3d 100644 --- a/src/runloop_api_client/resources/benchmark_runs.py +++ b/src/runloop_api_client/resources/benchmark_runs.py @@ -82,6 +82,7 @@ def list( self, *, benchmark_id: str | Omit = omit, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, name: str | Omit = omit, starting_after: str | Omit = omit, @@ -98,6 +99,9 @@ def list( Args: benchmark_id: The Benchmark ID to filter by. + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. name: Filter by name @@ -123,6 +127,7 @@ def list( query=maybe_transform( { "benchmark_id": benchmark_id, + "include_total_count": include_total_count, "limit": limit, "name": name, "starting_after": starting_after, @@ -220,6 +225,7 @@ def list_scenario_runs( self, id: str, *, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, starting_after: str | Omit = omit, state: Literal["running", "scoring", "scored", "completed", "canceled", "timeout", "failed"] | Omit = omit, @@ -234,6 +240,9 @@ def list_scenario_runs( List started scenario runs for a benchmark run. Args: + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. starting_after: Load the next page of data starting after the item with the given ID. @@ -260,6 +269,7 @@ def list_scenario_runs( timeout=timeout, query=maybe_transform( { + "include_total_count": include_total_count, "limit": limit, "starting_after": starting_after, "state": state, @@ -328,6 +338,7 @@ def list( self, *, benchmark_id: str | Omit = omit, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, name: str | Omit = omit, starting_after: str | Omit = omit, @@ -344,6 +355,9 @@ def list( Args: benchmark_id: The Benchmark ID to filter by. + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. name: Filter by name @@ -369,6 +383,7 @@ def list( query=maybe_transform( { "benchmark_id": benchmark_id, + "include_total_count": include_total_count, "limit": limit, "name": name, "starting_after": starting_after, @@ -466,6 +481,7 @@ def list_scenario_runs( self, id: str, *, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, starting_after: str | Omit = omit, state: Literal["running", "scoring", "scored", "completed", "canceled", "timeout", "failed"] | Omit = omit, @@ -480,6 +496,9 @@ def list_scenario_runs( List started scenario runs for a benchmark run. Args: + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. starting_after: Load the next page of data starting after the item with the given ID. @@ -506,6 +525,7 @@ def list_scenario_runs( timeout=timeout, query=maybe_transform( { + "include_total_count": include_total_count, "limit": limit, "starting_after": starting_after, "state": state, diff --git a/src/runloop_api_client/resources/benchmarks.py b/src/runloop_api_client/resources/benchmarks.py index fecf3e422..ca5442f52 100644 --- a/src/runloop_api_client/resources/benchmarks.py +++ b/src/runloop_api_client/resources/benchmarks.py @@ -244,6 +244,7 @@ def update( def list( self, *, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, name: str | Omit = omit, starting_after: str | Omit = omit, @@ -258,6 +259,9 @@ def list( List all Benchmarks matching filter. Args: + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. name: Filter by name @@ -282,6 +286,7 @@ def list( timeout=timeout, query=maybe_transform( { + "include_total_count": include_total_count, "limit": limit, "name": name, "starting_after": starting_after, @@ -344,6 +349,7 @@ def definitions( def list_public( self, *, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, starting_after: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -357,6 +363,9 @@ def list_public( List all public benchmarks matching filter. Args: + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. starting_after: Load the next page of data starting after the item with the given ID. @@ -379,6 +388,7 @@ def list_public( timeout=timeout, query=maybe_transform( { + "include_total_count": include_total_count, "limit": limit, "starting_after": starting_after, }, @@ -709,6 +719,7 @@ async def update( def list( self, *, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, name: str | Omit = omit, starting_after: str | Omit = omit, @@ -723,6 +734,9 @@ def list( List all Benchmarks matching filter. Args: + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. name: Filter by name @@ -747,6 +761,7 @@ def list( timeout=timeout, query=maybe_transform( { + "include_total_count": include_total_count, "limit": limit, "name": name, "starting_after": starting_after, @@ -809,6 +824,7 @@ async def definitions( def list_public( self, *, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, starting_after: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -822,6 +838,9 @@ def list_public( List all public benchmarks matching filter. Args: + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. starting_after: Load the next page of data starting after the item with the given ID. @@ -844,6 +863,7 @@ def list_public( timeout=timeout, query=maybe_transform( { + "include_total_count": include_total_count, "limit": limit, "starting_after": starting_after, }, diff --git a/src/runloop_api_client/resources/blueprints.py b/src/runloop_api_client/resources/blueprints.py index 6c6082054..7e5b09939 100644 --- a/src/runloop_api_client/resources/blueprints.py +++ b/src/runloop_api_client/resources/blueprints.py @@ -396,6 +396,7 @@ def create_and_await_build_complete( def list( self, *, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, name: str | Omit = omit, starting_after: str | Omit = omit, @@ -411,6 +412,9 @@ def list( List all Blueprints or filter by name. Args: + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. name: Filter by name @@ -437,6 +441,7 @@ def list( timeout=timeout, query=maybe_transform( { + "include_total_count": include_total_count, "limit": limit, "name": name, "starting_after": starting_after, @@ -574,6 +579,7 @@ def create_from_inspection( def list_public( self, *, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, name: str | Omit = omit, starting_after: str | Omit = omit, @@ -589,6 +595,9 @@ def list_public( List all public Blueprints that are available to all users. Args: + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. name: Filter by name @@ -615,6 +624,7 @@ def list_public( timeout=timeout, query=maybe_transform( { + "include_total_count": include_total_count, "limit": limit, "name": name, "starting_after": starting_after, @@ -1066,6 +1076,7 @@ async def create_and_await_build_complete( def list( self, *, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, name: str | Omit = omit, starting_after: str | Omit = omit, @@ -1081,6 +1092,9 @@ def list( List all Blueprints or filter by name. Args: + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. name: Filter by name @@ -1107,6 +1121,7 @@ def list( timeout=timeout, query=maybe_transform( { + "include_total_count": include_total_count, "limit": limit, "name": name, "starting_after": starting_after, @@ -1244,6 +1259,7 @@ async def create_from_inspection( def list_public( self, *, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, name: str | Omit = omit, starting_after: str | Omit = omit, @@ -1259,6 +1275,9 @@ def list_public( List all public Blueprints that are available to all users. Args: + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. name: Filter by name @@ -1285,6 +1304,7 @@ def list_public( timeout=timeout, query=maybe_transform( { + "include_total_count": include_total_count, "limit": limit, "name": name, "starting_after": starting_after, diff --git a/src/runloop_api_client/resources/devboxes/devboxes.py b/src/runloop_api_client/resources/devboxes/devboxes.py index 6e9f52d92..78c5bd066 100644 --- a/src/runloop_api_client/resources/devboxes/devboxes.py +++ b/src/runloop_api_client/resources/devboxes/devboxes.py @@ -571,6 +571,7 @@ def create_and_await_running( def list( self, *, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, starting_after: str | Omit = omit, status: Literal[ @@ -588,6 +589,9 @@ def list( List all Devboxes while optionally filtering by status. Args: + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. starting_after: Load the next page of data starting after the item with the given ID. @@ -612,6 +616,7 @@ def list( timeout=timeout, query=maybe_transform( { + "include_total_count": include_total_count, "limit": limit, "starting_after": starting_after, "status": status, @@ -1133,6 +1138,7 @@ def list_disk_snapshots( self, *, devbox_id: str | Omit = omit, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, metadata_key: str | Omit = omit, metadata_key_in: str | Omit = omit, @@ -1152,6 +1158,9 @@ def list_disk_snapshots( Args: devbox_id: Devbox ID to filter by. + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. metadata_key: Filter snapshots by metadata key-value pair. Can be used multiple times for @@ -1182,6 +1191,7 @@ def list_disk_snapshots( query=maybe_transform( { "devbox_id": devbox_id, + "include_total_count": include_total_count, "limit": limit, "metadata_key": metadata_key, "metadata_key_in": metadata_key_in, @@ -2206,6 +2216,7 @@ async def update( def list( self, *, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, starting_after: str | Omit = omit, status: Literal[ @@ -2223,6 +2234,9 @@ def list( List all Devboxes while optionally filtering by status. Args: + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. starting_after: Load the next page of data starting after the item with the given ID. @@ -2247,6 +2261,7 @@ def list( timeout=timeout, query=maybe_transform( { + "include_total_count": include_total_count, "limit": limit, "starting_after": starting_after, "status": status, @@ -2767,6 +2782,7 @@ def list_disk_snapshots( self, *, devbox_id: str | Omit = omit, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, metadata_key: str | Omit = omit, metadata_key_in: str | Omit = omit, @@ -2786,6 +2802,9 @@ def list_disk_snapshots( Args: devbox_id: Devbox ID to filter by. + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. metadata_key: Filter snapshots by metadata key-value pair. Can be used multiple times for @@ -2816,6 +2835,7 @@ def list_disk_snapshots( query=maybe_transform( { "devbox_id": devbox_id, + "include_total_count": include_total_count, "limit": limit, "metadata_key": metadata_key, "metadata_key_in": metadata_key_in, diff --git a/src/runloop_api_client/resources/devboxes/disk_snapshots.py b/src/runloop_api_client/resources/devboxes/disk_snapshots.py index d6d43c26f..c4f723359 100644 --- a/src/runloop_api_client/resources/devboxes/disk_snapshots.py +++ b/src/runloop_api_client/resources/devboxes/disk_snapshots.py @@ -111,6 +111,7 @@ def list( self, *, devbox_id: str | Omit = omit, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, metadata_key: str | Omit = omit, metadata_key_in: str | Omit = omit, @@ -130,6 +131,9 @@ def list( Args: devbox_id: Devbox ID to filter by. + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. metadata_key: Filter snapshots by metadata key-value pair. Can be used multiple times for @@ -160,6 +164,7 @@ def list( query=maybe_transform( { "devbox_id": devbox_id, + "include_total_count": include_total_count, "limit": limit, "metadata_key": metadata_key, "metadata_key_in": metadata_key_in, @@ -362,6 +367,7 @@ def list( self, *, devbox_id: str | Omit = omit, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, metadata_key: str | Omit = omit, metadata_key_in: str | Omit = omit, @@ -381,6 +387,9 @@ def list( Args: devbox_id: Devbox ID to filter by. + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. metadata_key: Filter snapshots by metadata key-value pair. Can be used multiple times for @@ -411,6 +420,7 @@ def list( query=maybe_transform( { "devbox_id": devbox_id, + "include_total_count": include_total_count, "limit": limit, "metadata_key": metadata_key, "metadata_key_in": metadata_key_in, diff --git a/src/runloop_api_client/resources/gateway_configs.py b/src/runloop_api_client/resources/gateway_configs.py index a521b871e..7874f7a79 100644 --- a/src/runloop_api_client/resources/gateway_configs.py +++ b/src/runloop_api_client/resources/gateway_configs.py @@ -205,6 +205,7 @@ def list( self, *, id: str | Omit = omit, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, name: str | Omit = omit, starting_after: str | Omit = omit, @@ -222,6 +223,9 @@ def list( Args: id: Filter by ID. + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. name: Filter by name (partial match supported). @@ -247,6 +251,7 @@ def list( query=maybe_transform( { "id": id, + "include_total_count": include_total_count, "limit": limit, "name": name, "starting_after": starting_after, @@ -480,6 +485,7 @@ def list( self, *, id: str | Omit = omit, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, name: str | Omit = omit, starting_after: str | Omit = omit, @@ -497,6 +503,9 @@ def list( Args: id: Filter by ID. + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. name: Filter by name (partial match supported). @@ -522,6 +531,7 @@ def list( query=maybe_transform( { "id": id, + "include_total_count": include_total_count, "limit": limit, "name": name, "starting_after": starting_after, diff --git a/src/runloop_api_client/resources/mcp_configs.py b/src/runloop_api_client/resources/mcp_configs.py index 9c1cabc0c..b097db18b 100644 --- a/src/runloop_api_client/resources/mcp_configs.py +++ b/src/runloop_api_client/resources/mcp_configs.py @@ -208,6 +208,7 @@ def list( self, *, id: str | Omit = omit, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, name: str | Omit = omit, starting_after: str | Omit = omit, @@ -224,6 +225,9 @@ def list( Args: id: Filter by ID. + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. name: Filter by name (prefix match supported). @@ -249,6 +253,7 @@ def list( query=maybe_transform( { "id": id, + "include_total_count": include_total_count, "limit": limit, "name": name, "starting_after": starting_after, @@ -485,6 +490,7 @@ def list( self, *, id: str | Omit = omit, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, name: str | Omit = omit, starting_after: str | Omit = omit, @@ -501,6 +507,9 @@ def list( Args: id: Filter by ID. + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. name: Filter by name (prefix match supported). @@ -526,6 +535,7 @@ def list( query=maybe_transform( { "id": id, + "include_total_count": include_total_count, "limit": limit, "name": name, "starting_after": starting_after, diff --git a/src/runloop_api_client/resources/network_policies.py b/src/runloop_api_client/resources/network_policies.py index d472b54a8..7ba7abd9e 100644 --- a/src/runloop_api_client/resources/network_policies.py +++ b/src/runloop_api_client/resources/network_policies.py @@ -235,6 +235,7 @@ def list( self, *, id: str | Omit = omit, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, name: str | Omit = omit, starting_after: str | Omit = omit, @@ -251,6 +252,9 @@ def list( Args: id: Filter by ID. + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. name: Filter by name (partial match supported). @@ -276,6 +280,7 @@ def list( query=maybe_transform( { "id": id, + "include_total_count": include_total_count, "limit": limit, "name": name, "starting_after": starting_after, @@ -539,6 +544,7 @@ def list( self, *, id: str | Omit = omit, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, name: str | Omit = omit, starting_after: str | Omit = omit, @@ -555,6 +561,9 @@ def list( Args: id: Filter by ID. + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. name: Filter by name (partial match supported). @@ -580,6 +589,7 @@ def list( query=maybe_transform( { "id": id, + "include_total_count": include_total_count, "limit": limit, "name": name, "starting_after": starting_after, diff --git a/src/runloop_api_client/resources/objects.py b/src/runloop_api_client/resources/objects.py index 78ab27aa3..5df9476ec 100644 --- a/src/runloop_api_client/resources/objects.py +++ b/src/runloop_api_client/resources/objects.py @@ -144,6 +144,7 @@ def list( self, *, content_type: Literal["unspecified", "text", "binary", "gzip", "tar", "tgz"] | Omit = omit, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, name: str | Omit = omit, search: str | Omit = omit, @@ -162,6 +163,9 @@ def list( Args: content_type: Filter storage objects by content type. + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. name: Filter storage objects by name (partial match supported). @@ -191,6 +195,7 @@ def list( query=maybe_transform( { "content_type": content_type, + "include_total_count": include_total_count, "limit": limit, "name": name, "search": search, @@ -334,6 +339,7 @@ def list_public( self, *, content_type: Literal["unspecified", "text", "binary", "gzip", "tar", "tgz"] | Omit = omit, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, name: str | Omit = omit, search: str | Omit = omit, @@ -352,6 +358,9 @@ def list_public( Args: content_type: Filter storage objects by content type. + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. name: Filter storage objects by name (partial match supported). @@ -381,6 +390,7 @@ def list_public( query=maybe_transform( { "content_type": content_type, + "include_total_count": include_total_count, "limit": limit, "name": name, "search": search, @@ -512,6 +522,7 @@ def list( self, *, content_type: Literal["unspecified", "text", "binary", "gzip", "tar", "tgz"] | Omit = omit, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, name: str | Omit = omit, search: str | Omit = omit, @@ -530,6 +541,9 @@ def list( Args: content_type: Filter storage objects by content type. + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. name: Filter storage objects by name (partial match supported). @@ -559,6 +573,7 @@ def list( query=maybe_transform( { "content_type": content_type, + "include_total_count": include_total_count, "limit": limit, "name": name, "search": search, @@ -702,6 +717,7 @@ def list_public( self, *, content_type: Literal["unspecified", "text", "binary", "gzip", "tar", "tgz"] | Omit = omit, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, name: str | Omit = omit, search: str | Omit = omit, @@ -720,6 +736,9 @@ def list_public( Args: content_type: Filter storage objects by content type. + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. name: Filter storage objects by name (partial match supported). @@ -749,6 +768,7 @@ def list_public( query=maybe_transform( { "content_type": content_type, + "include_total_count": include_total_count, "limit": limit, "name": name, "search": search, diff --git a/src/runloop_api_client/resources/repositories.py b/src/runloop_api_client/resources/repositories.py index 58421b8d2..5b961cf64 100644 --- a/src/runloop_api_client/resources/repositories.py +++ b/src/runloop_api_client/resources/repositories.py @@ -148,6 +148,7 @@ def retrieve( def list( self, *, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, name: str | Omit = omit, owner: str | Omit = omit, @@ -163,6 +164,9 @@ def list( List all available repository connections. Args: + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. name: Filter by repository name @@ -189,6 +193,7 @@ def list( timeout=timeout, query=maybe_transform( { + "include_total_count": include_total_count, "limit": limit, "name": name, "owner": owner, @@ -527,6 +532,7 @@ async def retrieve( def list( self, *, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, name: str | Omit = omit, owner: str | Omit = omit, @@ -542,6 +548,9 @@ def list( List all available repository connections. Args: + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. name: Filter by repository name @@ -568,6 +577,7 @@ def list( timeout=timeout, query=maybe_transform( { + "include_total_count": include_total_count, "limit": limit, "name": name, "owner": owner, diff --git a/src/runloop_api_client/resources/scenarios/runs.py b/src/runloop_api_client/resources/scenarios/runs.py index f655fe565..67c5c4428 100644 --- a/src/runloop_api_client/resources/scenarios/runs.py +++ b/src/runloop_api_client/resources/scenarios/runs.py @@ -90,6 +90,7 @@ def list( self, *, benchmark_run_id: str | Omit = omit, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, name: str | Omit = omit, scenario_id: str | Omit = omit, @@ -108,6 +109,9 @@ def list( Args: benchmark_run_id: Filter by benchmark run ID + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. name: Filter by name @@ -137,6 +141,7 @@ def list( query=maybe_transform( { "benchmark_run_id": benchmark_run_id, + "include_total_count": include_total_count, "limit": limit, "name": name, "scenario_id": scenario_id, @@ -510,6 +515,7 @@ def list( self, *, benchmark_run_id: str | Omit = omit, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, name: str | Omit = omit, scenario_id: str | Omit = omit, @@ -528,6 +534,9 @@ def list( Args: benchmark_run_id: Filter by benchmark run ID + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. name: Filter by name @@ -557,6 +566,7 @@ def list( query=maybe_transform( { "benchmark_run_id": benchmark_run_id, + "include_total_count": include_total_count, "limit": limit, "name": name, "scenario_id": scenario_id, diff --git a/src/runloop_api_client/resources/scenarios/scenarios.py b/src/runloop_api_client/resources/scenarios/scenarios.py index 65f282ed7..e3ce8c91b 100644 --- a/src/runloop_api_client/resources/scenarios/scenarios.py +++ b/src/runloop_api_client/resources/scenarios/scenarios.py @@ -298,6 +298,7 @@ def list( self, *, benchmark_id: str | Omit = omit, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, name: str | Omit = omit, starting_after: str | Omit = omit, @@ -315,6 +316,9 @@ def list( Args: benchmark_id: Filter scenarios by benchmark ID. + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. name: Query for Scenarios with a given name. @@ -342,6 +346,7 @@ def list( query=maybe_transform( { "benchmark_id": benchmark_id, + "include_total_count": include_total_count, "limit": limit, "name": name, "starting_after": starting_after, @@ -398,6 +403,7 @@ def archive( def list_public( self, *, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, name: str | Omit = omit, starting_after: str | Omit = omit, @@ -412,6 +418,9 @@ def list_public( List all public scenarios matching filter. Args: + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. name: Query for Scenarios with a given name. @@ -436,6 +445,7 @@ def list_public( timeout=timeout, query=maybe_transform( { + "include_total_count": include_total_count, "limit": limit, "name": name, "starting_after": starting_after, @@ -810,6 +820,7 @@ def list( self, *, benchmark_id: str | Omit = omit, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, name: str | Omit = omit, starting_after: str | Omit = omit, @@ -827,6 +838,9 @@ def list( Args: benchmark_id: Filter scenarios by benchmark ID. + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. name: Query for Scenarios with a given name. @@ -854,6 +868,7 @@ def list( query=maybe_transform( { "benchmark_id": benchmark_id, + "include_total_count": include_total_count, "limit": limit, "name": name, "starting_after": starting_after, @@ -910,6 +925,7 @@ async def archive( def list_public( self, *, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, name: str | Omit = omit, starting_after: str | Omit = omit, @@ -924,6 +940,9 @@ def list_public( List all public scenarios matching filter. Args: + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. name: Query for Scenarios with a given name. @@ -948,6 +967,7 @@ def list_public( timeout=timeout, query=maybe_transform( { + "include_total_count": include_total_count, "limit": limit, "name": name, "starting_after": starting_after, diff --git a/src/runloop_api_client/resources/scenarios/scorers.py b/src/runloop_api_client/resources/scenarios/scorers.py index 118439745..1472d86af 100644 --- a/src/runloop_api_client/resources/scenarios/scorers.py +++ b/src/runloop_api_client/resources/scenarios/scorers.py @@ -186,6 +186,7 @@ def update( def list( self, *, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, starting_after: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -199,6 +200,9 @@ def list( List all Scenario Scorers matching filter. Args: + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. starting_after: Load the next page of data starting after the item with the given ID. @@ -221,6 +225,7 @@ def list( timeout=timeout, query=maybe_transform( { + "include_total_count": include_total_count, "limit": limit, "starting_after": starting_after, }, @@ -392,6 +397,7 @@ async def update( def list( self, *, + include_total_count: bool | Omit = omit, limit: int | Omit = omit, starting_after: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -405,6 +411,9 @@ def list( List all Scenario Scorers matching filter. Args: + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + limit: The limit of items to return. Default is 20. Max is 5000. starting_after: Load the next page of data starting after the item with the given ID. @@ -427,6 +436,7 @@ def list( timeout=timeout, query=maybe_transform( { + "include_total_count": include_total_count, "limit": limit, "starting_after": starting_after, }, diff --git a/src/runloop_api_client/types/agent_list_params.py b/src/runloop_api_client/types/agent_list_params.py index 3df89fc25..5c3857fb5 100644 --- a/src/runloop_api_client/types/agent_list_params.py +++ b/src/runloop_api_client/types/agent_list_params.py @@ -8,6 +8,12 @@ class AgentListParams(TypedDict, total=False): + include_total_count: bool + """If true (default), includes total_count in the response. + + Set to false to skip the count query for better performance on large datasets. + """ + is_public: bool """Filter agents by public visibility.""" diff --git a/src/runloop_api_client/types/agent_list_view.py b/src/runloop_api_client/types/agent_list_view.py index 9e57a9769..f3dfbd09f 100644 --- a/src/runloop_api_client/types/agent_list_view.py +++ b/src/runloop_api_client/types/agent_list_view.py @@ -17,14 +17,5 @@ class AgentListView(BaseModel): has_more: bool """Whether there are more Agents to fetch.""" - remaining_count: Optional[int] = None - """The count of remaining Agents. - - Deprecated: will be removed in a future breaking change. - """ - total_count: Optional[int] = None - """The total count of Agents. - - Deprecated: will be removed in a future breaking change. - """ + """The total count of Agents.""" diff --git a/src/runloop_api_client/types/benchmark_job_list_params.py b/src/runloop_api_client/types/benchmark_job_list_params.py index c0db8843c..9ab17e8eb 100644 --- a/src/runloop_api_client/types/benchmark_job_list_params.py +++ b/src/runloop_api_client/types/benchmark_job_list_params.py @@ -8,6 +8,12 @@ class BenchmarkJobListParams(TypedDict, total=False): + include_total_count: bool + """If true (default), includes total_count in the response. + + Set to false to skip the count query for better performance on large datasets. + """ + limit: int """The limit of items to return. Default is 20. Max is 5000.""" diff --git a/src/runloop_api_client/types/benchmark_job_list_view.py b/src/runloop_api_client/types/benchmark_job_list_view.py index f0e1da7d0..f4f2458c3 100644 --- a/src/runloop_api_client/types/benchmark_job_list_view.py +++ b/src/runloop_api_client/types/benchmark_job_list_view.py @@ -14,6 +14,4 @@ class BenchmarkJobListView(BaseModel): jobs: List[BenchmarkJobView] """List of BenchmarkJobs matching filter.""" - remaining_count: Optional[int] = None - total_count: Optional[int] = None diff --git a/src/runloop_api_client/types/benchmark_list_params.py b/src/runloop_api_client/types/benchmark_list_params.py index 4e8b0c78b..d8be9aeca 100644 --- a/src/runloop_api_client/types/benchmark_list_params.py +++ b/src/runloop_api_client/types/benchmark_list_params.py @@ -8,6 +8,12 @@ class BenchmarkListParams(TypedDict, total=False): + include_total_count: bool + """If true (default), includes total_count in the response. + + Set to false to skip the count query for better performance on large datasets. + """ + limit: int """The limit of items to return. Default is 20. Max is 5000.""" diff --git a/src/runloop_api_client/types/benchmark_list_public_params.py b/src/runloop_api_client/types/benchmark_list_public_params.py index 6dec4283b..9be7ffdf4 100644 --- a/src/runloop_api_client/types/benchmark_list_public_params.py +++ b/src/runloop_api_client/types/benchmark_list_public_params.py @@ -8,6 +8,12 @@ class BenchmarkListPublicParams(TypedDict, total=False): + include_total_count: bool + """If true (default), includes total_count in the response. + + Set to false to skip the count query for better performance on large datasets. + """ + limit: int """The limit of items to return. Default is 20. Max is 5000.""" diff --git a/src/runloop_api_client/types/benchmark_run_list_params.py b/src/runloop_api_client/types/benchmark_run_list_params.py index 28d4e2e87..4f4c8a550 100644 --- a/src/runloop_api_client/types/benchmark_run_list_params.py +++ b/src/runloop_api_client/types/benchmark_run_list_params.py @@ -11,6 +11,12 @@ class BenchmarkRunListParams(TypedDict, total=False): benchmark_id: str """The Benchmark ID to filter by.""" + include_total_count: bool + """If true (default), includes total_count in the response. + + Set to false to skip the count query for better performance on large datasets. + """ + limit: int """The limit of items to return. Default is 20. Max is 5000.""" diff --git a/src/runloop_api_client/types/benchmark_run_list_scenario_runs_params.py b/src/runloop_api_client/types/benchmark_run_list_scenario_runs_params.py index c88c09167..dddfa256a 100644 --- a/src/runloop_api_client/types/benchmark_run_list_scenario_runs_params.py +++ b/src/runloop_api_client/types/benchmark_run_list_scenario_runs_params.py @@ -8,6 +8,12 @@ class BenchmarkRunListScenarioRunsParams(TypedDict, total=False): + include_total_count: bool + """If true (default), includes total_count in the response. + + Set to false to skip the count query for better performance on large datasets. + """ + limit: int """The limit of items to return. Default is 20. Max is 5000.""" diff --git a/src/runloop_api_client/types/benchmark_run_list_view.py b/src/runloop_api_client/types/benchmark_run_list_view.py index e85506bab..efee2176d 100644 --- a/src/runloop_api_client/types/benchmark_run_list_view.py +++ b/src/runloop_api_client/types/benchmark_run_list_view.py @@ -14,6 +14,4 @@ class BenchmarkRunListView(BaseModel): runs: List[BenchmarkRunView] """List of BenchmarkRuns matching filter.""" - remaining_count: Optional[int] = None - total_count: Optional[int] = None diff --git a/src/runloop_api_client/types/blueprint_list_params.py b/src/runloop_api_client/types/blueprint_list_params.py index f72de7d2f..c61df2640 100644 --- a/src/runloop_api_client/types/blueprint_list_params.py +++ b/src/runloop_api_client/types/blueprint_list_params.py @@ -8,6 +8,12 @@ class BlueprintListParams(TypedDict, total=False): + include_total_count: bool + """If true (default), includes total_count in the response. + + Set to false to skip the count query for better performance on large datasets. + """ + limit: int """The limit of items to return. Default is 20. Max is 5000.""" diff --git a/src/runloop_api_client/types/blueprint_list_public_params.py b/src/runloop_api_client/types/blueprint_list_public_params.py index e0f224f32..a6b66dc9b 100644 --- a/src/runloop_api_client/types/blueprint_list_public_params.py +++ b/src/runloop_api_client/types/blueprint_list_public_params.py @@ -8,6 +8,12 @@ class BlueprintListPublicParams(TypedDict, total=False): + include_total_count: bool + """If true (default), includes total_count in the response. + + Set to false to skip the count query for better performance on large datasets. + """ + limit: int """The limit of items to return. Default is 20. Max is 5000.""" diff --git a/src/runloop_api_client/types/blueprint_list_view.py b/src/runloop_api_client/types/blueprint_list_view.py index 7d97314f5..e5eee905b 100644 --- a/src/runloop_api_client/types/blueprint_list_view.py +++ b/src/runloop_api_client/types/blueprint_list_view.py @@ -14,6 +14,4 @@ class BlueprintListView(BaseModel): has_more: bool - remaining_count: Optional[int] = None - total_count: Optional[int] = None diff --git a/src/runloop_api_client/types/devbox_list_disk_snapshots_params.py b/src/runloop_api_client/types/devbox_list_disk_snapshots_params.py index d26c3fbd8..48e0fc9e3 100644 --- a/src/runloop_api_client/types/devbox_list_disk_snapshots_params.py +++ b/src/runloop_api_client/types/devbox_list_disk_snapshots_params.py @@ -13,6 +13,12 @@ class DevboxListDiskSnapshotsParams(TypedDict, total=False): devbox_id: str """Devbox ID to filter by.""" + include_total_count: bool + """If true (default), includes total_count in the response. + + Set to false to skip the count query for better performance on large datasets. + """ + limit: int """The limit of items to return. Default is 20. Max is 5000.""" diff --git a/src/runloop_api_client/types/devbox_list_params.py b/src/runloop_api_client/types/devbox_list_params.py index c508762da..d8bae28d7 100644 --- a/src/runloop_api_client/types/devbox_list_params.py +++ b/src/runloop_api_client/types/devbox_list_params.py @@ -8,6 +8,12 @@ class DevboxListParams(TypedDict, total=False): + include_total_count: bool + """If true (default), includes total_count in the response. + + Set to false to skip the count query for better performance on large datasets. + """ + limit: int """The limit of items to return. Default is 20. Max is 5000.""" diff --git a/src/runloop_api_client/types/devbox_list_view.py b/src/runloop_api_client/types/devbox_list_view.py index 6bd522ee5..6380f9326 100644 --- a/src/runloop_api_client/types/devbox_list_view.py +++ b/src/runloop_api_client/types/devbox_list_view.py @@ -14,6 +14,4 @@ class DevboxListView(BaseModel): has_more: bool - remaining_count: Optional[int] = None - total_count: Optional[int] = None diff --git a/src/runloop_api_client/types/devbox_snapshot_list_view.py b/src/runloop_api_client/types/devbox_snapshot_list_view.py index ca54f2eb1..0fdfd0594 100644 --- a/src/runloop_api_client/types/devbox_snapshot_list_view.py +++ b/src/runloop_api_client/types/devbox_snapshot_list_view.py @@ -14,6 +14,4 @@ class DevboxSnapshotListView(BaseModel): snapshots: List[DevboxSnapshotView] """List of snapshots matching filter.""" - remaining_count: Optional[int] = None - total_count: Optional[int] = None diff --git a/src/runloop_api_client/types/devboxes/disk_snapshot_list_params.py b/src/runloop_api_client/types/devboxes/disk_snapshot_list_params.py index 73e60f457..7a00118e4 100644 --- a/src/runloop_api_client/types/devboxes/disk_snapshot_list_params.py +++ b/src/runloop_api_client/types/devboxes/disk_snapshot_list_params.py @@ -13,6 +13,12 @@ class DiskSnapshotListParams(TypedDict, total=False): devbox_id: str """Devbox ID to filter by.""" + include_total_count: bool + """If true (default), includes total_count in the response. + + Set to false to skip the count query for better performance on large datasets. + """ + limit: int """The limit of items to return. Default is 20. Max is 5000.""" diff --git a/src/runloop_api_client/types/gateway_config_list_params.py b/src/runloop_api_client/types/gateway_config_list_params.py index cc8706b95..ef69f83b4 100644 --- a/src/runloop_api_client/types/gateway_config_list_params.py +++ b/src/runloop_api_client/types/gateway_config_list_params.py @@ -11,6 +11,12 @@ class GatewayConfigListParams(TypedDict, total=False): id: str """Filter by ID.""" + include_total_count: bool + """If true (default), includes total_count in the response. + + Set to false to skip the count query for better performance on large datasets. + """ + limit: int """The limit of items to return. Default is 20. Max is 5000.""" diff --git a/src/runloop_api_client/types/gateway_config_list_view.py b/src/runloop_api_client/types/gateway_config_list_view.py index 05511971f..745e6376b 100644 --- a/src/runloop_api_client/types/gateway_config_list_view.py +++ b/src/runloop_api_client/types/gateway_config_list_view.py @@ -18,7 +18,4 @@ class GatewayConfigListView(BaseModel): """Whether there are more results available beyond this page.""" total_count: Optional[int] = None - """Total count of GatewayConfigs that match the query. - - Deprecated: will be removed in a future breaking change. - """ + """Total count of GatewayConfigs that match the query.""" diff --git a/src/runloop_api_client/types/mcp_config_list_params.py b/src/runloop_api_client/types/mcp_config_list_params.py index 8b786ba13..684de89ec 100644 --- a/src/runloop_api_client/types/mcp_config_list_params.py +++ b/src/runloop_api_client/types/mcp_config_list_params.py @@ -11,6 +11,12 @@ class McpConfigListParams(TypedDict, total=False): id: str """Filter by ID.""" + include_total_count: bool + """If true (default), includes total_count in the response. + + Set to false to skip the count query for better performance on large datasets. + """ + limit: int """The limit of items to return. Default is 20. Max is 5000.""" diff --git a/src/runloop_api_client/types/mcp_config_list_view.py b/src/runloop_api_client/types/mcp_config_list_view.py index 4992698c5..ed742162d 100644 --- a/src/runloop_api_client/types/mcp_config_list_view.py +++ b/src/runloop_api_client/types/mcp_config_list_view.py @@ -18,7 +18,4 @@ class McpConfigListView(BaseModel): """The list of McpConfigs.""" total_count: Optional[int] = None - """Total count of McpConfigs that match the query. - - Deprecated: will be removed in a future breaking change. - """ + """Total count of McpConfigs that match the query.""" diff --git a/src/runloop_api_client/types/network_policy_list_params.py b/src/runloop_api_client/types/network_policy_list_params.py index 160da795f..cdbc84f1a 100644 --- a/src/runloop_api_client/types/network_policy_list_params.py +++ b/src/runloop_api_client/types/network_policy_list_params.py @@ -11,6 +11,12 @@ class NetworkPolicyListParams(TypedDict, total=False): id: str """Filter by ID.""" + include_total_count: bool + """If true (default), includes total_count in the response. + + Set to false to skip the count query for better performance on large datasets. + """ + limit: int """The limit of items to return. Default is 20. Max is 5000.""" diff --git a/src/runloop_api_client/types/network_policy_list_view.py b/src/runloop_api_client/types/network_policy_list_view.py index ba031d0a9..2530a3699 100644 --- a/src/runloop_api_client/types/network_policy_list_view.py +++ b/src/runloop_api_client/types/network_policy_list_view.py @@ -18,7 +18,4 @@ class NetworkPolicyListView(BaseModel): """The list of NetworkPolicies.""" total_count: Optional[int] = None - """Total count of items in this response. - - Deprecated: will be removed in a future breaking change. - """ + """Total count of items in this response.""" diff --git a/src/runloop_api_client/types/object_list_params.py b/src/runloop_api_client/types/object_list_params.py index eca1c7cdd..5ec2e88bc 100644 --- a/src/runloop_api_client/types/object_list_params.py +++ b/src/runloop_api_client/types/object_list_params.py @@ -11,6 +11,12 @@ class ObjectListParams(TypedDict, total=False): content_type: Literal["unspecified", "text", "binary", "gzip", "tar", "tgz"] """Filter storage objects by content type.""" + include_total_count: bool + """If true (default), includes total_count in the response. + + Set to false to skip the count query for better performance on large datasets. + """ + limit: int """The limit of items to return. Default is 20. Max is 5000.""" diff --git a/src/runloop_api_client/types/object_list_public_params.py b/src/runloop_api_client/types/object_list_public_params.py index 67475b263..a0b53dcd6 100644 --- a/src/runloop_api_client/types/object_list_public_params.py +++ b/src/runloop_api_client/types/object_list_public_params.py @@ -11,6 +11,12 @@ class ObjectListPublicParams(TypedDict, total=False): content_type: Literal["unspecified", "text", "binary", "gzip", "tar", "tgz"] """Filter storage objects by content type.""" + include_total_count: bool + """If true (default), includes total_count in the response. + + Set to false to skip the count query for better performance on large datasets. + """ + limit: int """The limit of items to return. Default is 20. Max is 5000.""" diff --git a/src/runloop_api_client/types/object_list_view.py b/src/runloop_api_client/types/object_list_view.py index 689c0899b..4b2658c1f 100644 --- a/src/runloop_api_client/types/object_list_view.py +++ b/src/runloop_api_client/types/object_list_view.py @@ -17,14 +17,5 @@ class ObjectListView(BaseModel): objects: List[ObjectView] """List of Object entities.""" - remaining_count: Optional[int] = None - """Number of Objects remaining after this page. - - Deprecated: will be removed in a future breaking change. - """ - total_count: Optional[int] = None - """Total number of Objects across all pages. - - Deprecated: will be removed in a future breaking change. - """ + """Total number of Objects across all pages.""" diff --git a/src/runloop_api_client/types/repository_connection_list_view.py b/src/runloop_api_client/types/repository_connection_list_view.py index eea040bc4..3146f27fd 100644 --- a/src/runloop_api_client/types/repository_connection_list_view.py +++ b/src/runloop_api_client/types/repository_connection_list_view.py @@ -14,6 +14,4 @@ class RepositoryConnectionListView(BaseModel): repositories: List[RepositoryConnectionView] """List of repositories matching filter.""" - remaining_count: Optional[int] = None - total_count: Optional[int] = None diff --git a/src/runloop_api_client/types/repository_list_params.py b/src/runloop_api_client/types/repository_list_params.py index d5f7b248a..813842cab 100644 --- a/src/runloop_api_client/types/repository_list_params.py +++ b/src/runloop_api_client/types/repository_list_params.py @@ -8,6 +8,12 @@ class RepositoryListParams(TypedDict, total=False): + include_total_count: bool + """If true (default), includes total_count in the response. + + Set to false to skip the count query for better performance on large datasets. + """ + limit: int """The limit of items to return. Default is 20. Max is 5000.""" diff --git a/src/runloop_api_client/types/scenario_definition_list_view.py b/src/runloop_api_client/types/scenario_definition_list_view.py index f39cf1ed9..25bad1da5 100644 --- a/src/runloop_api_client/types/scenario_definition_list_view.py +++ b/src/runloop_api_client/types/scenario_definition_list_view.py @@ -14,6 +14,4 @@ class ScenarioDefinitionListView(BaseModel): scenarios: List[ScenarioView] """List of Scenarios matching filter.""" - remaining_count: Optional[int] = None - total_count: Optional[int] = None diff --git a/src/runloop_api_client/types/scenario_list_params.py b/src/runloop_api_client/types/scenario_list_params.py index 45ff3a87b..3d34d711a 100644 --- a/src/runloop_api_client/types/scenario_list_params.py +++ b/src/runloop_api_client/types/scenario_list_params.py @@ -11,6 +11,12 @@ class ScenarioListParams(TypedDict, total=False): benchmark_id: str """Filter scenarios by benchmark ID.""" + include_total_count: bool + """If true (default), includes total_count in the response. + + Set to false to skip the count query for better performance on large datasets. + """ + limit: int """The limit of items to return. Default is 20. Max is 5000.""" diff --git a/src/runloop_api_client/types/scenario_list_public_params.py b/src/runloop_api_client/types/scenario_list_public_params.py index be7e40b8d..a2e71da64 100644 --- a/src/runloop_api_client/types/scenario_list_public_params.py +++ b/src/runloop_api_client/types/scenario_list_public_params.py @@ -8,6 +8,12 @@ class ScenarioListPublicParams(TypedDict, total=False): + include_total_count: bool + """If true (default), includes total_count in the response. + + Set to false to skip the count query for better performance on large datasets. + """ + limit: int """The limit of items to return. Default is 20. Max is 5000.""" diff --git a/src/runloop_api_client/types/scenario_run_list_view.py b/src/runloop_api_client/types/scenario_run_list_view.py index 142292dda..6083dbde2 100644 --- a/src/runloop_api_client/types/scenario_run_list_view.py +++ b/src/runloop_api_client/types/scenario_run_list_view.py @@ -14,6 +14,4 @@ class ScenarioRunListView(BaseModel): runs: List[ScenarioRunView] """List of ScenarioRuns matching filter.""" - remaining_count: Optional[int] = None - total_count: Optional[int] = None diff --git a/src/runloop_api_client/types/scenarios/run_list_params.py b/src/runloop_api_client/types/scenarios/run_list_params.py index 97eeb425a..6e7373a07 100644 --- a/src/runloop_api_client/types/scenarios/run_list_params.py +++ b/src/runloop_api_client/types/scenarios/run_list_params.py @@ -11,6 +11,12 @@ class RunListParams(TypedDict, total=False): benchmark_run_id: str """Filter by benchmark run ID""" + include_total_count: bool + """If true (default), includes total_count in the response. + + Set to false to skip the count query for better performance on large datasets. + """ + limit: int """The limit of items to return. Default is 20. Max is 5000.""" diff --git a/src/runloop_api_client/types/scenarios/scorer_list_params.py b/src/runloop_api_client/types/scenarios/scorer_list_params.py index f80e7f6ac..a387f1a86 100644 --- a/src/runloop_api_client/types/scenarios/scorer_list_params.py +++ b/src/runloop_api_client/types/scenarios/scorer_list_params.py @@ -8,6 +8,12 @@ class ScorerListParams(TypedDict, total=False): + include_total_count: bool + """If true (default), includes total_count in the response. + + Set to false to skip the count query for better performance on large datasets. + """ + limit: int """The limit of items to return. Default is 20. Max is 5000.""" diff --git a/src/runloop_api_client/types/secret_list_view.py b/src/runloop_api_client/types/secret_list_view.py index 2f10bae2c..bb6b69d12 100644 --- a/src/runloop_api_client/types/secret_list_view.py +++ b/src/runloop_api_client/types/secret_list_view.py @@ -17,14 +17,5 @@ class SecretListView(BaseModel): secrets: List[SecretView] """List of Secret objects. Values are omitted for security.""" - remaining_count: Optional[int] = None - """Number of Secrets remaining after this page. - - Deprecated: will be removed in a future breaking change. - """ - total_count: Optional[int] = None - """Total number of Secrets across all pages. - - Deprecated: will be removed in a future breaking change. - """ + """Total number of Secrets across all pages.""" diff --git a/src/runloop_api_client/types/shared/launch_parameters.py b/src/runloop_api_client/types/shared/launch_parameters.py index c96996c72..7c1fdc2a7 100644 --- a/src/runloop_api_client/types/shared/launch_parameters.py +++ b/src/runloop_api_client/types/shared/launch_parameters.py @@ -36,6 +36,12 @@ class LaunchParameters(BaseModel): architecture: Optional[Literal["x86_64", "arm64"]] = None """The target architecture for the Devbox. If unset, defaults to x86_64.""" + available_ports: Optional[List[int]] = None + """[Deprecated] A list of ports to make available on the Devbox. + + This field is ignored. + """ + custom_cpu_cores: Optional[int] = None """Custom CPU cores. Must be 0.5, 1, or a multiple of 2. Max is 16.""" diff --git a/src/runloop_api_client/types/shared_params/launch_parameters.py b/src/runloop_api_client/types/shared_params/launch_parameters.py index dc0a54f9b..c30f90d86 100644 --- a/src/runloop_api_client/types/shared_params/launch_parameters.py +++ b/src/runloop_api_client/types/shared_params/launch_parameters.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Optional +from typing import Iterable, Optional from typing_extensions import Literal, Required, TypedDict from ..._types import SequenceNotStr @@ -38,6 +38,12 @@ class LaunchParameters(TypedDict, total=False): architecture: Optional[Literal["x86_64", "arm64"]] """The target architecture for the Devbox. If unset, defaults to x86_64.""" + available_ports: Optional[Iterable[int]] + """[Deprecated] A list of ports to make available on the Devbox. + + This field is ignored. + """ + custom_cpu_cores: Optional[int] """Custom CPU cores. Must be 0.5, 1, or a multiple of 2. Max is 16.""" diff --git a/tests/api_resources/devboxes/test_disk_snapshots.py b/tests/api_resources/devboxes/test_disk_snapshots.py index 170e618ba..3d861089e 100644 --- a/tests/api_resources/devboxes/test_disk_snapshots.py +++ b/tests/api_resources/devboxes/test_disk_snapshots.py @@ -81,6 +81,7 @@ def test_method_list(self, client: Runloop) -> None: def test_method_list_with_all_params(self, client: Runloop) -> None: disk_snapshot = client.devboxes.disk_snapshots.list( devbox_id="devbox_id", + include_total_count=True, limit=0, metadata_key="metadata[key]", metadata_key_in="metadata[key][in]", @@ -354,6 +355,7 @@ async def test_method_list(self, async_client: AsyncRunloop) -> None: async def test_method_list_with_all_params(self, async_client: AsyncRunloop) -> None: disk_snapshot = await async_client.devboxes.disk_snapshots.list( devbox_id="devbox_id", + include_total_count=True, limit=0, metadata_key="metadata[key]", metadata_key_in="metadata[key][in]", diff --git a/tests/api_resources/scenarios/test_runs.py b/tests/api_resources/scenarios/test_runs.py index f3ac8eb88..429a815ee 100644 --- a/tests/api_resources/scenarios/test_runs.py +++ b/tests/api_resources/scenarios/test_runs.py @@ -73,6 +73,7 @@ def test_method_list(self, client: Runloop) -> None: def test_method_list_with_all_params(self, client: Runloop) -> None: run = client.scenarios.runs.list( benchmark_run_id="benchmark_run_id", + include_total_count=True, limit=0, name="name", scenario_id="scenario_id", @@ -324,6 +325,7 @@ async def test_method_list(self, async_client: AsyncRunloop) -> None: async def test_method_list_with_all_params(self, async_client: AsyncRunloop) -> None: run = await async_client.scenarios.runs.list( benchmark_run_id="benchmark_run_id", + include_total_count=True, limit=0, name="name", scenario_id="scenario_id", diff --git a/tests/api_resources/scenarios/test_scorers.py b/tests/api_resources/scenarios/test_scorers.py index 359e0dcc7..cd15e860d 100644 --- a/tests/api_resources/scenarios/test_scorers.py +++ b/tests/api_resources/scenarios/test_scorers.py @@ -149,6 +149,7 @@ def test_method_list(self, client: Runloop) -> None: @parametrize def test_method_list_with_all_params(self, client: Runloop) -> None: scorer = client.scenarios.scorers.list( + include_total_count=True, limit=0, starting_after="starting_after", ) @@ -306,6 +307,7 @@ async def test_method_list(self, async_client: AsyncRunloop) -> None: @parametrize async def test_method_list_with_all_params(self, async_client: AsyncRunloop) -> None: scorer = await async_client.scenarios.scorers.list( + include_total_count=True, limit=0, starting_after="starting_after", ) diff --git a/tests/api_resources/test_agents.py b/tests/api_resources/test_agents.py index 693eec250..a6304d70d 100644 --- a/tests/api_resources/test_agents.py +++ b/tests/api_resources/test_agents.py @@ -128,6 +128,7 @@ def test_method_list(self, client: Runloop) -> None: @parametrize def test_method_list_with_all_params(self, client: Runloop) -> None: agent = client.agents.list( + include_total_count=True, is_public=True, limit=0, name="name", @@ -273,6 +274,7 @@ async def test_method_list(self, async_client: AsyncRunloop) -> None: @parametrize async def test_method_list_with_all_params(self, async_client: AsyncRunloop) -> None: agent = await async_client.agents.list( + include_total_count=True, is_public=True, limit=0, name="name", diff --git a/tests/api_resources/test_benchmark_jobs.py b/tests/api_resources/test_benchmark_jobs.py index 461943458..9b577a0c2 100644 --- a/tests/api_resources/test_benchmark_jobs.py +++ b/tests/api_resources/test_benchmark_jobs.py @@ -102,6 +102,7 @@ def test_method_list(self, client: Runloop) -> None: @parametrize def test_method_list_with_all_params(self, client: Runloop) -> None: benchmark_job = client.benchmark_jobs.list( + include_total_count=True, limit=0, name="name", starting_after="starting_after", @@ -216,6 +217,7 @@ async def test_method_list(self, async_client: AsyncRunloop) -> None: @parametrize async def test_method_list_with_all_params(self, async_client: AsyncRunloop) -> None: benchmark_job = await async_client.benchmark_jobs.list( + include_total_count=True, limit=0, name="name", starting_after="starting_after", diff --git a/tests/api_resources/test_benchmark_runs.py b/tests/api_resources/test_benchmark_runs.py index 854a44574..5e8709aff 100644 --- a/tests/api_resources/test_benchmark_runs.py +++ b/tests/api_resources/test_benchmark_runs.py @@ -68,6 +68,7 @@ def test_method_list(self, client: Runloop) -> None: def test_method_list_with_all_params(self, client: Runloop) -> None: benchmark_run = client.benchmark_runs.list( benchmark_id="benchmark_id", + include_total_count=True, limit=0, name="name", starting_after="starting_after", @@ -181,6 +182,7 @@ def test_method_list_scenario_runs(self, client: Runloop) -> None: def test_method_list_scenario_runs_with_all_params(self, client: Runloop) -> None: benchmark_run = client.benchmark_runs.list_scenario_runs( id="id", + include_total_count=True, limit=0, starting_after="starting_after", state="running", @@ -271,6 +273,7 @@ async def test_method_list(self, async_client: AsyncRunloop) -> None: async def test_method_list_with_all_params(self, async_client: AsyncRunloop) -> None: benchmark_run = await async_client.benchmark_runs.list( benchmark_id="benchmark_id", + include_total_count=True, limit=0, name="name", starting_after="starting_after", @@ -384,6 +387,7 @@ async def test_method_list_scenario_runs(self, async_client: AsyncRunloop) -> No async def test_method_list_scenario_runs_with_all_params(self, async_client: AsyncRunloop) -> None: benchmark_run = await async_client.benchmark_runs.list_scenario_runs( id="id", + include_total_count=True, limit=0, starting_after="starting_after", state="running", diff --git a/tests/api_resources/test_benchmarks.py b/tests/api_resources/test_benchmarks.py index f25be7420..2e1909775 100644 --- a/tests/api_resources/test_benchmarks.py +++ b/tests/api_resources/test_benchmarks.py @@ -164,6 +164,7 @@ def test_method_list(self, client: Runloop) -> None: @parametrize def test_method_list_with_all_params(self, client: Runloop) -> None: benchmark = client.benchmarks.list( + include_total_count=True, limit=0, name="name", starting_after="starting_after", @@ -245,6 +246,7 @@ def test_method_list_public(self, client: Runloop) -> None: @parametrize def test_method_list_public_with_all_params(self, client: Runloop) -> None: benchmark = client.benchmarks.list_public( + include_total_count=True, limit=0, starting_after="starting_after", ) @@ -291,6 +293,7 @@ def test_method_start_run_with_all_params(self, client: Runloop) -> None: "on_idle": "shutdown", }, "architecture": "x86_64", + "available_ports": [0], "custom_cpu_cores": 0, "custom_disk_size": 0, "custom_gb_memory": 0, @@ -536,6 +539,7 @@ async def test_method_list(self, async_client: AsyncRunloop) -> None: @parametrize async def test_method_list_with_all_params(self, async_client: AsyncRunloop) -> None: benchmark = await async_client.benchmarks.list( + include_total_count=True, limit=0, name="name", starting_after="starting_after", @@ -617,6 +621,7 @@ async def test_method_list_public(self, async_client: AsyncRunloop) -> None: @parametrize async def test_method_list_public_with_all_params(self, async_client: AsyncRunloop) -> None: benchmark = await async_client.benchmarks.list_public( + include_total_count=True, limit=0, starting_after="starting_after", ) @@ -663,6 +668,7 @@ async def test_method_start_run_with_all_params(self, async_client: AsyncRunloop "on_idle": "shutdown", }, "architecture": "x86_64", + "available_ports": [0], "custom_cpu_cores": 0, "custom_disk_size": 0, "custom_gb_memory": 0, diff --git a/tests/api_resources/test_blueprints.py b/tests/api_resources/test_blueprints.py index 460fb9768..7ae5fada4 100644 --- a/tests/api_resources/test_blueprints.py +++ b/tests/api_resources/test_blueprints.py @@ -56,6 +56,7 @@ def test_method_create_with_all_params(self, client: Runloop) -> None: "on_idle": "shutdown", }, "architecture": "x86_64", + "available_ports": [0], "custom_cpu_cores": 0, "custom_disk_size": 0, "custom_gb_memory": 0, @@ -181,6 +182,7 @@ def test_method_list(self, client: Runloop) -> None: @parametrize def test_method_list_with_all_params(self, client: Runloop) -> None: blueprint = client.blueprints.list( + include_total_count=True, limit=0, name="name", starting_after="starting_after", @@ -269,6 +271,7 @@ def test_method_create_from_inspection_with_all_params(self, client: Runloop) -> "on_idle": "shutdown", }, "architecture": "x86_64", + "available_ports": [0], "custom_cpu_cores": 0, "custom_disk_size": 0, "custom_gb_memory": 0, @@ -323,6 +326,7 @@ def test_method_list_public(self, client: Runloop) -> None: @parametrize def test_method_list_public_with_all_params(self, client: Runloop) -> None: blueprint = client.blueprints.list_public( + include_total_count=True, limit=0, name="name", starting_after="starting_after", @@ -422,6 +426,7 @@ def test_method_preview_with_all_params(self, client: Runloop) -> None: "on_idle": "shutdown", }, "architecture": "x86_64", + "available_ports": [0], "custom_cpu_cores": 0, "custom_disk_size": 0, "custom_gb_memory": 0, @@ -519,6 +524,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncRunloop) - "on_idle": "shutdown", }, "architecture": "x86_64", + "available_ports": [0], "custom_cpu_cores": 0, "custom_disk_size": 0, "custom_gb_memory": 0, @@ -644,6 +650,7 @@ async def test_method_list(self, async_client: AsyncRunloop) -> None: @parametrize async def test_method_list_with_all_params(self, async_client: AsyncRunloop) -> None: blueprint = await async_client.blueprints.list( + include_total_count=True, limit=0, name="name", starting_after="starting_after", @@ -732,6 +739,7 @@ async def test_method_create_from_inspection_with_all_params(self, async_client: "on_idle": "shutdown", }, "architecture": "x86_64", + "available_ports": [0], "custom_cpu_cores": 0, "custom_disk_size": 0, "custom_gb_memory": 0, @@ -786,6 +794,7 @@ async def test_method_list_public(self, async_client: AsyncRunloop) -> None: @parametrize async def test_method_list_public_with_all_params(self, async_client: AsyncRunloop) -> None: blueprint = await async_client.blueprints.list_public( + include_total_count=True, limit=0, name="name", starting_after="starting_after", @@ -885,6 +894,7 @@ async def test_method_preview_with_all_params(self, async_client: AsyncRunloop) "on_idle": "shutdown", }, "architecture": "x86_64", + "available_ports": [0], "custom_cpu_cores": 0, "custom_disk_size": 0, "custom_gb_memory": 0, diff --git a/tests/api_resources/test_devboxes.py b/tests/api_resources/test_devboxes.py index 209900587..ab7a4b437 100644 --- a/tests/api_resources/test_devboxes.py +++ b/tests/api_resources/test_devboxes.py @@ -78,6 +78,7 @@ def test_method_create_with_all_params(self, client: Runloop) -> None: "on_idle": "shutdown", }, "architecture": "x86_64", + "available_ports": [0], "custom_cpu_cores": 0, "custom_disk_size": 0, "custom_gb_memory": 0, @@ -229,6 +230,7 @@ def test_method_list(self, client: Runloop) -> None: @parametrize def test_method_list_with_all_params(self, client: Runloop) -> None: devbox = client.devboxes.list( + include_total_count=True, limit=0, starting_after="starting_after", status="provisioning", @@ -652,6 +654,7 @@ def test_method_list_disk_snapshots(self, client: Runloop) -> None: def test_method_list_disk_snapshots_with_all_params(self, client: Runloop) -> None: devbox = client.devboxes.list_disk_snapshots( devbox_id="devbox_id", + include_total_count=True, limit=0, metadata_key="metadata[key]", metadata_key_in="metadata[key][in]", @@ -1698,6 +1701,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncRunloop) - "on_idle": "shutdown", }, "architecture": "x86_64", + "available_ports": [0], "custom_cpu_cores": 0, "custom_disk_size": 0, "custom_gb_memory": 0, @@ -1849,6 +1853,7 @@ async def test_method_list(self, async_client: AsyncRunloop) -> None: @parametrize async def test_method_list_with_all_params(self, async_client: AsyncRunloop) -> None: devbox = await async_client.devboxes.list( + include_total_count=True, limit=0, starting_after="starting_after", status="provisioning", @@ -2272,6 +2277,7 @@ async def test_method_list_disk_snapshots(self, async_client: AsyncRunloop) -> N async def test_method_list_disk_snapshots_with_all_params(self, async_client: AsyncRunloop) -> None: devbox = await async_client.devboxes.list_disk_snapshots( devbox_id="devbox_id", + include_total_count=True, limit=0, metadata_key="metadata[key]", metadata_key_in="metadata[key][in]", diff --git a/tests/api_resources/test_gateway_configs.py b/tests/api_resources/test_gateway_configs.py index 6265b9875..5a60422dd 100644 --- a/tests/api_resources/test_gateway_configs.py +++ b/tests/api_resources/test_gateway_configs.py @@ -169,6 +169,7 @@ def test_method_list(self, client: Runloop) -> None: def test_method_list_with_all_params(self, client: Runloop) -> None: gateway_config = client.gateway_configs.list( id="id", + include_total_count=True, limit=0, name="name", starting_after="starting_after", @@ -388,6 +389,7 @@ async def test_method_list(self, async_client: AsyncRunloop) -> None: async def test_method_list_with_all_params(self, async_client: AsyncRunloop) -> None: gateway_config = await async_client.gateway_configs.list( id="id", + include_total_count=True, limit=0, name="name", starting_after="starting_after", diff --git a/tests/api_resources/test_mcp_configs.py b/tests/api_resources/test_mcp_configs.py index 0d3135668..ea6ed337c 100644 --- a/tests/api_resources/test_mcp_configs.py +++ b/tests/api_resources/test_mcp_configs.py @@ -163,6 +163,7 @@ def test_method_list(self, client: Runloop) -> None: def test_method_list_with_all_params(self, client: Runloop) -> None: mcp_config = client.mcp_configs.list( id="id", + include_total_count=True, limit=0, name="name", starting_after="starting_after", @@ -376,6 +377,7 @@ async def test_method_list(self, async_client: AsyncRunloop) -> None: async def test_method_list_with_all_params(self, async_client: AsyncRunloop) -> None: mcp_config = await async_client.mcp_configs.list( id="id", + include_total_count=True, limit=0, name="name", starting_after="starting_after", diff --git a/tests/api_resources/test_network_policies.py b/tests/api_resources/test_network_policies.py index 6a1587cb9..e7b5fb038 100644 --- a/tests/api_resources/test_network_policies.py +++ b/tests/api_resources/test_network_policies.py @@ -163,6 +163,7 @@ def test_method_list(self, client: Runloop) -> None: def test_method_list_with_all_params(self, client: Runloop) -> None: network_policy = client.network_policies.list( id="id", + include_total_count=True, limit=0, name="name", starting_after="starting_after", @@ -376,6 +377,7 @@ async def test_method_list(self, async_client: AsyncRunloop) -> None: async def test_method_list_with_all_params(self, async_client: AsyncRunloop) -> None: network_policy = await async_client.network_policies.list( id="id", + include_total_count=True, limit=0, name="name", starting_after="starting_after", diff --git a/tests/api_resources/test_objects.py b/tests/api_resources/test_objects.py index 2593e03bf..ca5d06651 100644 --- a/tests/api_resources/test_objects.py +++ b/tests/api_resources/test_objects.py @@ -112,6 +112,7 @@ def test_method_list(self, client: Runloop) -> None: def test_method_list_with_all_params(self, client: Runloop) -> None: object_ = client.objects.list( content_type="unspecified", + include_total_count=True, limit=0, name="name", search="search", @@ -271,6 +272,7 @@ def test_method_list_public(self, client: Runloop) -> None: def test_method_list_public_with_all_params(self, client: Runloop) -> None: object_ = client.objects.list_public( content_type="unspecified", + include_total_count=True, limit=0, name="name", search="search", @@ -396,6 +398,7 @@ async def test_method_list(self, async_client: AsyncRunloop) -> None: async def test_method_list_with_all_params(self, async_client: AsyncRunloop) -> None: object_ = await async_client.objects.list( content_type="unspecified", + include_total_count=True, limit=0, name="name", search="search", @@ -555,6 +558,7 @@ async def test_method_list_public(self, async_client: AsyncRunloop) -> None: async def test_method_list_public_with_all_params(self, async_client: AsyncRunloop) -> None: object_ = await async_client.objects.list_public( content_type="unspecified", + include_total_count=True, limit=0, name="name", search="search", diff --git a/tests/api_resources/test_repositories.py b/tests/api_resources/test_repositories.py index 282688d80..20fa91e6d 100644 --- a/tests/api_resources/test_repositories.py +++ b/tests/api_resources/test_repositories.py @@ -114,6 +114,7 @@ def test_method_list(self, client: Runloop) -> None: @parametrize def test_method_list_with_all_params(self, client: Runloop) -> None: repository = client.repositories.list( + include_total_count=True, limit=0, name="name", owner="owner", @@ -451,6 +452,7 @@ async def test_method_list(self, async_client: AsyncRunloop) -> None: @parametrize async def test_method_list_with_all_params(self, async_client: AsyncRunloop) -> None: repository = await async_client.repositories.list( + include_total_count=True, limit=0, name="name", owner="owner", diff --git a/tests/api_resources/test_scenarios.py b/tests/api_resources/test_scenarios.py index e212f6e81..8182687c0 100644 --- a/tests/api_resources/test_scenarios.py +++ b/tests/api_resources/test_scenarios.py @@ -72,6 +72,7 @@ def test_method_create_with_all_params(self, client: Runloop) -> None: "on_idle": "shutdown", }, "architecture": "x86_64", + "available_ports": [0], "custom_cpu_cores": 0, "custom_disk_size": 0, "custom_gb_memory": 0, @@ -206,6 +207,7 @@ def test_method_update_with_all_params(self, client: Runloop) -> None: "on_idle": "shutdown", }, "architecture": "x86_64", + "available_ports": [0], "custom_cpu_cores": 0, "custom_disk_size": 0, "custom_gb_memory": 0, @@ -290,6 +292,7 @@ def test_method_list(self, client: Runloop) -> None: def test_method_list_with_all_params(self, client: Runloop) -> None: scenario = client.scenarios.list( benchmark_id="benchmark_id", + include_total_count=True, limit=0, name="name", starting_after="starting_after", @@ -363,6 +366,7 @@ def test_method_list_public(self, client: Runloop) -> None: @parametrize def test_method_list_public_with_all_params(self, client: Runloop) -> None: scenario = client.scenarios.list_public( + include_total_count=True, limit=0, name="name", starting_after="starting_after", @@ -411,6 +415,7 @@ def test_method_start_run_with_all_params(self, client: Runloop) -> None: "on_idle": "shutdown", }, "architecture": "x86_64", + "available_ports": [0], "custom_cpu_cores": 0, "custom_disk_size": 0, "custom_gb_memory": 0, @@ -518,6 +523,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncRunloop) - "on_idle": "shutdown", }, "architecture": "x86_64", + "available_ports": [0], "custom_cpu_cores": 0, "custom_disk_size": 0, "custom_gb_memory": 0, @@ -652,6 +658,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncRunloop) - "on_idle": "shutdown", }, "architecture": "x86_64", + "available_ports": [0], "custom_cpu_cores": 0, "custom_disk_size": 0, "custom_gb_memory": 0, @@ -736,6 +743,7 @@ async def test_method_list(self, async_client: AsyncRunloop) -> None: async def test_method_list_with_all_params(self, async_client: AsyncRunloop) -> None: scenario = await async_client.scenarios.list( benchmark_id="benchmark_id", + include_total_count=True, limit=0, name="name", starting_after="starting_after", @@ -809,6 +817,7 @@ async def test_method_list_public(self, async_client: AsyncRunloop) -> None: @parametrize async def test_method_list_public_with_all_params(self, async_client: AsyncRunloop) -> None: scenario = await async_client.scenarios.list_public( + include_total_count=True, limit=0, name="name", starting_after="starting_after", @@ -857,6 +866,7 @@ async def test_method_start_run_with_all_params(self, async_client: AsyncRunloop "on_idle": "shutdown", }, "architecture": "x86_64", + "available_ports": [0], "custom_cpu_cores": 0, "custom_disk_size": 0, "custom_gb_memory": 0, From 66cb637779eaea2cacc4b1a06ac3a33f2358b0ee Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 21 Mar 2026 02:17:57 +0000 Subject: [PATCH 06/13] chore(tests): bump steady to v0.19.4 --- scripts/mock | 6 +++--- scripts/test | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/mock b/scripts/mock index 38201de83..e1c19e88f 100755 --- a/scripts/mock +++ b/scripts/mock @@ -22,9 +22,9 @@ echo "==> Starting mock server with URL ${URL}" # Run steady mock on the given spec if [ "$1" == "--daemon" ]; then # Pre-install the package so the download doesn't eat into the startup timeout - npm exec --package=@stdy/cli@0.19.3 -- steady --version + npm exec --package=@stdy/cli@0.19.4 -- steady --version - npm exec --package=@stdy/cli@0.19.3 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=comma --validator-query-object-format=brackets "$URL" &> .stdy.log & + npm exec --package=@stdy/cli@0.19.4 -- steady --host 127.0.0.1 -p 4010 --validator-form-array-format=comma --validator-query-array-format=comma --validator-form-object-format=brackets --validator-query-object-format=brackets "$URL" &> .stdy.log & # Wait for server to come online via health endpoint (max 30s) echo -n "Waiting for server" @@ -48,5 +48,5 @@ if [ "$1" == "--daemon" ]; then echo else - npm exec --package=@stdy/cli@0.19.3 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=comma --validator-query-object-format=brackets "$URL" + npm exec --package=@stdy/cli@0.19.4 -- steady --host 127.0.0.1 -p 4010 --validator-form-array-format=comma --validator-query-array-format=comma --validator-form-object-format=brackets --validator-query-object-format=brackets "$URL" fi diff --git a/scripts/test b/scripts/test index 59bf28128..04968ea03 100755 --- a/scripts/test +++ b/scripts/test @@ -43,7 +43,7 @@ elif ! steady_is_running ; then echo -e "To run the server, pass in the path or url of your OpenAPI" echo -e "spec to the steady command:" echo - echo -e " \$ ${YELLOW}npm exec --package=@stdy/cli@0.19.3 -- steady path/to/your.openapi.yml --host 127.0.0.1 -p 4010 --validator-query-array-format=comma --validator-query-object-format=brackets${NC}" + echo -e " \$ ${YELLOW}npm exec --package=@stdy/cli@0.19.4 -- steady path/to/your.openapi.yml --host 127.0.0.1 -p 4010 --validator-form-array-format=comma --validator-query-array-format=comma --validator-form-object-format=brackets --validator-query-object-format=brackets${NC}" echo exit 1 From 601c93afffb8379770ac895b89235903c8348b00 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 21 Mar 2026 02:24:03 +0000 Subject: [PATCH 07/13] chore(tests): bump steady to v0.19.5 --- scripts/mock | 6 +++--- scripts/test | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/mock b/scripts/mock index e1c19e88f..ab814d383 100755 --- a/scripts/mock +++ b/scripts/mock @@ -22,9 +22,9 @@ echo "==> Starting mock server with URL ${URL}" # Run steady mock on the given spec if [ "$1" == "--daemon" ]; then # Pre-install the package so the download doesn't eat into the startup timeout - npm exec --package=@stdy/cli@0.19.4 -- steady --version + npm exec --package=@stdy/cli@0.19.5 -- steady --version - npm exec --package=@stdy/cli@0.19.4 -- steady --host 127.0.0.1 -p 4010 --validator-form-array-format=comma --validator-query-array-format=comma --validator-form-object-format=brackets --validator-query-object-format=brackets "$URL" &> .stdy.log & + npm exec --package=@stdy/cli@0.19.5 -- steady --host 127.0.0.1 -p 4010 --validator-form-array-format=comma --validator-query-array-format=comma --validator-form-object-format=brackets --validator-query-object-format=brackets "$URL" &> .stdy.log & # Wait for server to come online via health endpoint (max 30s) echo -n "Waiting for server" @@ -48,5 +48,5 @@ if [ "$1" == "--daemon" ]; then echo else - npm exec --package=@stdy/cli@0.19.4 -- steady --host 127.0.0.1 -p 4010 --validator-form-array-format=comma --validator-query-array-format=comma --validator-form-object-format=brackets --validator-query-object-format=brackets "$URL" + npm exec --package=@stdy/cli@0.19.5 -- steady --host 127.0.0.1 -p 4010 --validator-form-array-format=comma --validator-query-array-format=comma --validator-form-object-format=brackets --validator-query-object-format=brackets "$URL" fi diff --git a/scripts/test b/scripts/test index 04968ea03..2fee3cb82 100755 --- a/scripts/test +++ b/scripts/test @@ -43,7 +43,7 @@ elif ! steady_is_running ; then echo -e "To run the server, pass in the path or url of your OpenAPI" echo -e "spec to the steady command:" echo - echo -e " \$ ${YELLOW}npm exec --package=@stdy/cli@0.19.4 -- steady path/to/your.openapi.yml --host 127.0.0.1 -p 4010 --validator-form-array-format=comma --validator-query-array-format=comma --validator-form-object-format=brackets --validator-query-object-format=brackets${NC}" + echo -e " \$ ${YELLOW}npm exec --package=@stdy/cli@0.19.5 -- steady path/to/your.openapi.yml --host 127.0.0.1 -p 4010 --validator-form-array-format=comma --validator-query-array-format=comma --validator-form-object-format=brackets --validator-query-object-format=brackets${NC}" echo exit 1 From 96bd6fdc7b767371b21f5753729f01020f9e78ec Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Mar 2026 02:20:35 +0000 Subject: [PATCH 08/13] chore(internal): update gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2f716b579..90668e988 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .prism.log +.stdy.log _dev __pycache__ From 95b2ffc5d487f6595ec1dc73b6a34696c309f67d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Mar 2026 02:26:58 +0000 Subject: [PATCH 09/13] chore(tests): bump steady to v0.19.6 --- scripts/mock | 6 +++--- scripts/test | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/mock b/scripts/mock index ab814d383..b319bdfbb 100755 --- a/scripts/mock +++ b/scripts/mock @@ -22,9 +22,9 @@ echo "==> Starting mock server with URL ${URL}" # Run steady mock on the given spec if [ "$1" == "--daemon" ]; then # Pre-install the package so the download doesn't eat into the startup timeout - npm exec --package=@stdy/cli@0.19.5 -- steady --version + npm exec --package=@stdy/cli@0.19.6 -- steady --version - npm exec --package=@stdy/cli@0.19.5 -- steady --host 127.0.0.1 -p 4010 --validator-form-array-format=comma --validator-query-array-format=comma --validator-form-object-format=brackets --validator-query-object-format=brackets "$URL" &> .stdy.log & + npm exec --package=@stdy/cli@0.19.6 -- steady --host 127.0.0.1 -p 4010 --validator-form-array-format=comma --validator-query-array-format=comma --validator-form-object-format=brackets --validator-query-object-format=brackets "$URL" &> .stdy.log & # Wait for server to come online via health endpoint (max 30s) echo -n "Waiting for server" @@ -48,5 +48,5 @@ if [ "$1" == "--daemon" ]; then echo else - npm exec --package=@stdy/cli@0.19.5 -- steady --host 127.0.0.1 -p 4010 --validator-form-array-format=comma --validator-query-array-format=comma --validator-form-object-format=brackets --validator-query-object-format=brackets "$URL" + npm exec --package=@stdy/cli@0.19.6 -- steady --host 127.0.0.1 -p 4010 --validator-form-array-format=comma --validator-query-array-format=comma --validator-form-object-format=brackets --validator-query-object-format=brackets "$URL" fi diff --git a/scripts/test b/scripts/test index 2fee3cb82..bc3f79742 100755 --- a/scripts/test +++ b/scripts/test @@ -43,7 +43,7 @@ elif ! steady_is_running ; then echo -e "To run the server, pass in the path or url of your OpenAPI" echo -e "spec to the steady command:" echo - echo -e " \$ ${YELLOW}npm exec --package=@stdy/cli@0.19.5 -- steady path/to/your.openapi.yml --host 127.0.0.1 -p 4010 --validator-form-array-format=comma --validator-query-array-format=comma --validator-form-object-format=brackets --validator-query-object-format=brackets${NC}" + echo -e " \$ ${YELLOW}npm exec --package=@stdy/cli@0.19.6 -- steady path/to/your.openapi.yml --host 127.0.0.1 -p 4010 --validator-form-array-format=comma --validator-query-array-format=comma --validator-form-object-format=brackets --validator-query-object-format=brackets${NC}" echo exit 1 From 6cff97ee43a2f2c906833e9900841e34d478d176 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Mar 2026 20:22:09 +0000 Subject: [PATCH 10/13] fix(tunnels): allow tunnel removal (#8257) --- .stats.yml | 4 +- api.md | 2 +- .../resources/devboxes/devboxes.py | 51 +++-------- src/runloop_api_client/sdk/_types.py | 3 +- src/runloop_api_client/sdk/async_devbox.py | 2 +- src/runloop_api_client/sdk/devbox.py | 2 +- src/runloop_api_client/types/__init__.py | 1 - .../types/devbox_remove_tunnel_params.py | 12 --- tests/api_resources/test_devboxes.py | 86 ++++++++----------- tests/sdk/async_devbox/test_interfaces.py | 1 - tests/sdk/devbox/test_interfaces.py | 2 - 11 files changed, 52 insertions(+), 114 deletions(-) delete mode 100644 src/runloop_api_client/types/devbox_remove_tunnel_params.py diff --git a/.stats.yml b/.stats.yml index 96414b617..0f058b7eb 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 117 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-3d441e9c2074aef7bd1e87b2ebfbe5c25e340084dbdfeed8a2e4dc9384561e3b.yml -openapi_spec_hash: 250bfdf2c2270ade53c058419e23c5dc +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-64b2ded3d3cc58131c7ebf36c411f43c3d8dabae0b5b1edd65dd7dab307c54a1.yml +openapi_spec_hash: 80fabb5e89a61299203113505a894dc2 config_hash: de99cfce88e2d1f02246dc6c2f43bc6c diff --git a/api.md b/api.md index 4a2dd3da3..f005bdad7 100644 --- a/api.md +++ b/api.md @@ -155,7 +155,7 @@ Methods: - client.devboxes.keep_alive(id) -> object - client.devboxes.list_disk_snapshots(\*\*params) -> SyncDiskSnapshotsCursorIDPage[DevboxSnapshotView] - client.devboxes.read_file_contents(id, \*\*params) -> str -- client.devboxes.remove_tunnel(id, \*\*params) -> object +- client.devboxes.remove_tunnel(id) -> object - client.devboxes.resume(id) -> DevboxView - client.devboxes.retrieve_resource_usage(id) -> DevboxResourceUsageView - client.devboxes.shutdown(id, \*\*params) -> DevboxView diff --git a/src/runloop_api_client/resources/devboxes/devboxes.py b/src/runloop_api_client/resources/devboxes/devboxes.py index 78c5bd066..1f016f2eb 100644 --- a/src/runloop_api_client/resources/devboxes/devboxes.py +++ b/src/runloop_api_client/resources/devboxes/devboxes.py @@ -30,7 +30,6 @@ devbox_download_file_params, devbox_enable_tunnel_params, devbox_execute_async_params, - devbox_remove_tunnel_params, devbox_snapshot_disk_params, devbox_wait_for_command_params, devbox_read_file_contents_params, @@ -1257,14 +1256,10 @@ def read_file_contents( cast_to=str, ) - @typing_extensions.deprecated( - "remove_tunnel is deprecated; V2 tunnels cannot be removed and close on devbox shutdown." - ) def remove_tunnel( self, id: str, *, - port: int, # 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, @@ -1273,14 +1268,10 @@ def remove_tunnel( timeout: float | httpx.Timeout | None | NotGiven = not_given, idempotency_key: str | None = None, ) -> object: - """[Deprecated] V2 tunnels cannot be removed and close on devbox shutdown. - - This endpoint - removes a legacy tunnel. + """ + Remove an existing V2 tunnel from the Devbox. Args: - port: Devbox port that tunnel will expose. - extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -1295,7 +1286,6 @@ def remove_tunnel( raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( path_template("/v1/devboxes/{id}/remove_tunnel", id=id), - body=maybe_transform({"port": port}, devbox_remove_tunnel_params.DevboxRemoveTunnelParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -2901,14 +2891,10 @@ async def read_file_contents( cast_to=str, ) - @typing_extensions.deprecated( - "remove_tunnel is deprecated; V2 tunnels cannot be removed and close on devbox shutdown." - ) async def remove_tunnel( self, id: str, *, - port: int, # 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, @@ -2917,14 +2903,10 @@ async def remove_tunnel( timeout: float | httpx.Timeout | None | NotGiven = not_given, idempotency_key: str | None = None, ) -> object: - """[Deprecated] V2 tunnels cannot be removed and close on devbox shutdown. - - This endpoint - removes a legacy tunnel. + """ + Remove an existing V2 tunnel from the Devbox. Args: - port: Devbox port that tunnel will expose. - extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -2939,7 +2921,6 @@ async def remove_tunnel( raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( path_template("/v1/devboxes/{id}/remove_tunnel", id=id), - body=await async_maybe_transform({"port": port}, devbox_remove_tunnel_params.DevboxRemoveTunnelParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -3481,10 +3462,8 @@ def __init__(self, devboxes: DevboxesResource) -> None: self.read_file_contents = to_raw_response_wrapper( devboxes.read_file_contents, ) - self.remove_tunnel = ( # pyright: ignore[reportDeprecated] - to_raw_response_wrapper( - devboxes.remove_tunnel, # pyright: ignore[reportDeprecated], - ) + self.remove_tunnel = to_raw_response_wrapper( + devboxes.remove_tunnel, ) self.resume = to_raw_response_wrapper( devboxes.resume, @@ -3584,10 +3563,8 @@ def __init__(self, devboxes: AsyncDevboxesResource) -> None: self.read_file_contents = async_to_raw_response_wrapper( devboxes.read_file_contents, ) - self.remove_tunnel = ( # pyright: ignore[reportDeprecated] - async_to_raw_response_wrapper( - devboxes.remove_tunnel, # pyright: ignore[reportDeprecated], - ) + self.remove_tunnel = async_to_raw_response_wrapper( + devboxes.remove_tunnel, ) self.resume = async_to_raw_response_wrapper( devboxes.resume, @@ -3687,10 +3664,8 @@ def __init__(self, devboxes: DevboxesResource) -> None: self.read_file_contents = to_streamed_response_wrapper( devboxes.read_file_contents, ) - self.remove_tunnel = ( # pyright: ignore[reportDeprecated] - to_streamed_response_wrapper( - devboxes.remove_tunnel, # pyright: ignore[reportDeprecated], - ) + self.remove_tunnel = to_streamed_response_wrapper( + devboxes.remove_tunnel, ) self.resume = to_streamed_response_wrapper( devboxes.resume, @@ -3790,10 +3765,8 @@ def __init__(self, devboxes: AsyncDevboxesResource) -> None: self.read_file_contents = async_to_streamed_response_wrapper( devboxes.read_file_contents, ) - self.remove_tunnel = ( # pyright: ignore[reportDeprecated] - async_to_streamed_response_wrapper( - devboxes.remove_tunnel, # pyright: ignore[reportDeprecated], - ) + self.remove_tunnel = async_to_streamed_response_wrapper( + devboxes.remove_tunnel, ) self.resume = async_to_streamed_response_wrapper( devboxes.resume, diff --git a/src/runloop_api_client/sdk/_types.py b/src/runloop_api_client/sdk/_types.py index c66efebe3..82e4bbb9a 100644 --- a/src/runloop_api_client/sdk/_types.py +++ b/src/runloop_api_client/sdk/_types.py @@ -26,7 +26,6 @@ NetworkPolicyListParams, DevboxDownloadFileParams, DevboxEnableTunnelParams, - DevboxRemoveTunnelParams, DevboxSnapshotDiskParams, GatewayConfigCreateParams, GatewayConfigUpdateParams, @@ -127,7 +126,7 @@ class SDKDevboxEnableTunnelParams(DevboxEnableTunnelParams, LongRequestOptions): pass -class SDKDevboxRemoveTunnelParams(DevboxRemoveTunnelParams, LongRequestOptions): +class SDKDevboxRemoveTunnelParams(LongRequestOptions): pass diff --git a/src/runloop_api_client/sdk/async_devbox.py b/src/runloop_api_client/sdk/async_devbox.py index 9a97cb5f3..cdedcb5db 100644 --- a/src/runloop_api_client/sdk/async_devbox.py +++ b/src/runloop_api_client/sdk/async_devbox.py @@ -840,7 +840,7 @@ async def remove_tunnel( :rtype: object Example: - >>> await devbox.net.remove_tunnel(port=8080) + >>> await devbox.net.remove_tunnel() """ warnings.warn( "remove_tunnel is deprecated; V2 tunnels cannot be removed and close on devbox shutdown.", diff --git a/src/runloop_api_client/sdk/devbox.py b/src/runloop_api_client/sdk/devbox.py index ac1743a6b..aec345715 100644 --- a/src/runloop_api_client/sdk/devbox.py +++ b/src/runloop_api_client/sdk/devbox.py @@ -843,7 +843,7 @@ def remove_tunnel( :rtype: object Example: - >>> devbox.net.remove_tunnel(port=8080) + >>> devbox.net.remove_tunnel() """ warnings.warn( "remove_tunnel is deprecated; V2 tunnels cannot be removed and close on devbox shutdown.", diff --git a/src/runloop_api_client/types/__init__.py b/src/runloop_api_client/types/__init__.py index 693d5c6f8..cf0d86cab 100644 --- a/src/runloop_api_client/types/__init__.py +++ b/src/runloop_api_client/types/__init__.py @@ -99,7 +99,6 @@ from .devbox_download_file_params import DevboxDownloadFileParams as DevboxDownloadFileParams from .devbox_enable_tunnel_params import DevboxEnableTunnelParams as DevboxEnableTunnelParams from .devbox_execute_async_params import DevboxExecuteAsyncParams as DevboxExecuteAsyncParams -from .devbox_remove_tunnel_params import DevboxRemoveTunnelParams as DevboxRemoveTunnelParams from .devbox_snapshot_disk_params import DevboxSnapshotDiskParams as DevboxSnapshotDiskParams from .scenario_list_public_params import ScenarioListPublicParams as ScenarioListPublicParams from .benchmark_definitions_params import BenchmarkDefinitionsParams as BenchmarkDefinitionsParams diff --git a/src/runloop_api_client/types/devbox_remove_tunnel_params.py b/src/runloop_api_client/types/devbox_remove_tunnel_params.py deleted file mode 100644 index eb58e2702..000000000 --- a/src/runloop_api_client/types/devbox_remove_tunnel_params.py +++ /dev/null @@ -1,12 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Required, TypedDict - -__all__ = ["DevboxRemoveTunnelParams"] - - -class DevboxRemoveTunnelParams(TypedDict, total=False): - port: Required[int] - """Devbox port that tunnel will expose.""" diff --git a/tests/api_resources/test_devboxes.py b/tests/api_resources/test_devboxes.py index ab7a4b437..ab78cd157 100644 --- a/tests/api_resources/test_devboxes.py +++ b/tests/api_resources/test_devboxes.py @@ -727,21 +727,16 @@ def test_path_params_read_file_contents(self, client: Runloop) -> None: @parametrize def test_method_remove_tunnel(self, client: Runloop) -> None: - with pytest.warns(DeprecationWarning): - devbox = client.devboxes.remove_tunnel( - id="id", - port=0, - ) - + devbox = client.devboxes.remove_tunnel( + "id", + ) assert_matches_type(object, devbox, path=["response"]) @parametrize def test_raw_response_remove_tunnel(self, client: Runloop) -> None: - with pytest.warns(DeprecationWarning): - response = client.devboxes.with_raw_response.remove_tunnel( - id="id", - port=0, - ) + response = client.devboxes.with_raw_response.remove_tunnel( + "id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -750,27 +745,23 @@ def test_raw_response_remove_tunnel(self, client: Runloop) -> None: @parametrize def test_streaming_response_remove_tunnel(self, client: Runloop) -> None: - with pytest.warns(DeprecationWarning): - with client.devboxes.with_streaming_response.remove_tunnel( - id="id", - port=0, - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with client.devboxes.with_streaming_response.remove_tunnel( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - devbox = response.parse() - assert_matches_type(object, devbox, path=["response"]) + devbox = response.parse() + assert_matches_type(object, devbox, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize def test_path_params_remove_tunnel(self, client: Runloop) -> None: - with pytest.warns(DeprecationWarning): - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.devboxes.with_raw_response.remove_tunnel( - id="", - port=0, - ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.devboxes.with_raw_response.remove_tunnel( + "", + ) @parametrize def test_method_resume(self, client: Runloop) -> None: @@ -2350,21 +2341,16 @@ async def test_path_params_read_file_contents(self, async_client: AsyncRunloop) @parametrize async def test_method_remove_tunnel(self, async_client: AsyncRunloop) -> None: - with pytest.warns(DeprecationWarning): - devbox = await async_client.devboxes.remove_tunnel( - id="id", - port=0, - ) - + devbox = await async_client.devboxes.remove_tunnel( + "id", + ) assert_matches_type(object, devbox, path=["response"]) @parametrize async def test_raw_response_remove_tunnel(self, async_client: AsyncRunloop) -> None: - with pytest.warns(DeprecationWarning): - response = await async_client.devboxes.with_raw_response.remove_tunnel( - id="id", - port=0, - ) + response = await async_client.devboxes.with_raw_response.remove_tunnel( + "id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -2373,27 +2359,23 @@ async def test_raw_response_remove_tunnel(self, async_client: AsyncRunloop) -> N @parametrize async def test_streaming_response_remove_tunnel(self, async_client: AsyncRunloop) -> None: - with pytest.warns(DeprecationWarning): - async with async_client.devboxes.with_streaming_response.remove_tunnel( - id="id", - port=0, - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + async with async_client.devboxes.with_streaming_response.remove_tunnel( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - devbox = await response.parse() - assert_matches_type(object, devbox, path=["response"]) + devbox = await response.parse() + assert_matches_type(object, devbox, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize async def test_path_params_remove_tunnel(self, async_client: AsyncRunloop) -> None: - with pytest.warns(DeprecationWarning): - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.devboxes.with_raw_response.remove_tunnel( - id="", - port=0, - ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.devboxes.with_raw_response.remove_tunnel( + "", + ) @parametrize async def test_method_resume(self, async_client: AsyncRunloop) -> None: diff --git a/tests/sdk/async_devbox/test_interfaces.py b/tests/sdk/async_devbox/test_interfaces.py index 7e766ea06..79376132e 100644 --- a/tests/sdk/async_devbox/test_interfaces.py +++ b/tests/sdk/async_devbox/test_interfaces.py @@ -186,7 +186,6 @@ async def test_remove_tunnel(self, mock_async_client: AsyncMock) -> None: devbox = AsyncDevbox(mock_async_client, "dbx_123") with pytest.warns(DeprecationWarning, match="remove_tunnel is deprecated"): result = await devbox.net.remove_tunnel( - port=8080, extra_headers={"X-Custom": "value"}, extra_query={"param": "value"}, extra_body={"key": "value"}, diff --git a/tests/sdk/devbox/test_interfaces.py b/tests/sdk/devbox/test_interfaces.py index 401849f28..7d0c1d567 100644 --- a/tests/sdk/devbox/test_interfaces.py +++ b/tests/sdk/devbox/test_interfaces.py @@ -287,7 +287,6 @@ def test_remove_tunnel(self, mock_client: Mock) -> None: devbox = Devbox(mock_client, "dbx_123") with pytest.warns(DeprecationWarning, match="remove_tunnel is deprecated"): result = devbox.net.remove_tunnel( - port=8080, extra_headers={"X-Custom": "value"}, extra_query={"param": "value"}, extra_body={"key": "value"}, @@ -298,7 +297,6 @@ def test_remove_tunnel(self, mock_client: Mock) -> None: assert result is not None # Verify return value is propagated mock_client.devboxes.remove_tunnel.assert_called_once_with( "dbx_123", - port=8080, extra_headers={"X-Custom": "value"}, extra_query={"param": "value"}, extra_body={"key": "value"}, From 618f1bb48ff7757e5285cddd083f49ccbbeeed3c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Mar 2026 21:04:20 +0000 Subject: [PATCH 11/13] fix: add AxonEventView schema to OpenAPI spec for SSE subscribe endpoint (#8274) --- .stats.yml | 8 +- api.md | 23 + src/runloop_api_client/_client.py | 38 ++ src/runloop_api_client/resources/__init__.py | 14 + src/runloop_api_client/resources/axons.py | 497 ++++++++++++++++++ src/runloop_api_client/types/__init__.py | 5 + .../types/axon_event_view.py | 30 ++ .../types/axon_list_view.py | 13 + .../types/axon_publish_params.py | 21 + src/runloop_api_client/types/axon_view.py | 13 + .../types/publish_result_view.py | 13 + tests/api_resources/test_axons.py | 382 ++++++++++++++ 12 files changed, 1053 insertions(+), 4 deletions(-) create mode 100644 src/runloop_api_client/resources/axons.py create mode 100644 src/runloop_api_client/types/axon_event_view.py create mode 100644 src/runloop_api_client/types/axon_list_view.py create mode 100644 src/runloop_api_client/types/axon_publish_params.py create mode 100644 src/runloop_api_client/types/axon_view.py create mode 100644 src/runloop_api_client/types/publish_result_view.py create mode 100644 tests/api_resources/test_axons.py diff --git a/.stats.yml b/.stats.yml index 0f058b7eb..d85c18351 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 117 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-64b2ded3d3cc58131c7ebf36c411f43c3d8dabae0b5b1edd65dd7dab307c54a1.yml -openapi_spec_hash: 80fabb5e89a61299203113505a894dc2 -config_hash: de99cfce88e2d1f02246dc6c2f43bc6c +configured_endpoints: 122 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-82e6ea2fc76e43ff397ca1d9997693307a006ef30ec1a001c0283319a3e2eb3b.yml +openapi_spec_hash: 69cf8a2d13bda298f2b588bd3ba1e562 +config_hash: c422b761c745873bce8fa5ccf03b7b98 diff --git a/api.md b/api.md index f005bdad7..ccda6b5d9 100644 --- a/api.md +++ b/api.md @@ -87,6 +87,29 @@ Methods: - client.agents.retrieve(id) -> AgentView - client.agents.list(\*\*params) -> SyncAgentsCursorIDPage[AgentView] +# Axons + +Types: + +```python +from runloop_api_client.types import ( + AxonCreateParams, + AxonEventView, + AxonListView, + AxonView, + PublishParams, + PublishResultView, +) +``` + +Methods: + +- client.axons.create() -> AxonView +- client.axons.retrieve(id) -> AxonView +- client.axons.list() -> AxonListView +- client.axons.publish(id, \*\*params) -> PublishResultView +- client.axons.subscribe_sse(id) -> AxonEventView + # Blueprints Types: diff --git a/src/runloop_api_client/_client.py b/src/runloop_api_client/_client.py index 0f626950b..3b5ab20db 100644 --- a/src/runloop_api_client/_client.py +++ b/src/runloop_api_client/_client.py @@ -32,6 +32,7 @@ if TYPE_CHECKING: from .resources import ( + axons, agents, objects, secrets, @@ -46,6 +47,7 @@ gateway_configs, network_policies, ) + from .resources.axons import AxonsResource, AsyncAxonsResource from .resources.agents import AgentsResource, AsyncAgentsResource from .resources.objects import ObjectsResource, AsyncObjectsResource from .resources.secrets import SecretsResource, AsyncSecretsResource @@ -144,6 +146,12 @@ def agents(self) -> AgentsResource: return AgentsResource(self) + @cached_property + def axons(self) -> AxonsResource: + from .resources.axons import AxonsResource + + return AxonsResource(self) + @cached_property def blueprints(self) -> BlueprintsResource: from .resources.blueprints import BlueprintsResource @@ -392,6 +400,12 @@ def agents(self) -> AsyncAgentsResource: return AsyncAgentsResource(self) + @cached_property + def axons(self) -> AsyncAxonsResource: + from .resources.axons import AsyncAxonsResource + + return AsyncAxonsResource(self) + @cached_property def blueprints(self) -> AsyncBlueprintsResource: from .resources.blueprints import AsyncBlueprintsResource @@ -589,6 +603,12 @@ def agents(self) -> agents.AgentsResourceWithRawResponse: return AgentsResourceWithRawResponse(self._client.agents) + @cached_property + def axons(self) -> axons.AxonsResourceWithRawResponse: + from .resources.axons import AxonsResourceWithRawResponse + + return AxonsResourceWithRawResponse(self._client.axons) + @cached_property def blueprints(self) -> blueprints.BlueprintsResourceWithRawResponse: from .resources.blueprints import BlueprintsResourceWithRawResponse @@ -674,6 +694,12 @@ def agents(self) -> agents.AsyncAgentsResourceWithRawResponse: return AsyncAgentsResourceWithRawResponse(self._client.agents) + @cached_property + def axons(self) -> axons.AsyncAxonsResourceWithRawResponse: + from .resources.axons import AsyncAxonsResourceWithRawResponse + + return AsyncAxonsResourceWithRawResponse(self._client.axons) + @cached_property def blueprints(self) -> blueprints.AsyncBlueprintsResourceWithRawResponse: from .resources.blueprints import AsyncBlueprintsResourceWithRawResponse @@ -759,6 +785,12 @@ def agents(self) -> agents.AgentsResourceWithStreamingResponse: return AgentsResourceWithStreamingResponse(self._client.agents) + @cached_property + def axons(self) -> axons.AxonsResourceWithStreamingResponse: + from .resources.axons import AxonsResourceWithStreamingResponse + + return AxonsResourceWithStreamingResponse(self._client.axons) + @cached_property def blueprints(self) -> blueprints.BlueprintsResourceWithStreamingResponse: from .resources.blueprints import BlueprintsResourceWithStreamingResponse @@ -844,6 +876,12 @@ def agents(self) -> agents.AsyncAgentsResourceWithStreamingResponse: return AsyncAgentsResourceWithStreamingResponse(self._client.agents) + @cached_property + def axons(self) -> axons.AsyncAxonsResourceWithStreamingResponse: + from .resources.axons import AsyncAxonsResourceWithStreamingResponse + + return AsyncAxonsResourceWithStreamingResponse(self._client.axons) + @cached_property def blueprints(self) -> blueprints.AsyncBlueprintsResourceWithStreamingResponse: from .resources.blueprints import AsyncBlueprintsResourceWithStreamingResponse diff --git a/src/runloop_api_client/resources/__init__.py b/src/runloop_api_client/resources/__init__.py index 2e0584f25..58d7488f6 100644 --- a/src/runloop_api_client/resources/__init__.py +++ b/src/runloop_api_client/resources/__init__.py @@ -1,5 +1,13 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from .axons import ( + AxonsResource, + AsyncAxonsResource, + AxonsResourceWithRawResponse, + AsyncAxonsResourceWithRawResponse, + AxonsResourceWithStreamingResponse, + AsyncAxonsResourceWithStreamingResponse, +) from .agents import ( AgentsResource, AsyncAgentsResource, @@ -130,6 +138,12 @@ "AsyncAgentsResourceWithRawResponse", "AgentsResourceWithStreamingResponse", "AsyncAgentsResourceWithStreamingResponse", + "AxonsResource", + "AsyncAxonsResource", + "AxonsResourceWithRawResponse", + "AsyncAxonsResourceWithRawResponse", + "AxonsResourceWithStreamingResponse", + "AsyncAxonsResourceWithStreamingResponse", "BlueprintsResource", "AsyncBlueprintsResource", "BlueprintsResourceWithRawResponse", diff --git a/src/runloop_api_client/resources/axons.py b/src/runloop_api_client/resources/axons.py new file mode 100644 index 000000000..fcf1b4a52 --- /dev/null +++ b/src/runloop_api_client/resources/axons.py @@ -0,0 +1,497 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +from ..types import axon_publish_params +from .._types import Body, Query, Headers, NotGiven, not_given +from .._utils import path_template, maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from .._streaming import Stream, AsyncStream +from .._base_client import make_request_options +from ..types.axon_view import AxonView +from ..types.axon_list_view import AxonListView +from ..types.axon_event_view import AxonEventView +from ..types.publish_result_view import PublishResultView + +__all__ = ["AxonsResource", "AsyncAxonsResource"] + + +class AxonsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> AxonsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/runloopai/api-client-python#accessing-raw-response-data-eg-headers + """ + return AxonsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AxonsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/runloopai/api-client-python#with_streaming_response + """ + return AxonsResourceWithStreamingResponse(self) + + def create( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> AxonView: + """[Beta] Create a new axon.""" + return self._post( + "/v1/axons", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=AxonView, + ) + + def retrieve( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AxonView: + """ + [Beta] Get an axon given ID. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._get( + path_template("/v1/axons/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AxonView, + ) + + def list( + self, + *, + # 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, + ) -> AxonListView: + """[Beta] List all active axons.""" + return self._get( + "/v1/axons", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AxonListView, + ) + + def publish( + self, + id: str, + *, + event_type: str, + origin: Literal["EXTERNAL_EVENT", "AGENT_EVENT", "USER_EVENT"], + payload: str, + source: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> PublishResultView: + """ + [Beta] Publish an event to a specified axon. + + Args: + event_type: The event type (e.g. push, pull_request). + + origin: Event origin. + + payload: Event payload. + + source: The source of the event (e.g. github, slack). + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._post( + path_template("/v1/axons/{id}/publish", id=id), + body=maybe_transform( + { + "event_type": event_type, + "origin": origin, + "payload": payload, + "source": source, + }, + axon_publish_params.AxonPublishParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=PublishResultView, + ) + + def subscribe_sse( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Stream[AxonEventView]: + """ + [Beta] Subscribe to an axon event stream via server-sent events. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._get( + path_template("/v1/axons/{id}/subscribe/sse", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AxonEventView, + stream=True, + stream_cls=Stream[AxonEventView], + ) + + +class AsyncAxonsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncAxonsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/runloopai/api-client-python#accessing-raw-response-data-eg-headers + """ + return AsyncAxonsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncAxonsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/runloopai/api-client-python#with_streaming_response + """ + return AsyncAxonsResourceWithStreamingResponse(self) + + async def create( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> AxonView: + """[Beta] Create a new axon.""" + return await self._post( + "/v1/axons", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=AxonView, + ) + + async def retrieve( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AxonView: + """ + [Beta] Get an axon given ID. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._get( + path_template("/v1/axons/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AxonView, + ) + + async def list( + self, + *, + # 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, + ) -> AxonListView: + """[Beta] List all active axons.""" + return await self._get( + "/v1/axons", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AxonListView, + ) + + async def publish( + self, + id: str, + *, + event_type: str, + origin: Literal["EXTERNAL_EVENT", "AGENT_EVENT", "USER_EVENT"], + payload: str, + source: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> PublishResultView: + """ + [Beta] Publish an event to a specified axon. + + Args: + event_type: The event type (e.g. push, pull_request). + + origin: Event origin. + + payload: Event payload. + + source: The source of the event (e.g. github, slack). + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._post( + path_template("/v1/axons/{id}/publish", id=id), + body=await async_maybe_transform( + { + "event_type": event_type, + "origin": origin, + "payload": payload, + "source": source, + }, + axon_publish_params.AxonPublishParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=PublishResultView, + ) + + async def subscribe_sse( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncStream[AxonEventView]: + """ + [Beta] Subscribe to an axon event stream via server-sent events. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._get( + path_template("/v1/axons/{id}/subscribe/sse", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AxonEventView, + stream=True, + stream_cls=AsyncStream[AxonEventView], + ) + + +class AxonsResourceWithRawResponse: + def __init__(self, axons: AxonsResource) -> None: + self._axons = axons + + self.create = to_raw_response_wrapper( + axons.create, + ) + self.retrieve = to_raw_response_wrapper( + axons.retrieve, + ) + self.list = to_raw_response_wrapper( + axons.list, + ) + self.publish = to_raw_response_wrapper( + axons.publish, + ) + self.subscribe_sse = to_raw_response_wrapper( + axons.subscribe_sse, + ) + + +class AsyncAxonsResourceWithRawResponse: + def __init__(self, axons: AsyncAxonsResource) -> None: + self._axons = axons + + self.create = async_to_raw_response_wrapper( + axons.create, + ) + self.retrieve = async_to_raw_response_wrapper( + axons.retrieve, + ) + self.list = async_to_raw_response_wrapper( + axons.list, + ) + self.publish = async_to_raw_response_wrapper( + axons.publish, + ) + self.subscribe_sse = async_to_raw_response_wrapper( + axons.subscribe_sse, + ) + + +class AxonsResourceWithStreamingResponse: + def __init__(self, axons: AxonsResource) -> None: + self._axons = axons + + self.create = to_streamed_response_wrapper( + axons.create, + ) + self.retrieve = to_streamed_response_wrapper( + axons.retrieve, + ) + self.list = to_streamed_response_wrapper( + axons.list, + ) + self.publish = to_streamed_response_wrapper( + axons.publish, + ) + self.subscribe_sse = to_streamed_response_wrapper( + axons.subscribe_sse, + ) + + +class AsyncAxonsResourceWithStreamingResponse: + def __init__(self, axons: AsyncAxonsResource) -> None: + self._axons = axons + + self.create = async_to_streamed_response_wrapper( + axons.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + axons.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + axons.list, + ) + self.publish = async_to_streamed_response_wrapper( + axons.publish, + ) + self.subscribe_sse = async_to_streamed_response_wrapper( + axons.subscribe_sse, + ) diff --git a/src/runloop_api_client/types/__init__.py b/src/runloop_api_client/types/__init__.py index cf0d86cab..194a41101 100644 --- a/src/runloop_api_client/types/__init__.py +++ b/src/runloop_api_client/types/__init__.py @@ -12,6 +12,7 @@ LaunchParameters as LaunchParameters, CodeMountParameters as CodeMountParameters, ) +from .axon_view import AxonView as AxonView from .agent_view import AgentView as AgentView from .devbox_view import DevboxView as DevboxView from .object_view import ObjectView as ObjectView @@ -19,9 +20,11 @@ from .tunnel_view import TunnelView as TunnelView from .input_context import InputContext as InputContext from .scenario_view import ScenarioView as ScenarioView +from .axon_list_view import AxonListView as AxonListView from .benchmark_view import BenchmarkView as BenchmarkView from .blueprint_view import BlueprintView as BlueprintView from .agent_list_view import AgentListView as AgentListView +from .axon_event_view import AxonEventView as AxonEventView from .mcp_config_view import McpConfigView as McpConfigView from .devbox_list_view import DevboxListView as DevboxListView from .object_list_view import ObjectListView as ObjectListView @@ -36,11 +39,13 @@ from .object_list_params import ObjectListParams as ObjectListParams from .secret_list_params import SecretListParams as SecretListParams from .agent_create_params import AgentCreateParams as AgentCreateParams +from .axon_publish_params import AxonPublishParams as AxonPublishParams from .blueprint_build_log import BlueprintBuildLog as BlueprintBuildLog from .blueprint_list_view import BlueprintListView as BlueprintListView from .gateway_config_view import GatewayConfigView as GatewayConfigView from .input_context_param import InputContextParam as InputContextParam from .network_policy_view import NetworkPolicyView as NetworkPolicyView +from .publish_result_view import PublishResultView as PublishResultView from .devbox_create_params import DevboxCreateParams as DevboxCreateParams from .devbox_snapshot_view import DevboxSnapshotView as DevboxSnapshotView from .devbox_update_params import DevboxUpdateParams as DevboxUpdateParams diff --git a/src/runloop_api_client/types/axon_event_view.py b/src/runloop_api_client/types/axon_event_view.py new file mode 100644 index 000000000..63f09b80a --- /dev/null +++ b/src/runloop_api_client/types/axon_event_view.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["AxonEventView"] + + +class AxonEventView(BaseModel): + axon_id: str + """The axon identifier.""" + + event_type: str + """Event type (e.g. push, pull_request).""" + + origin: Literal["EXTERNAL_EVENT", "AGENT_EVENT", "USER_EVENT", "SYSTEM_EVENT"] + """Event origin.""" + + payload: str + """JSON-encoded event payload.""" + + sequence: int + """Monotonic sequence number.""" + + source: str + """Event source (e.g. github, slack).""" + + timestamp_ms: int + """Timestamp in milliseconds since epoch.""" diff --git a/src/runloop_api_client/types/axon_list_view.py b/src/runloop_api_client/types/axon_list_view.py new file mode 100644 index 000000000..bd6c4d31c --- /dev/null +++ b/src/runloop_api_client/types/axon_list_view.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List + +from .._models import BaseModel +from .axon_view import AxonView + +__all__ = ["AxonListView"] + + +class AxonListView(BaseModel): + axons: List[AxonView] + """List of active axons.""" diff --git a/src/runloop_api_client/types/axon_publish_params.py b/src/runloop_api_client/types/axon_publish_params.py new file mode 100644 index 000000000..98fcd91bf --- /dev/null +++ b/src/runloop_api_client/types/axon_publish_params.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["AxonPublishParams"] + + +class AxonPublishParams(TypedDict, total=False): + event_type: Required[str] + """The event type (e.g. push, pull_request).""" + + origin: Required[Literal["EXTERNAL_EVENT", "AGENT_EVENT", "USER_EVENT"]] + """Event origin.""" + + payload: Required[str] + """Event payload.""" + + source: Required[str] + """The source of the event (e.g. github, slack).""" diff --git a/src/runloop_api_client/types/axon_view.py b/src/runloop_api_client/types/axon_view.py new file mode 100644 index 000000000..db4a13006 --- /dev/null +++ b/src/runloop_api_client/types/axon_view.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel + +__all__ = ["AxonView"] + + +class AxonView(BaseModel): + id: str + """The axon identifier.""" + + created_at_ms: int + """Creation time in milliseconds since epoch.""" diff --git a/src/runloop_api_client/types/publish_result_view.py b/src/runloop_api_client/types/publish_result_view.py new file mode 100644 index 000000000..f74e56a65 --- /dev/null +++ b/src/runloop_api_client/types/publish_result_view.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel + +__all__ = ["PublishResultView"] + + +class PublishResultView(BaseModel): + sequence: int + """Assigned sequence number.""" + + timestamp_ms: int + """Timestamp in milliseconds since epoch.""" diff --git a/tests/api_resources/test_axons.py b/tests/api_resources/test_axons.py new file mode 100644 index 000000000..9bbec5f4a --- /dev/null +++ b/tests/api_resources/test_axons.py @@ -0,0 +1,382 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from tests.utils import assert_matches_type +from runloop_api_client import Runloop, AsyncRunloop +from runloop_api_client.types import AxonView, AxonListView, PublishResultView + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestAxons: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: Runloop) -> None: + axon = client.axons.create() + assert_matches_type(AxonView, axon, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: Runloop) -> None: + response = client.axons.with_raw_response.create() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + axon = response.parse() + assert_matches_type(AxonView, axon, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: Runloop) -> None: + with client.axons.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + axon = response.parse() + assert_matches_type(AxonView, axon, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_retrieve(self, client: Runloop) -> None: + axon = client.axons.retrieve( + "id", + ) + assert_matches_type(AxonView, axon, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Runloop) -> None: + response = client.axons.with_raw_response.retrieve( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + axon = response.parse() + assert_matches_type(AxonView, axon, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Runloop) -> None: + with client.axons.with_streaming_response.retrieve( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + axon = response.parse() + assert_matches_type(AxonView, axon, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Runloop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.axons.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_list(self, client: Runloop) -> None: + axon = client.axons.list() + assert_matches_type(AxonListView, axon, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Runloop) -> None: + response = client.axons.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + axon = response.parse() + assert_matches_type(AxonListView, axon, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Runloop) -> None: + with client.axons.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + axon = response.parse() + assert_matches_type(AxonListView, axon, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_publish(self, client: Runloop) -> None: + axon = client.axons.publish( + id="id", + event_type="event_type", + origin="EXTERNAL_EVENT", + payload="payload", + source="source", + ) + assert_matches_type(PublishResultView, axon, path=["response"]) + + @parametrize + def test_raw_response_publish(self, client: Runloop) -> None: + response = client.axons.with_raw_response.publish( + id="id", + event_type="event_type", + origin="EXTERNAL_EVENT", + payload="payload", + source="source", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + axon = response.parse() + assert_matches_type(PublishResultView, axon, path=["response"]) + + @parametrize + def test_streaming_response_publish(self, client: Runloop) -> None: + with client.axons.with_streaming_response.publish( + id="id", + event_type="event_type", + origin="EXTERNAL_EVENT", + payload="payload", + source="source", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + axon = response.parse() + assert_matches_type(PublishResultView, axon, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_publish(self, client: Runloop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.axons.with_raw_response.publish( + id="", + event_type="event_type", + origin="EXTERNAL_EVENT", + payload="payload", + source="source", + ) + + @parametrize + def test_method_subscribe_sse(self, client: Runloop) -> None: + axon_stream = client.axons.subscribe_sse( + "id", + ) + axon_stream.response.close() + + @parametrize + def test_raw_response_subscribe_sse(self, client: Runloop) -> None: + response = client.axons.with_raw_response.subscribe_sse( + "id", + ) + + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stream = response.parse() + stream.close() + + @parametrize + def test_streaming_response_subscribe_sse(self, client: Runloop) -> None: + with client.axons.with_streaming_response.subscribe_sse( + "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_subscribe_sse(self, client: Runloop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.axons.with_raw_response.subscribe_sse( + "", + ) + + +class TestAsyncAxons: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncRunloop) -> None: + axon = await async_client.axons.create() + assert_matches_type(AxonView, axon, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncRunloop) -> None: + response = await async_client.axons.with_raw_response.create() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + axon = await response.parse() + assert_matches_type(AxonView, axon, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncRunloop) -> None: + async with async_client.axons.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + axon = await response.parse() + assert_matches_type(AxonView, axon, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncRunloop) -> None: + axon = await async_client.axons.retrieve( + "id", + ) + assert_matches_type(AxonView, axon, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncRunloop) -> None: + response = await async_client.axons.with_raw_response.retrieve( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + axon = await response.parse() + assert_matches_type(AxonView, axon, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncRunloop) -> None: + async with async_client.axons.with_streaming_response.retrieve( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + axon = await response.parse() + assert_matches_type(AxonView, axon, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncRunloop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.axons.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncRunloop) -> None: + axon = await async_client.axons.list() + assert_matches_type(AxonListView, axon, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncRunloop) -> None: + response = await async_client.axons.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + axon = await response.parse() + assert_matches_type(AxonListView, axon, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncRunloop) -> None: + async with async_client.axons.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + axon = await response.parse() + assert_matches_type(AxonListView, axon, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_publish(self, async_client: AsyncRunloop) -> None: + axon = await async_client.axons.publish( + id="id", + event_type="event_type", + origin="EXTERNAL_EVENT", + payload="payload", + source="source", + ) + assert_matches_type(PublishResultView, axon, path=["response"]) + + @parametrize + async def test_raw_response_publish(self, async_client: AsyncRunloop) -> None: + response = await async_client.axons.with_raw_response.publish( + id="id", + event_type="event_type", + origin="EXTERNAL_EVENT", + payload="payload", + source="source", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + axon = await response.parse() + assert_matches_type(PublishResultView, axon, path=["response"]) + + @parametrize + async def test_streaming_response_publish(self, async_client: AsyncRunloop) -> None: + async with async_client.axons.with_streaming_response.publish( + id="id", + event_type="event_type", + origin="EXTERNAL_EVENT", + payload="payload", + source="source", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + axon = await response.parse() + assert_matches_type(PublishResultView, axon, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_publish(self, async_client: AsyncRunloop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.axons.with_raw_response.publish( + id="", + event_type="event_type", + origin="EXTERNAL_EVENT", + payload="payload", + source="source", + ) + + @parametrize + async def test_method_subscribe_sse(self, async_client: AsyncRunloop) -> None: + axon_stream = await async_client.axons.subscribe_sse( + "id", + ) + await axon_stream.response.aclose() + + @parametrize + async def test_raw_response_subscribe_sse(self, async_client: AsyncRunloop) -> None: + response = await async_client.axons.with_raw_response.subscribe_sse( + "id", + ) + + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stream = await response.parse() + await stream.close() + + @parametrize + async def test_streaming_response_subscribe_sse(self, async_client: AsyncRunloop) -> None: + async with async_client.axons.with_streaming_response.subscribe_sse( + "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_subscribe_sse(self, async_client: AsyncRunloop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.axons.with_raw_response.subscribe_sse( + "", + ) From 7bdb527e9011a68f456c927f138fac22d73b3035 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Mar 2026 21:51:57 +0000 Subject: [PATCH 12/13] fix: add name to Axon (#8277) --- .stats.yml | 4 +- api.md | 2 +- src/runloop_api_client/resources/axons.py | 43 +++++++++++++++++-- src/runloop_api_client/types/__init__.py | 1 + .../types/axon_create_params.py | 13 ++++++ src/runloop_api_client/types/axon_view.py | 5 +++ tests/api_resources/test_axons.py | 14 ++++++ 7 files changed, 75 insertions(+), 7 deletions(-) create mode 100644 src/runloop_api_client/types/axon_create_params.py diff --git a/.stats.yml b/.stats.yml index d85c18351..f43fecc35 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 122 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-82e6ea2fc76e43ff397ca1d9997693307a006ef30ec1a001c0283319a3e2eb3b.yml -openapi_spec_hash: 69cf8a2d13bda298f2b588bd3ba1e562 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-32e4b2dfb75745be076697d252bd80aff21c08464750928ffe2b7dd997d0b443.yml +openapi_spec_hash: eb0ccabfcda0fb8c56b53939b56f6d80 config_hash: c422b761c745873bce8fa5ccf03b7b98 diff --git a/api.md b/api.md index ccda6b5d9..81abd6751 100644 --- a/api.md +++ b/api.md @@ -104,7 +104,7 @@ from runloop_api_client.types import ( Methods: -- client.axons.create() -> AxonView +- client.axons.create(\*\*params) -> AxonView - client.axons.retrieve(id) -> AxonView - client.axons.list() -> AxonListView - client.axons.publish(id, \*\*params) -> PublishResultView diff --git a/src/runloop_api_client/resources/axons.py b/src/runloop_api_client/resources/axons.py index fcf1b4a52..9758057de 100644 --- a/src/runloop_api_client/resources/axons.py +++ b/src/runloop_api_client/resources/axons.py @@ -2,12 +2,13 @@ from __future__ import annotations +from typing import Optional from typing_extensions import Literal import httpx -from ..types import axon_publish_params -from .._types import Body, Query, Headers, NotGiven, not_given +from ..types import axon_create_params, axon_publish_params +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from .._utils import path_template, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource @@ -50,6 +51,7 @@ def with_streaming_response(self) -> AxonsResourceWithStreamingResponse: def create( self, *, + name: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -58,9 +60,25 @@ def create( timeout: float | httpx.Timeout | None | NotGiven = not_given, idempotency_key: str | None = None, ) -> AxonView: - """[Beta] Create a new axon.""" + """ + [Beta] Create a new axon. + + Args: + name: (Optional) Name for the axon. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ return self._post( "/v1/axons", + body=maybe_transform({"name": name}, axon_create_params.AxonCreateParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -243,6 +261,7 @@ def with_streaming_response(self) -> AsyncAxonsResourceWithStreamingResponse: async def create( self, *, + name: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -251,9 +270,25 @@ async def create( timeout: float | httpx.Timeout | None | NotGiven = not_given, idempotency_key: str | None = None, ) -> AxonView: - """[Beta] Create a new axon.""" + """ + [Beta] Create a new axon. + + Args: + name: (Optional) Name for the axon. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ return await self._post( "/v1/axons", + body=await async_maybe_transform({"name": name}, axon_create_params.AxonCreateParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, diff --git a/src/runloop_api_client/types/__init__.py b/src/runloop_api_client/types/__init__.py index 194a41101..11f82aa7a 100644 --- a/src/runloop_api_client/types/__init__.py +++ b/src/runloop_api_client/types/__init__.py @@ -33,6 +33,7 @@ from .secret_list_view import SecretListView as SecretListView from .agent_list_params import AgentListParams as AgentListParams from .scenario_run_view import ScenarioRunView as ScenarioRunView +from .axon_create_params import AxonCreateParams as AxonCreateParams from .benchmark_job_view import BenchmarkJobView as BenchmarkJobView from .benchmark_run_view import BenchmarkRunView as BenchmarkRunView from .devbox_list_params import DevboxListParams as DevboxListParams diff --git a/src/runloop_api_client/types/axon_create_params.py b/src/runloop_api_client/types/axon_create_params.py new file mode 100644 index 000000000..0906db0ae --- /dev/null +++ b/src/runloop_api_client/types/axon_create_params.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import TypedDict + +__all__ = ["AxonCreateParams"] + + +class AxonCreateParams(TypedDict, total=False): + name: Optional[str] + """(Optional) Name for the axon.""" diff --git a/src/runloop_api_client/types/axon_view.py b/src/runloop_api_client/types/axon_view.py index db4a13006..61d1bd243 100644 --- a/src/runloop_api_client/types/axon_view.py +++ b/src/runloop_api_client/types/axon_view.py @@ -1,5 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from typing import Optional + from .._models import BaseModel __all__ = ["AxonView"] @@ -11,3 +13,6 @@ class AxonView(BaseModel): created_at_ms: int """Creation time in milliseconds since epoch.""" + + name: Optional[str] = None + """The name of the axon.""" diff --git a/tests/api_resources/test_axons.py b/tests/api_resources/test_axons.py index 9bbec5f4a..bda5f1a23 100644 --- a/tests/api_resources/test_axons.py +++ b/tests/api_resources/test_axons.py @@ -22,6 +22,13 @@ def test_method_create(self, client: Runloop) -> None: axon = client.axons.create() assert_matches_type(AxonView, axon, path=["response"]) + @parametrize + def test_method_create_with_all_params(self, client: Runloop) -> None: + axon = client.axons.create( + name="name", + ) + assert_matches_type(AxonView, axon, path=["response"]) + @parametrize def test_raw_response_create(self, client: Runloop) -> None: response = client.axons.with_raw_response.create() @@ -207,6 +214,13 @@ async def test_method_create(self, async_client: AsyncRunloop) -> None: axon = await async_client.axons.create() assert_matches_type(AxonView, axon, path=["response"]) + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncRunloop) -> None: + axon = await async_client.axons.create( + name="name", + ) + assert_matches_type(AxonView, axon, path=["response"]) + @parametrize async def test_raw_response_create(self, async_client: AsyncRunloop) -> None: response = await async_client.axons.with_raw_response.create() From 74a5f2ae0674c7349e4a4e0ed24997913b449169 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Mar 2026 21:52:20 +0000 Subject: [PATCH 13/13] release: 1.13.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 32 ++++++++++++++++++++++++++++++ pyproject.toml | 2 +- src/runloop_api_client/_version.py | 2 +- 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index ffb929a02..f94eeca26 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.12.1" + ".": "1.13.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fa80f3dd..1f7dfb282 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,37 @@ # Changelog +## 1.13.0 (2026-03-24) + +Full Changelog: [v1.12.1...v1.13.0](https://github.com/runloopai/api-client-python/compare/v1.12.1...v1.13.0) + +### Features + +* example for using tunnels ([#754](https://github.com/runloopai/api-client-python/issues/754)) ([97bcd70](https://github.com/runloopai/api-client-python/commit/97bcd701c6aac8077b8dea9cd4d0693d524de9b7)) + + +### Bug Fixes + +* add AxonEventView schema to OpenAPI spec for SSE subscribe endpoint ([#8274](https://github.com/runloopai/api-client-python/issues/8274)) ([618f1bb](https://github.com/runloopai/api-client-python/commit/618f1bb48ff7757e5285cddd083f49ccbbeeed3c)) +* add name to Axon ([#8277](https://github.com/runloopai/api-client-python/issues/8277)) ([7bdb527](https://github.com/runloopai/api-client-python/commit/7bdb527e9011a68f456c927f138fac22d73b3035)) +* sanitize endpoint path params ([5182d36](https://github.com/runloopai/api-client-python/commit/5182d3666b20f4fe5b245201e0b992705443d024)) +* sanitize endpoint path params ([0993026](https://github.com/runloopai/api-client-python/commit/0993026b7dd67b1f7a9a81036c7fc93ab638112d)) +* **tunnels:** allow tunnel removal ([#8257](https://github.com/runloopai/api-client-python/issues/8257)) ([6cff97e](https://github.com/runloopai/api-client-python/commit/6cff97ee43a2f2c906833e9900841e34d478d176)) + + +### Chores + +* **internal:** update gitignore ([96bd6fd](https://github.com/runloopai/api-client-python/commit/96bd6fdc7b767371b21f5753729f01020f9e78ec)) +* remove dead port configuration code, mark deprecated / ignored in the API ([#8195](https://github.com/runloopai/api-client-python/issues/8195)) ([e565324](https://github.com/runloopai/api-client-python/commit/e5653245b65f7afd6210abe1b507ae64151da9b7)) +* **tests:** bump steady to v0.19.4 ([66cb637](https://github.com/runloopai/api-client-python/commit/66cb637779eaea2cacc4b1a06ac3a33f2358b0ee)) +* **tests:** bump steady to v0.19.5 ([601c93a](https://github.com/runloopai/api-client-python/commit/601c93afffb8379770ac895b89235903c8348b00)) +* **tests:** bump steady to v0.19.6 ([95b2ffc](https://github.com/runloopai/api-client-python/commit/95b2ffc5d487f6595ec1dc73b6a34696c309f67d)) + + +### Refactors + +* **tests:** switch from prism to steady ([dd8fe60](https://github.com/runloopai/api-client-python/commit/dd8fe60922808bb0f001b49a99988004195d806f)) +* undeprecate total_count from pagination API, remove remaining_count ([#8084](https://github.com/runloopai/api-client-python/issues/8084)) ([4bd524f](https://github.com/runloopai/api-client-python/commit/4bd524fa32e6aad6efeeabd8a5bbef5e3c7ea742)) + ## 1.12.1 (2026-03-19) Full Changelog: [v1.12.0...v1.12.1](https://github.com/runloopai/api-client-python/compare/v1.12.0...v1.12.1) diff --git a/pyproject.toml b/pyproject.toml index 1fccbf83b..931df6d19 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "runloop_api_client" -version = "1.12.1" +version = "1.13.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 f93dfde0e..5dafd5d62 100644 --- a/src/runloop_api_client/_version.py +++ b/src/runloop_api_client/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "runloop_api_client" -__version__ = "1.12.1" # x-release-please-version +__version__ = "1.13.0" # x-release-please-version