From 2e03b55638a83a8354481f47a30910e94ff57fc3 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 23:09:05 +0000 Subject: [PATCH 1/5] feat: Allow API keys to create API and restricted keys (#8663) --- .stats.yml | 8 +- api.md | 28 +++ src/runloop_api_client/_client.py | 76 +++++++ src/runloop_api_client/resources/__init__.py | 28 +++ src/runloop_api_client/resources/apikeys.py | 197 +++++++++++++++++ .../resources/restricted_keys.py | 202 ++++++++++++++++++ src/runloop_api_client/types/__init__.py | 6 + .../types/api_key_created_view.py | 17 ++ .../types/apikey_create_params.py | 14 ++ .../types/restricted_key_create_params.py | 18 ++ .../types/restricted_key_created_view.py | 20 ++ .../types/scope_entry_view.py | 26 +++ .../types/scope_entry_view_param.py | 23 ++ tests/api_resources/test_apikeys.py | 90 ++++++++ tests/api_resources/test_restricted_keys.py | 102 +++++++++ 15 files changed, 851 insertions(+), 4 deletions(-) create mode 100644 src/runloop_api_client/resources/apikeys.py create mode 100644 src/runloop_api_client/resources/restricted_keys.py create mode 100644 src/runloop_api_client/types/api_key_created_view.py create mode 100644 src/runloop_api_client/types/apikey_create_params.py create mode 100644 src/runloop_api_client/types/restricted_key_create_params.py create mode 100644 src/runloop_api_client/types/restricted_key_created_view.py create mode 100644 src/runloop_api_client/types/scope_entry_view.py create mode 100644 src/runloop_api_client/types/scope_entry_view_param.py create mode 100644 tests/api_resources/test_apikeys.py create mode 100644 tests/api_resources/test_restricted_keys.py diff --git a/.stats.yml b/.stats.yml index 7c298b7e4..36aa46087 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 110 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-f0eb12cf4df4fa3046bd88aae4966062eb6e9703768a07a0136da2f26a2ebd56.yml -openapi_spec_hash: cdbd63a8162f1e987e937042cbd60eea -config_hash: 526cf0707adc54c690fc687a1c6db728 +configured_endpoints: 112 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-f1ad4729124d46b6b2f926f177680a9644c6701d7e7c87731df9bf21a6414546.yml +openapi_spec_hash: eb49220bba68581587f2d9d4d79d0c7e +config_hash: 636a44fb2f5d76373805b629a0f3dd35 diff --git a/api.md b/api.md index fd95076a1..3952222dd 100644 --- a/api.md +++ b/api.md @@ -439,3 +439,31 @@ Methods: - client.mcp_configs.update(id, \*\*params) -> McpConfigView - client.mcp_configs.list(\*\*params) -> SyncMcpConfigsCursorIDPage[McpConfigView] - client.mcp_configs.delete(id) -> McpConfigView + +# Apikeys + +Types: + +```python +from runloop_api_client.types import APIKeyCreatedView, APIKeyCreateParameters +``` + +Methods: + +- client.apikeys.create(\*\*params) -> APIKeyCreatedView + +# RestrictedKeys + +Types: + +```python +from runloop_api_client.types import ( + RestrictedKeyCreatedView, + RestrictedKeyCreateParameters, + ScopeEntryView, +) +``` + +Methods: + +- client.restricted_keys.create(\*\*params) -> RestrictedKeyCreatedView diff --git a/src/runloop_api_client/_client.py b/src/runloop_api_client/_client.py index 28e1e6f94..61db3a474 100644 --- a/src/runloop_api_client/_client.py +++ b/src/runloop_api_client/_client.py @@ -34,6 +34,7 @@ from .resources import ( axons, agents, + apikeys, objects, secrets, devboxes, @@ -44,9 +45,11 @@ benchmark_jobs, benchmark_runs, gateway_configs, + restricted_keys, network_policies, ) from .resources.agents import AgentsResource, AsyncAgentsResource + from .resources.apikeys import ApikeysResource, AsyncApikeysResource from .resources.objects import ObjectsResource, AsyncObjectsResource from .resources.secrets import SecretsResource, AsyncSecretsResource from .resources.benchmarks import BenchmarksResource, AsyncBenchmarksResource @@ -56,6 +59,7 @@ from .resources.benchmark_jobs import BenchmarkJobsResource, AsyncBenchmarkJobsResource from .resources.benchmark_runs import BenchmarkRunsResource, AsyncBenchmarkRunsResource from .resources.gateway_configs import GatewayConfigsResource, AsyncGatewayConfigsResource + from .resources.restricted_keys import RestrictedKeysResource, AsyncRestrictedKeysResource from .resources.network_policies import NetworkPoliciesResource, AsyncNetworkPoliciesResource from .resources.devboxes.devboxes import DevboxesResource, AsyncDevboxesResource from .resources.scenarios.scenarios import ScenariosResource, AsyncScenariosResource @@ -198,6 +202,18 @@ def mcp_configs(self) -> McpConfigsResource: return McpConfigsResource(self) + @cached_property + def apikeys(self) -> ApikeysResource: + from .resources.apikeys import ApikeysResource + + return ApikeysResource(self) + + @cached_property + def restricted_keys(self) -> RestrictedKeysResource: + from .resources.restricted_keys import RestrictedKeysResource + + return RestrictedKeysResource(self) + @cached_property def with_raw_response(self) -> RunloopWithRawResponse: return RunloopWithRawResponse(self) @@ -446,6 +462,18 @@ def mcp_configs(self) -> AsyncMcpConfigsResource: return AsyncMcpConfigsResource(self) + @cached_property + def apikeys(self) -> AsyncApikeysResource: + from .resources.apikeys import AsyncApikeysResource + + return AsyncApikeysResource(self) + + @cached_property + def restricted_keys(self) -> AsyncRestrictedKeysResource: + from .resources.restricted_keys import AsyncRestrictedKeysResource + + return AsyncRestrictedKeysResource(self) + @cached_property def with_raw_response(self) -> AsyncRunloopWithRawResponse: return AsyncRunloopWithRawResponse(self) @@ -643,6 +671,18 @@ def mcp_configs(self) -> mcp_configs.McpConfigsResourceWithRawResponse: return McpConfigsResourceWithRawResponse(self._client.mcp_configs) + @cached_property + def apikeys(self) -> apikeys.ApikeysResourceWithRawResponse: + from .resources.apikeys import ApikeysResourceWithRawResponse + + return ApikeysResourceWithRawResponse(self._client.apikeys) + + @cached_property + def restricted_keys(self) -> restricted_keys.RestrictedKeysResourceWithRawResponse: + from .resources.restricted_keys import RestrictedKeysResourceWithRawResponse + + return RestrictedKeysResourceWithRawResponse(self._client.restricted_keys) + class AsyncRunloopWithRawResponse: _client: AsyncRunloop @@ -728,6 +768,18 @@ def mcp_configs(self) -> mcp_configs.AsyncMcpConfigsResourceWithRawResponse: return AsyncMcpConfigsResourceWithRawResponse(self._client.mcp_configs) + @cached_property + def apikeys(self) -> apikeys.AsyncApikeysResourceWithRawResponse: + from .resources.apikeys import AsyncApikeysResourceWithRawResponse + + return AsyncApikeysResourceWithRawResponse(self._client.apikeys) + + @cached_property + def restricted_keys(self) -> restricted_keys.AsyncRestrictedKeysResourceWithRawResponse: + from .resources.restricted_keys import AsyncRestrictedKeysResourceWithRawResponse + + return AsyncRestrictedKeysResourceWithRawResponse(self._client.restricted_keys) + class RunloopWithStreamedResponse: _client: Runloop @@ -813,6 +865,18 @@ def mcp_configs(self) -> mcp_configs.McpConfigsResourceWithStreamingResponse: return McpConfigsResourceWithStreamingResponse(self._client.mcp_configs) + @cached_property + def apikeys(self) -> apikeys.ApikeysResourceWithStreamingResponse: + from .resources.apikeys import ApikeysResourceWithStreamingResponse + + return ApikeysResourceWithStreamingResponse(self._client.apikeys) + + @cached_property + def restricted_keys(self) -> restricted_keys.RestrictedKeysResourceWithStreamingResponse: + from .resources.restricted_keys import RestrictedKeysResourceWithStreamingResponse + + return RestrictedKeysResourceWithStreamingResponse(self._client.restricted_keys) + class AsyncRunloopWithStreamedResponse: _client: AsyncRunloop @@ -898,6 +962,18 @@ def mcp_configs(self) -> mcp_configs.AsyncMcpConfigsResourceWithStreamingRespons return AsyncMcpConfigsResourceWithStreamingResponse(self._client.mcp_configs) + @cached_property + def apikeys(self) -> apikeys.AsyncApikeysResourceWithStreamingResponse: + from .resources.apikeys import AsyncApikeysResourceWithStreamingResponse + + return AsyncApikeysResourceWithStreamingResponse(self._client.apikeys) + + @cached_property + def restricted_keys(self) -> restricted_keys.AsyncRestrictedKeysResourceWithStreamingResponse: + from .resources.restricted_keys import AsyncRestrictedKeysResourceWithStreamingResponse + + return AsyncRestrictedKeysResourceWithStreamingResponse(self._client.restricted_keys) + Client = Runloop diff --git a/src/runloop_api_client/resources/__init__.py b/src/runloop_api_client/resources/__init__.py index 292f6d666..dd27ba9e4 100644 --- a/src/runloop_api_client/resources/__init__.py +++ b/src/runloop_api_client/resources/__init__.py @@ -16,6 +16,14 @@ AgentsResourceWithStreamingResponse, AsyncAgentsResourceWithStreamingResponse, ) +from .apikeys import ( + ApikeysResource, + AsyncApikeysResource, + ApikeysResourceWithRawResponse, + AsyncApikeysResourceWithRawResponse, + ApikeysResourceWithStreamingResponse, + AsyncApikeysResourceWithStreamingResponse, +) from .objects import ( ObjectsResource, AsyncObjectsResource, @@ -96,6 +104,14 @@ GatewayConfigsResourceWithStreamingResponse, AsyncGatewayConfigsResourceWithStreamingResponse, ) +from .restricted_keys import ( + RestrictedKeysResource, + AsyncRestrictedKeysResource, + RestrictedKeysResourceWithRawResponse, + AsyncRestrictedKeysResourceWithRawResponse, + RestrictedKeysResourceWithStreamingResponse, + AsyncRestrictedKeysResourceWithStreamingResponse, +) from .network_policies import ( NetworkPoliciesResource, AsyncNetworkPoliciesResource, @@ -184,4 +200,16 @@ "AsyncMcpConfigsResourceWithRawResponse", "McpConfigsResourceWithStreamingResponse", "AsyncMcpConfigsResourceWithStreamingResponse", + "ApikeysResource", + "AsyncApikeysResource", + "ApikeysResourceWithRawResponse", + "AsyncApikeysResourceWithRawResponse", + "ApikeysResourceWithStreamingResponse", + "AsyncApikeysResourceWithStreamingResponse", + "RestrictedKeysResource", + "AsyncRestrictedKeysResource", + "RestrictedKeysResourceWithRawResponse", + "AsyncRestrictedKeysResourceWithRawResponse", + "RestrictedKeysResourceWithStreamingResponse", + "AsyncRestrictedKeysResourceWithStreamingResponse", ] diff --git a/src/runloop_api_client/resources/apikeys.py b/src/runloop_api_client/resources/apikeys.py new file mode 100644 index 000000000..fcc565ac0 --- /dev/null +++ b/src/runloop_api_client/resources/apikeys.py @@ -0,0 +1,197 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional + +import httpx + +from ..types import apikey_create_params +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import 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 .._base_client import make_request_options +from ..types.api_key_created_view import APIKeyCreatedView + +__all__ = ["ApikeysResource", "AsyncApikeysResource"] + + +class ApikeysResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ApikeysResourceWithRawResponse: + """ + 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 ApikeysResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ApikeysResourceWithStreamingResponse: + """ + 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 ApikeysResourceWithStreamingResponse(self) + + def create( + self, + *, + expires_at_ms: Optional[int] | Omit = omit, + name: 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, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> APIKeyCreatedView: + """Create a new API key for the authenticated account. + + Use a standard API key (ak*) + or a restricted key (rk*) with RESOURCE_TYPE_ACCOUNT write scope. + + 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 + + idempotency_key: Specify a custom idempotency key for this request + """ + return self._post( + "/v1/apikeys", + body=maybe_transform( + { + "expires_at_ms": expires_at_ms, + "name": name, + }, + apikey_create_params.ApikeyCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=APIKeyCreatedView, + ) + + +class AsyncApikeysResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncApikeysResourceWithRawResponse: + """ + 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 AsyncApikeysResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncApikeysResourceWithStreamingResponse: + """ + 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 AsyncApikeysResourceWithStreamingResponse(self) + + async def create( + self, + *, + expires_at_ms: Optional[int] | Omit = omit, + name: 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, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> APIKeyCreatedView: + """Create a new API key for the authenticated account. + + Use a standard API key (ak*) + or a restricted key (rk*) with RESOURCE_TYPE_ACCOUNT write scope. + + 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 + + idempotency_key: Specify a custom idempotency key for this request + """ + return await self._post( + "/v1/apikeys", + body=await async_maybe_transform( + { + "expires_at_ms": expires_at_ms, + "name": name, + }, + apikey_create_params.ApikeyCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=APIKeyCreatedView, + ) + + +class ApikeysResourceWithRawResponse: + def __init__(self, apikeys: ApikeysResource) -> None: + self._apikeys = apikeys + + self.create = to_raw_response_wrapper( + apikeys.create, + ) + + +class AsyncApikeysResourceWithRawResponse: + def __init__(self, apikeys: AsyncApikeysResource) -> None: + self._apikeys = apikeys + + self.create = async_to_raw_response_wrapper( + apikeys.create, + ) + + +class ApikeysResourceWithStreamingResponse: + def __init__(self, apikeys: ApikeysResource) -> None: + self._apikeys = apikeys + + self.create = to_streamed_response_wrapper( + apikeys.create, + ) + + +class AsyncApikeysResourceWithStreamingResponse: + def __init__(self, apikeys: AsyncApikeysResource) -> None: + self._apikeys = apikeys + + self.create = async_to_streamed_response_wrapper( + apikeys.create, + ) diff --git a/src/runloop_api_client/resources/restricted_keys.py b/src/runloop_api_client/resources/restricted_keys.py new file mode 100644 index 000000000..6e71dfedb --- /dev/null +++ b/src/runloop_api_client/resources/restricted_keys.py @@ -0,0 +1,202 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable, Optional + +import httpx + +from ..types import restricted_key_create_params +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import 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 .._base_client import make_request_options +from ..types.scope_entry_view_param import ScopeEntryViewParam +from ..types.restricted_key_created_view import RestrictedKeyCreatedView + +__all__ = ["RestrictedKeysResource", "AsyncRestrictedKeysResource"] + + +class RestrictedKeysResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> RestrictedKeysResourceWithRawResponse: + """ + 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 RestrictedKeysResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> RestrictedKeysResourceWithStreamingResponse: + """ + 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 RestrictedKeysResourceWithStreamingResponse(self) + + def create( + self, + *, + expires_at_ms: Optional[int] | Omit = omit, + name: str | Omit = omit, + scopes: Iterable[ScopeEntryViewParam] | 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, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> RestrictedKeyCreatedView: + """Create a restricted API key with specific resource scopes. + + Use a standard API + key (ak*) or a restricted key (rk*) with RESOURCE_TYPE_ACCOUNT write scope. + + 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 + + idempotency_key: Specify a custom idempotency key for this request + """ + return self._post( + "/v1/restricted_keys", + body=maybe_transform( + { + "expires_at_ms": expires_at_ms, + "name": name, + "scopes": scopes, + }, + restricted_key_create_params.RestrictedKeyCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=RestrictedKeyCreatedView, + ) + + +class AsyncRestrictedKeysResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncRestrictedKeysResourceWithRawResponse: + """ + 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 AsyncRestrictedKeysResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncRestrictedKeysResourceWithStreamingResponse: + """ + 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 AsyncRestrictedKeysResourceWithStreamingResponse(self) + + async def create( + self, + *, + expires_at_ms: Optional[int] | Omit = omit, + name: str | Omit = omit, + scopes: Iterable[ScopeEntryViewParam] | 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, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> RestrictedKeyCreatedView: + """Create a restricted API key with specific resource scopes. + + Use a standard API + key (ak*) or a restricted key (rk*) with RESOURCE_TYPE_ACCOUNT write scope. + + 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 + + idempotency_key: Specify a custom idempotency key for this request + """ + return await self._post( + "/v1/restricted_keys", + body=await async_maybe_transform( + { + "expires_at_ms": expires_at_ms, + "name": name, + "scopes": scopes, + }, + restricted_key_create_params.RestrictedKeyCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=RestrictedKeyCreatedView, + ) + + +class RestrictedKeysResourceWithRawResponse: + def __init__(self, restricted_keys: RestrictedKeysResource) -> None: + self._restricted_keys = restricted_keys + + self.create = to_raw_response_wrapper( + restricted_keys.create, + ) + + +class AsyncRestrictedKeysResourceWithRawResponse: + def __init__(self, restricted_keys: AsyncRestrictedKeysResource) -> None: + self._restricted_keys = restricted_keys + + self.create = async_to_raw_response_wrapper( + restricted_keys.create, + ) + + +class RestrictedKeysResourceWithStreamingResponse: + def __init__(self, restricted_keys: RestrictedKeysResource) -> None: + self._restricted_keys = restricted_keys + + self.create = to_streamed_response_wrapper( + restricted_keys.create, + ) + + +class AsyncRestrictedKeysResourceWithStreamingResponse: + def __init__(self, restricted_keys: AsyncRestrictedKeysResource) -> None: + self._restricted_keys = restricted_keys + + self.create = async_to_streamed_response_wrapper( + restricted_keys.create, + ) diff --git a/src/runloop_api_client/types/__init__.py b/src/runloop_api_client/types/__init__.py index da2236119..51e6407e5 100644 --- a/src/runloop_api_client/types/__init__.py +++ b/src/runloop_api_client/types/__init__.py @@ -30,6 +30,7 @@ from .axon_list_params import AxonListParams as AxonListParams from .devbox_list_view import DevboxListView as DevboxListView from .object_list_view import ObjectListView as ObjectListView +from .scope_entry_view import ScopeEntryView as ScopeEntryView from .scoring_contract import ScoringContract as ScoringContract from .scoring_function import ScoringFunction as ScoringFunction from .secret_list_view import SecretListView as SecretListView @@ -49,6 +50,8 @@ 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 .api_key_created_view import APIKeyCreatedView as APIKeyCreatedView +from .apikey_create_params import ApikeyCreateParams as ApikeyCreateParams from .devbox_create_params import DevboxCreateParams as DevboxCreateParams from .devbox_snapshot_view import DevboxSnapshotView as DevboxSnapshotView from .devbox_update_params import DevboxUpdateParams as DevboxUpdateParams @@ -68,6 +71,7 @@ from .scenario_create_params import ScenarioCreateParams as ScenarioCreateParams from .scenario_run_list_view import ScenarioRunListView as ScenarioRunListView from .scenario_update_params import ScenarioUpdateParams as ScenarioUpdateParams +from .scope_entry_view_param import ScopeEntryViewParam as ScopeEntryViewParam from .scoring_contract_param import ScoringContractParam as ScoringContractParam from .scoring_function_param import ScoringFunctionParam as ScoringFunctionParam from .benchmark_create_params import BenchmarkCreateParams as BenchmarkCreateParams @@ -103,6 +107,7 @@ from .devbox_enable_tunnel_params import DevboxEnableTunnelParams as DevboxEnableTunnelParams from .devbox_execute_async_params import DevboxExecuteAsyncParams as DevboxExecuteAsyncParams from .devbox_snapshot_disk_params import DevboxSnapshotDiskParams as DevboxSnapshotDiskParams +from .restricted_key_created_view import RestrictedKeyCreatedView as RestrictedKeyCreatedView from .scenario_list_public_params import ScenarioListPublicParams as ScenarioListPublicParams from .benchmark_definitions_params import BenchmarkDefinitionsParams as BenchmarkDefinitionsParams from .benchmark_list_public_params import BenchmarkListPublicParams as BenchmarkListPublicParams @@ -112,6 +117,7 @@ from .gateway_config_update_params import GatewayConfigUpdateParams as GatewayConfigUpdateParams from .network_policy_create_params import NetworkPolicyCreateParams as NetworkPolicyCreateParams from .network_policy_update_params import NetworkPolicyUpdateParams as NetworkPolicyUpdateParams +from .restricted_key_create_params import RestrictedKeyCreateParams as RestrictedKeyCreateParams from .scoring_contract_result_view import ScoringContractResultView as ScoringContractResultView from .scoring_function_result_view import ScoringFunctionResultView as ScoringFunctionResultView from .scenario_definition_list_view import ScenarioDefinitionListView as ScenarioDefinitionListView diff --git a/src/runloop_api_client/types/api_key_created_view.py b/src/runloop_api_client/types/api_key_created_view.py new file mode 100644 index 000000000..f48902c64 --- /dev/null +++ b/src/runloop_api_client/types/api_key_created_view.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["APIKeyCreatedView"] + + +class APIKeyCreatedView(BaseModel): + id: Optional[str] = None + + expires_at_ms: Optional[int] = None + + key_secret: Optional[str] = None + + name: Optional[str] = None diff --git a/src/runloop_api_client/types/apikey_create_params.py b/src/runloop_api_client/types/apikey_create_params.py new file mode 100644 index 000000000..106427199 --- /dev/null +++ b/src/runloop_api_client/types/apikey_create_params.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import TypedDict + +__all__ = ["ApikeyCreateParams"] + + +class ApikeyCreateParams(TypedDict, total=False): + expires_at_ms: Optional[int] + + name: str diff --git a/src/runloop_api_client/types/restricted_key_create_params.py b/src/runloop_api_client/types/restricted_key_create_params.py new file mode 100644 index 000000000..420e65a4e --- /dev/null +++ b/src/runloop_api_client/types/restricted_key_create_params.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable, Optional +from typing_extensions import TypedDict + +from .scope_entry_view_param import ScopeEntryViewParam + +__all__ = ["RestrictedKeyCreateParams"] + + +class RestrictedKeyCreateParams(TypedDict, total=False): + expires_at_ms: Optional[int] + + name: str + + scopes: Iterable[ScopeEntryViewParam] diff --git a/src/runloop_api_client/types/restricted_key_created_view.py b/src/runloop_api_client/types/restricted_key_created_view.py new file mode 100644 index 000000000..dab3875db --- /dev/null +++ b/src/runloop_api_client/types/restricted_key_created_view.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from .._models import BaseModel +from .scope_entry_view import ScopeEntryView + +__all__ = ["RestrictedKeyCreatedView"] + + +class RestrictedKeyCreatedView(BaseModel): + id: Optional[str] = None + + expires_at_ms: Optional[int] = None + + key_secret: Optional[str] = None + + name: Optional[str] = None + + scopes: Optional[List[ScopeEntryView]] = None diff --git a/src/runloop_api_client/types/scope_entry_view.py b/src/runloop_api_client/types/scope_entry_view.py new file mode 100644 index 000000000..1a0903895 --- /dev/null +++ b/src/runloop_api_client/types/scope_entry_view.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["ScopeEntryView"] + + +class ScopeEntryView(BaseModel): + access_level: Optional[Literal["ACCESS_LEVEL_NONE", "ACCESS_LEVEL_READ", "ACCESS_LEVEL_WRITE"]] = None + + resource_type: Optional[ + Literal[ + "RESOURCE_TYPE_DEVBOXES", + "RESOURCE_TYPE_BLUEPRINTS", + "RESOURCE_TYPE_SNAPSHOTS", + "RESOURCE_TYPE_BENCHMARKS", + "RESOURCE_TYPE_SCENARIOS", + "RESOURCE_TYPE_REPO_CONNECTIONS", + "RESOURCE_TYPE_AGENTS", + "RESOURCE_TYPE_OBJECTS", + "RESOURCE_TYPE_ACCOUNT", + ] + ] = None diff --git a/src/runloop_api_client/types/scope_entry_view_param.py b/src/runloop_api_client/types/scope_entry_view_param.py new file mode 100644 index 000000000..5379b838a --- /dev/null +++ b/src/runloop_api_client/types/scope_entry_view_param.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["ScopeEntryViewParam"] + + +class ScopeEntryViewParam(TypedDict, total=False): + access_level: Literal["ACCESS_LEVEL_NONE", "ACCESS_LEVEL_READ", "ACCESS_LEVEL_WRITE"] + + resource_type: Literal[ + "RESOURCE_TYPE_DEVBOXES", + "RESOURCE_TYPE_BLUEPRINTS", + "RESOURCE_TYPE_SNAPSHOTS", + "RESOURCE_TYPE_BENCHMARKS", + "RESOURCE_TYPE_SCENARIOS", + "RESOURCE_TYPE_REPO_CONNECTIONS", + "RESOURCE_TYPE_AGENTS", + "RESOURCE_TYPE_OBJECTS", + "RESOURCE_TYPE_ACCOUNT", + ] diff --git a/tests/api_resources/test_apikeys.py b/tests/api_resources/test_apikeys.py new file mode 100644 index 000000000..e96ff6111 --- /dev/null +++ b/tests/api_resources/test_apikeys.py @@ -0,0 +1,90 @@ +# 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 APIKeyCreatedView + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestApikeys: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: Runloop) -> None: + apikey = client.apikeys.create() + assert_matches_type(APIKeyCreatedView, apikey, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: Runloop) -> None: + apikey = client.apikeys.create( + expires_at_ms=0, + name="name", + ) + assert_matches_type(APIKeyCreatedView, apikey, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: Runloop) -> None: + response = client.apikeys.with_raw_response.create() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + apikey = response.parse() + assert_matches_type(APIKeyCreatedView, apikey, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: Runloop) -> None: + with client.apikeys.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + apikey = response.parse() + assert_matches_type(APIKeyCreatedView, apikey, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncApikeys: + 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: + apikey = await async_client.apikeys.create() + assert_matches_type(APIKeyCreatedView, apikey, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncRunloop) -> None: + apikey = await async_client.apikeys.create( + expires_at_ms=0, + name="name", + ) + assert_matches_type(APIKeyCreatedView, apikey, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncRunloop) -> None: + response = await async_client.apikeys.with_raw_response.create() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + apikey = await response.parse() + assert_matches_type(APIKeyCreatedView, apikey, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncRunloop) -> None: + async with async_client.apikeys.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + apikey = await response.parse() + assert_matches_type(APIKeyCreatedView, apikey, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_restricted_keys.py b/tests/api_resources/test_restricted_keys.py new file mode 100644 index 000000000..5e2bc4fed --- /dev/null +++ b/tests/api_resources/test_restricted_keys.py @@ -0,0 +1,102 @@ +# 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 RestrictedKeyCreatedView + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestRestrictedKeys: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: Runloop) -> None: + restricted_key = client.restricted_keys.create() + assert_matches_type(RestrictedKeyCreatedView, restricted_key, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: Runloop) -> None: + restricted_key = client.restricted_keys.create( + expires_at_ms=0, + name="name", + scopes=[ + { + "access_level": "ACCESS_LEVEL_NONE", + "resource_type": "RESOURCE_TYPE_DEVBOXES", + } + ], + ) + assert_matches_type(RestrictedKeyCreatedView, restricted_key, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: Runloop) -> None: + response = client.restricted_keys.with_raw_response.create() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + restricted_key = response.parse() + assert_matches_type(RestrictedKeyCreatedView, restricted_key, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: Runloop) -> None: + with client.restricted_keys.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + restricted_key = response.parse() + assert_matches_type(RestrictedKeyCreatedView, restricted_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncRestrictedKeys: + 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: + restricted_key = await async_client.restricted_keys.create() + assert_matches_type(RestrictedKeyCreatedView, restricted_key, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncRunloop) -> None: + restricted_key = await async_client.restricted_keys.create( + expires_at_ms=0, + name="name", + scopes=[ + { + "access_level": "ACCESS_LEVEL_NONE", + "resource_type": "RESOURCE_TYPE_DEVBOXES", + } + ], + ) + assert_matches_type(RestrictedKeyCreatedView, restricted_key, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncRunloop) -> None: + response = await async_client.restricted_keys.with_raw_response.create() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + restricted_key = await response.parse() + assert_matches_type(RestrictedKeyCreatedView, restricted_key, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncRunloop) -> None: + async with async_client.restricted_keys.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + restricted_key = await response.parse() + assert_matches_type(RestrictedKeyCreatedView, restricted_key, path=["response"]) + + assert cast(Any, response.is_closed) is True From f7ca2cc280f1e7a4ab73f331be8fe3e33ae1c611 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 11 Apr 2026 02:59:37 +0000 Subject: [PATCH 2/5] fix: ensure file data are only sent as 1 parameter --- src/runloop_api_client/_utils/_utils.py | 5 +++-- tests/test_extract_files.py | 9 +++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/runloop_api_client/_utils/_utils.py b/src/runloop_api_client/_utils/_utils.py index eec7f4a1f..63b8cd602 100644 --- a/src/runloop_api_client/_utils/_utils.py +++ b/src/runloop_api_client/_utils/_utils.py @@ -86,8 +86,9 @@ def _extract_items( index += 1 if is_dict(obj): try: - # We are at the last entry in the path so we must remove the field - if (len(path)) == index: + # Remove the field if there are no more dict keys in the path, + # only "" traversal markers or end. + if all(p == "" for p in path[index:]): item = obj.pop(key) else: item = obj[key] diff --git a/tests/test_extract_files.py b/tests/test_extract_files.py index 41498b836..a76b07d19 100644 --- a/tests/test_extract_files.py +++ b/tests/test_extract_files.py @@ -35,6 +35,15 @@ def test_multiple_files() -> None: assert query == {"documents": [{}, {}]} +def test_top_level_file_array() -> None: + query = {"files": [b"file one", b"file two"], "title": "hello"} + assert extract_files(query, paths=[["files", ""]]) == [ + ("files[]", b"file one"), + ("files[]", b"file two"), + ] + assert query == {"title": "hello"} + + @pytest.mark.parametrize( "query,paths,expected", [ From 255099a940a7892f3c66e6534517e3545d7daccb Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 03:06:14 +0000 Subject: [PATCH 3/5] feat: Add wake on axon event as an API primitive (#8681) --- .stats.yml | 4 +-- .../types/shared/launch_parameters.py | 3 ++ .../types/shared_params/launch_parameters.py | 3 ++ tests/api_resources/test_benchmarks.py | 10 +++++-- tests/api_resources/test_blueprints.py | 30 +++++++++++++++---- tests/api_resources/test_devboxes.py | 10 +++++-- tests/api_resources/test_scenarios.py | 30 +++++++++++++++---- 7 files changed, 72 insertions(+), 18 deletions(-) diff --git a/.stats.yml b/.stats.yml index 36aa46087..bddb40ca1 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 112 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-f1ad4729124d46b6b2f926f177680a9644c6701d7e7c87731df9bf21a6414546.yml -openapi_spec_hash: eb49220bba68581587f2d9d4d79d0c7e +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-5b536a11a713dd4e47b270c130368dbfdf1f30282f262c160cd0411fcd291806.yml +openapi_spec_hash: f94d993a7f34461ebde7d0186e8e3c3a config_hash: 636a44fb2f5d76373805b629a0f3dd35 diff --git a/src/runloop_api_client/types/shared/launch_parameters.py b/src/runloop_api_client/types/shared/launch_parameters.py index a1b52ec03..6e74af438 100644 --- a/src/runloop_api_client/types/shared/launch_parameters.py +++ b/src/runloop_api_client/types/shared/launch_parameters.py @@ -12,6 +12,9 @@ class LifecycleResumeTriggers(BaseModel): """Triggers that can resume a suspended Devbox.""" + axon_event: Optional[bool] = None + """When true, axon events targeting a suspended Devbox will trigger a resume.""" + http: Optional[bool] = None """When true, HTTP traffic to a suspended Devbox via tunnel will trigger a resume.""" 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 3906163bf..44ced4761 100644 --- a/src/runloop_api_client/types/shared_params/launch_parameters.py +++ b/src/runloop_api_client/types/shared_params/launch_parameters.py @@ -14,6 +14,9 @@ class LifecycleResumeTriggers(TypedDict, total=False): """Triggers that can resume a suspended Devbox.""" + axon_event: Optional[bool] + """When true, axon events targeting a suspended Devbox will trigger a resume.""" + http: Optional[bool] """When true, HTTP traffic to a suspended Devbox via tunnel will trigger a resume.""" diff --git a/tests/api_resources/test_benchmarks.py b/tests/api_resources/test_benchmarks.py index 924deb133..7612b34f2 100644 --- a/tests/api_resources/test_benchmarks.py +++ b/tests/api_resources/test_benchmarks.py @@ -304,7 +304,10 @@ def test_method_start_run_with_all_params(self, client: Runloop) -> None: "idle_time_seconds": 0, "on_idle": "shutdown", }, - "resume_triggers": {"http": True}, + "resume_triggers": { + "axon_event": True, + "http": True, + }, }, "network_policy_id": "network_policy_id", "required_services": ["string"], @@ -686,7 +689,10 @@ async def test_method_start_run_with_all_params(self, async_client: AsyncRunloop "idle_time_seconds": 0, "on_idle": "shutdown", }, - "resume_triggers": {"http": True}, + "resume_triggers": { + "axon_event": True, + "http": True, + }, }, "network_policy_id": "network_policy_id", "required_services": ["string"], diff --git a/tests/api_resources/test_blueprints.py b/tests/api_resources/test_blueprints.py index 89afc9e4b..dd3e19fcb 100644 --- a/tests/api_resources/test_blueprints.py +++ b/tests/api_resources/test_blueprints.py @@ -70,7 +70,10 @@ def test_method_create_with_all_params(self, client: Runloop) -> None: "idle_time_seconds": 0, "on_idle": "shutdown", }, - "resume_triggers": {"http": True}, + "resume_triggers": { + "axon_event": True, + "http": True, + }, }, "network_policy_id": "network_policy_id", "required_services": ["string"], @@ -292,7 +295,10 @@ def test_method_create_from_inspection_with_all_params(self, client: Runloop) -> "idle_time_seconds": 0, "on_idle": "shutdown", }, - "resume_triggers": {"http": True}, + "resume_triggers": { + "axon_event": True, + "http": True, + }, }, "network_policy_id": "network_policy_id", "required_services": ["string"], @@ -458,7 +464,10 @@ def test_method_preview_with_all_params(self, client: Runloop) -> None: "idle_time_seconds": 0, "on_idle": "shutdown", }, - "resume_triggers": {"http": True}, + "resume_triggers": { + "axon_event": True, + "http": True, + }, }, "network_policy_id": "network_policy_id", "required_services": ["string"], @@ -567,7 +576,10 @@ async def test_method_create_with_all_params(self, async_client: AsyncRunloop) - "idle_time_seconds": 0, "on_idle": "shutdown", }, - "resume_triggers": {"http": True}, + "resume_triggers": { + "axon_event": True, + "http": True, + }, }, "network_policy_id": "network_policy_id", "required_services": ["string"], @@ -789,7 +801,10 @@ async def test_method_create_from_inspection_with_all_params(self, async_client: "idle_time_seconds": 0, "on_idle": "shutdown", }, - "resume_triggers": {"http": True}, + "resume_triggers": { + "axon_event": True, + "http": True, + }, }, "network_policy_id": "network_policy_id", "required_services": ["string"], @@ -955,7 +970,10 @@ async def test_method_preview_with_all_params(self, async_client: AsyncRunloop) "idle_time_seconds": 0, "on_idle": "shutdown", }, - "resume_triggers": {"http": True}, + "resume_triggers": { + "axon_event": True, + "http": True, + }, }, "network_policy_id": "network_policy_id", "required_services": ["string"], diff --git a/tests/api_resources/test_devboxes.py b/tests/api_resources/test_devboxes.py index ec948f719..920ff437a 100644 --- a/tests/api_resources/test_devboxes.py +++ b/tests/api_resources/test_devboxes.py @@ -90,7 +90,10 @@ def test_method_create_with_all_params(self, client: Runloop) -> None: "idle_time_seconds": 0, "on_idle": "shutdown", }, - "resume_triggers": {"http": True}, + "resume_triggers": { + "axon_event": True, + "http": True, + }, }, "network_policy_id": "network_policy_id", "required_services": ["string"], @@ -1714,7 +1717,10 @@ async def test_method_create_with_all_params(self, async_client: AsyncRunloop) - "idle_time_seconds": 0, "on_idle": "shutdown", }, - "resume_triggers": {"http": True}, + "resume_triggers": { + "axon_event": True, + "http": True, + }, }, "network_policy_id": "network_policy_id", "required_services": ["string"], diff --git a/tests/api_resources/test_scenarios.py b/tests/api_resources/test_scenarios.py index 748d153c8..9dd8c3e63 100644 --- a/tests/api_resources/test_scenarios.py +++ b/tests/api_resources/test_scenarios.py @@ -83,7 +83,10 @@ def test_method_create_with_all_params(self, client: Runloop) -> None: "idle_time_seconds": 0, "on_idle": "shutdown", }, - "resume_triggers": {"http": True}, + "resume_triggers": { + "axon_event": True, + "http": True, + }, }, "network_policy_id": "network_policy_id", "required_services": ["string"], @@ -225,7 +228,10 @@ def test_method_update_with_all_params(self, client: Runloop) -> None: "idle_time_seconds": 0, "on_idle": "shutdown", }, - "resume_triggers": {"http": True}, + "resume_triggers": { + "axon_event": True, + "http": True, + }, }, "network_policy_id": "network_policy_id", "required_services": ["string"], @@ -440,7 +446,10 @@ def test_method_start_run_with_all_params(self, client: Runloop) -> None: "idle_time_seconds": 0, "on_idle": "shutdown", }, - "resume_triggers": {"http": True}, + "resume_triggers": { + "axon_event": True, + "http": True, + }, }, "network_policy_id": "network_policy_id", "required_services": ["string"], @@ -555,7 +564,10 @@ async def test_method_create_with_all_params(self, async_client: AsyncRunloop) - "idle_time_seconds": 0, "on_idle": "shutdown", }, - "resume_triggers": {"http": True}, + "resume_triggers": { + "axon_event": True, + "http": True, + }, }, "network_policy_id": "network_policy_id", "required_services": ["string"], @@ -697,7 +709,10 @@ async def test_method_update_with_all_params(self, async_client: AsyncRunloop) - "idle_time_seconds": 0, "on_idle": "shutdown", }, - "resume_triggers": {"http": True}, + "resume_triggers": { + "axon_event": True, + "http": True, + }, }, "network_policy_id": "network_policy_id", "required_services": ["string"], @@ -912,7 +927,10 @@ async def test_method_start_run_with_all_params(self, async_client: AsyncRunloop "idle_time_seconds": 0, "on_idle": "shutdown", }, - "resume_triggers": {"http": True}, + "resume_triggers": { + "axon_event": True, + "http": True, + }, }, "network_policy_id": "network_policy_id", "required_services": ["string"], From 4f6fe60a63fd31c049e54febb81801031dce2325 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 23:29:10 +0000 Subject: [PATCH 4/5] fix: add missing agent API paths to stainless config (#8699) --- .stats.yml | 4 +- api.md | 10 +- src/runloop_api_client/resources/agents.py | 297 +++++++++++++++++- src/runloop_api_client/types/__init__.py | 2 + .../types/agent_devbox_counts_view.py | 24 ++ .../types/agent_list_public_params.py | 30 ++ tests/api_resources/test_agents.py | 205 +++++++++++- 7 files changed, 567 insertions(+), 5 deletions(-) create mode 100644 src/runloop_api_client/types/agent_devbox_counts_view.py create mode 100644 src/runloop_api_client/types/agent_list_public_params.py diff --git a/.stats.yml b/.stats.yml index bddb40ca1..e948713b6 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 112 +configured_endpoints: 115 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-5b536a11a713dd4e47b270c130368dbfdf1f30282f262c160cd0411fcd291806.yml openapi_spec_hash: f94d993a7f34461ebde7d0186e8e3c3a -config_hash: 636a44fb2f5d76373805b629a0f3dd35 +config_hash: 12de9459ff629b6a3072a75b236b7b70 diff --git a/api.md b/api.md index 3952222dd..1d97dc90c 100644 --- a/api.md +++ b/api.md @@ -79,7 +79,12 @@ Methods: Types: ```python -from runloop_api_client.types import AgentCreateParameters, AgentListView, AgentView +from runloop_api_client.types import ( + AgentCreateParameters, + AgentDevboxCountsView, + AgentListView, + AgentView, +) ``` Methods: @@ -87,6 +92,9 @@ Methods: - client.agents.create(\*\*params) -> AgentView - client.agents.retrieve(id) -> AgentView - client.agents.list(\*\*params) -> SyncAgentsCursorIDPage[AgentView] +- client.agents.delete(id) -> object +- client.agents.devbox_counts() -> AgentDevboxCountsView +- client.agents.list_public(\*\*params) -> SyncAgentsCursorIDPage[AgentView] # Axons diff --git a/src/runloop_api_client/resources/agents.py b/src/runloop_api_client/resources/agents.py index 082d5725c..6febe22f0 100644 --- a/src/runloop_api_client/resources/agents.py +++ b/src/runloop_api_client/resources/agents.py @@ -6,7 +6,7 @@ import httpx -from ..types import agent_list_params, agent_create_params +from ..types import agent_list_params, agent_create_params, agent_list_public_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 @@ -20,6 +20,7 @@ from ..pagination import SyncAgentsCursorIDPage, AsyncAgentsCursorIDPage from .._base_client import AsyncPaginator, make_request_options from ..types.agent_view import AgentView +from ..types.agent_devbox_counts_view import AgentDevboxCountsView from ..types.shared_params.agent_source import AgentSource __all__ = ["AgentsResource", "AsyncAgentsResource"] @@ -202,6 +203,135 @@ def list( model=AgentView, ) + def delete( + 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, + idempotency_key: str | None = None, + ) -> object: + """Delete an Agent by its unique identifier. + + The Agent will be permanently removed. + + 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 + + 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/agents/{id}/delete", id=id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=object, + ) + + def devbox_counts( + 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, + ) -> AgentDevboxCountsView: + """Returns devbox counts grouped by agent name. + + This endpoint efficiently + aggregates devbox counts for all agents in a single request, avoiding N+1 query + patterns. + """ + return self._get( + "/v1/agents/devbox_counts", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AgentDevboxCountsView, + ) + + def list_public( + self, + *, + include_total_count: bool | Omit = omit, + limit: int | Omit = omit, + name: str | Omit = omit, + search: str | Omit = omit, + starting_after: str | Omit = omit, + version: 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, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncAgentsCursorIDPage[AgentView]: + """ + List all public Agents 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. + + limit: The limit of items to return. Default is 20. Max is 5000. + + name: Filter agents by name (partial match supported). + + search: Search by agent ID or name. + + starting_after: Load the next page of data starting after the item with the given ID. + + version: Filter by version. Use 'latest' to get the most recently created agent. + + 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 + """ + return self._get_api_list( + "/v1/agents/list_public", + page=SyncAgentsCursorIDPage[AgentView], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "include_total_count": include_total_count, + "limit": limit, + "name": name, + "search": search, + "starting_after": starting_after, + "version": version, + }, + agent_list_public_params.AgentListPublicParams, + ), + ), + model=AgentView, + ) + class AsyncAgentsResource(AsyncAPIResource): @cached_property @@ -380,6 +510,135 @@ def list( model=AgentView, ) + async def delete( + 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, + idempotency_key: str | None = None, + ) -> object: + """Delete an Agent by its unique identifier. + + The Agent will be permanently removed. + + 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 + + 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/agents/{id}/delete", id=id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=object, + ) + + async def devbox_counts( + 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, + ) -> AgentDevboxCountsView: + """Returns devbox counts grouped by agent name. + + This endpoint efficiently + aggregates devbox counts for all agents in a single request, avoiding N+1 query + patterns. + """ + return await self._get( + "/v1/agents/devbox_counts", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AgentDevboxCountsView, + ) + + def list_public( + self, + *, + include_total_count: bool | Omit = omit, + limit: int | Omit = omit, + name: str | Omit = omit, + search: str | Omit = omit, + starting_after: str | Omit = omit, + version: 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, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[AgentView, AsyncAgentsCursorIDPage[AgentView]]: + """ + List all public Agents 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. + + limit: The limit of items to return. Default is 20. Max is 5000. + + name: Filter agents by name (partial match supported). + + search: Search by agent ID or name. + + starting_after: Load the next page of data starting after the item with the given ID. + + version: Filter by version. Use 'latest' to get the most recently created agent. + + 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 + """ + return self._get_api_list( + "/v1/agents/list_public", + page=AsyncAgentsCursorIDPage[AgentView], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "include_total_count": include_total_count, + "limit": limit, + "name": name, + "search": search, + "starting_after": starting_after, + "version": version, + }, + agent_list_public_params.AgentListPublicParams, + ), + ), + model=AgentView, + ) + class AgentsResourceWithRawResponse: def __init__(self, agents: AgentsResource) -> None: @@ -394,6 +653,15 @@ def __init__(self, agents: AgentsResource) -> None: self.list = to_raw_response_wrapper( agents.list, ) + self.delete = to_raw_response_wrapper( + agents.delete, + ) + self.devbox_counts = to_raw_response_wrapper( + agents.devbox_counts, + ) + self.list_public = to_raw_response_wrapper( + agents.list_public, + ) class AsyncAgentsResourceWithRawResponse: @@ -409,6 +677,15 @@ def __init__(self, agents: AsyncAgentsResource) -> None: self.list = async_to_raw_response_wrapper( agents.list, ) + self.delete = async_to_raw_response_wrapper( + agents.delete, + ) + self.devbox_counts = async_to_raw_response_wrapper( + agents.devbox_counts, + ) + self.list_public = async_to_raw_response_wrapper( + agents.list_public, + ) class AgentsResourceWithStreamingResponse: @@ -424,6 +701,15 @@ def __init__(self, agents: AgentsResource) -> None: self.list = to_streamed_response_wrapper( agents.list, ) + self.delete = to_streamed_response_wrapper( + agents.delete, + ) + self.devbox_counts = to_streamed_response_wrapper( + agents.devbox_counts, + ) + self.list_public = to_streamed_response_wrapper( + agents.list_public, + ) class AsyncAgentsResourceWithStreamingResponse: @@ -439,3 +725,12 @@ def __init__(self, agents: AsyncAgentsResource) -> None: self.list = async_to_streamed_response_wrapper( agents.list, ) + self.delete = async_to_streamed_response_wrapper( + agents.delete, + ) + self.devbox_counts = async_to_streamed_response_wrapper( + agents.devbox_counts, + ) + self.list_public = async_to_streamed_response_wrapper( + agents.list_public, + ) diff --git a/src/runloop_api_client/types/__init__.py b/src/runloop_api_client/types/__init__.py index 51e6407e5..01130880d 100644 --- a/src/runloop_api_client/types/__init__.py +++ b/src/runloop_api_client/types/__init__.py @@ -80,6 +80,8 @@ from .benchmark_update_params import BenchmarkUpdateParams as BenchmarkUpdateParams from .blueprint_create_params import BlueprintCreateParams as BlueprintCreateParams from .inspection_source_param import InspectionSourceParam as InspectionSourceParam +from .agent_devbox_counts_view import AgentDevboxCountsView as AgentDevboxCountsView +from .agent_list_public_params import AgentListPublicParams as AgentListPublicParams from .blueprint_preview_params import BlueprintPreviewParams as BlueprintPreviewParams from .gateway_config_list_view import GatewayConfigListView as GatewayConfigListView from .mcp_config_create_params import McpConfigCreateParams as McpConfigCreateParams diff --git a/src/runloop_api_client/types/agent_devbox_counts_view.py b/src/runloop_api_client/types/agent_devbox_counts_view.py new file mode 100644 index 000000000..af9006d1e --- /dev/null +++ b/src/runloop_api_client/types/agent_devbox_counts_view.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict + +from .._models import BaseModel + +__all__ = ["AgentDevboxCountsView"] + + +class AgentDevboxCountsView(BaseModel): + """Devbox counts grouped by agent name. + + Used to efficiently fetch devbox counts for multiple agents in a single request. + """ + + counts: Dict[str, int] + """Map of agent name to devbox count. + + Each key is an agent name, and the value is the count of devboxes associated + with that agent. + """ + + total_count: int + """Total count of devboxes across all agents in the result.""" diff --git a/src/runloop_api_client/types/agent_list_public_params.py b/src/runloop_api_client/types/agent_list_public_params.py new file mode 100644 index 000000000..a0e53d503 --- /dev/null +++ b/src/runloop_api_client/types/agent_list_public_params.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["AgentListPublicParams"] + + +class AgentListPublicParams(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.""" + + name: str + """Filter agents by name (partial match supported).""" + + search: str + """Search by agent ID or name.""" + + starting_after: str + """Load the next page of data starting after the item with the given ID.""" + + version: str + """Filter by version. Use 'latest' to get the most recently created agent.""" diff --git a/tests/api_resources/test_agents.py b/tests/api_resources/test_agents.py index a6304d70d..fb602d148 100644 --- a/tests/api_resources/test_agents.py +++ b/tests/api_resources/test_agents.py @@ -9,7 +9,10 @@ from tests.utils import assert_matches_type from runloop_api_client import Runloop, AsyncRunloop -from runloop_api_client.types import AgentView +from runloop_api_client.types import ( + AgentView, + AgentDevboxCountsView, +) from runloop_api_client.pagination import SyncAgentsCursorIDPage, AsyncAgentsCursorIDPage base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -158,6 +161,106 @@ def test_streaming_response_list(self, client: Runloop) -> None: assert cast(Any, response.is_closed) is True + @parametrize + def test_method_delete(self, client: Runloop) -> None: + agent = client.agents.delete( + "id", + ) + assert_matches_type(object, agent, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: Runloop) -> None: + response = client.agents.with_raw_response.delete( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + agent = response.parse() + assert_matches_type(object, agent, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: Runloop) -> None: + with client.agents.with_streaming_response.delete( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + agent = response.parse() + assert_matches_type(object, agent, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: Runloop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.agents.with_raw_response.delete( + "", + ) + + @parametrize + def test_method_devbox_counts(self, client: Runloop) -> None: + agent = client.agents.devbox_counts() + assert_matches_type(AgentDevboxCountsView, agent, path=["response"]) + + @parametrize + def test_raw_response_devbox_counts(self, client: Runloop) -> None: + response = client.agents.with_raw_response.devbox_counts() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + agent = response.parse() + assert_matches_type(AgentDevboxCountsView, agent, path=["response"]) + + @parametrize + def test_streaming_response_devbox_counts(self, client: Runloop) -> None: + with client.agents.with_streaming_response.devbox_counts() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + agent = response.parse() + assert_matches_type(AgentDevboxCountsView, agent, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_list_public(self, client: Runloop) -> None: + agent = client.agents.list_public() + assert_matches_type(SyncAgentsCursorIDPage[AgentView], agent, path=["response"]) + + @parametrize + def test_method_list_public_with_all_params(self, client: Runloop) -> None: + agent = client.agents.list_public( + include_total_count=True, + limit=0, + name="name", + search="search", + starting_after="starting_after", + version="version", + ) + assert_matches_type(SyncAgentsCursorIDPage[AgentView], agent, path=["response"]) + + @parametrize + def test_raw_response_list_public(self, client: Runloop) -> None: + response = client.agents.with_raw_response.list_public() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + agent = response.parse() + assert_matches_type(SyncAgentsCursorIDPage[AgentView], agent, path=["response"]) + + @parametrize + def test_streaming_response_list_public(self, client: Runloop) -> None: + with client.agents.with_streaming_response.list_public() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + agent = response.parse() + assert_matches_type(SyncAgentsCursorIDPage[AgentView], agent, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncAgents: parametrize = pytest.mark.parametrize( @@ -303,3 +406,103 @@ async def test_streaming_response_list(self, async_client: AsyncRunloop) -> None assert_matches_type(AsyncAgentsCursorIDPage[AgentView], agent, path=["response"]) assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_delete(self, async_client: AsyncRunloop) -> None: + agent = await async_client.agents.delete( + "id", + ) + assert_matches_type(object, agent, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncRunloop) -> None: + response = await async_client.agents.with_raw_response.delete( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + agent = await response.parse() + assert_matches_type(object, agent, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncRunloop) -> None: + async with async_client.agents.with_streaming_response.delete( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + agent = await response.parse() + assert_matches_type(object, agent, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncRunloop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.agents.with_raw_response.delete( + "", + ) + + @parametrize + async def test_method_devbox_counts(self, async_client: AsyncRunloop) -> None: + agent = await async_client.agents.devbox_counts() + assert_matches_type(AgentDevboxCountsView, agent, path=["response"]) + + @parametrize + async def test_raw_response_devbox_counts(self, async_client: AsyncRunloop) -> None: + response = await async_client.agents.with_raw_response.devbox_counts() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + agent = await response.parse() + assert_matches_type(AgentDevboxCountsView, agent, path=["response"]) + + @parametrize + async def test_streaming_response_devbox_counts(self, async_client: AsyncRunloop) -> None: + async with async_client.agents.with_streaming_response.devbox_counts() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + agent = await response.parse() + assert_matches_type(AgentDevboxCountsView, agent, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_list_public(self, async_client: AsyncRunloop) -> None: + agent = await async_client.agents.list_public() + assert_matches_type(AsyncAgentsCursorIDPage[AgentView], agent, path=["response"]) + + @parametrize + async def test_method_list_public_with_all_params(self, async_client: AsyncRunloop) -> None: + agent = await async_client.agents.list_public( + include_total_count=True, + limit=0, + name="name", + search="search", + starting_after="starting_after", + version="version", + ) + assert_matches_type(AsyncAgentsCursorIDPage[AgentView], agent, path=["response"]) + + @parametrize + async def test_raw_response_list_public(self, async_client: AsyncRunloop) -> None: + response = await async_client.agents.with_raw_response.list_public() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + agent = await response.parse() + assert_matches_type(AsyncAgentsCursorIDPage[AgentView], agent, path=["response"]) + + @parametrize + async def test_streaming_response_list_public(self, async_client: AsyncRunloop) -> None: + async with async_client.agents.with_streaming_response.list_public() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + agent = await response.parse() + assert_matches_type(AsyncAgentsCursorIDPage[AgentView], agent, path=["response"]) + + assert cast(Any, response.is_closed) is True From 0d1d616637422d1a5762123f194e9aaef0639bf4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 23:29:32 +0000 Subject: [PATCH 5/5] release: 1.19.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 23 +++++++++++++++++++++++ pyproject.toml | 2 +- src/runloop_api_client/_version.py | 2 +- 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index aa06f8634..de44c40d8 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.18.1" + ".": "1.19.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index e01266433..afa8cdb3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # Changelog +## 1.19.0 (2026-04-13) + +Full Changelog: [v1.18.1...v1.19.0](https://github.com/runloopai/api-client-python/compare/v1.18.1...v1.19.0) + +### Features + +* Add wake on axon event as an API primitive ([#8681](https://github.com/runloopai/api-client-python/issues/8681)) ([255099a](https://github.com/runloopai/api-client-python/commit/255099a940a7892f3c66e6534517e3545d7daccb)) +* Allow API keys to create API and restricted keys ([#8663](https://github.com/runloopai/api-client-python/issues/8663)) ([2e03b55](https://github.com/runloopai/api-client-python/commit/2e03b55638a83a8354481f47a30910e94ff57fc3)) + + +### Bug Fixes + +* add missing agent API paths to stainless config ([#8699](https://github.com/runloopai/api-client-python/issues/8699)) ([4f6fe60](https://github.com/runloopai/api-client-python/commit/4f6fe60a63fd31c049e54febb81801031dce2325)) +* ensure file data are only sent as 1 parameter ([f7ca2cc](https://github.com/runloopai/api-client-python/commit/f7ca2cc280f1e7a4ab73f331be8fe3e33ae1c611)) + + +### Documentation + +* add snapshot, suspend & resume example ([#764](https://github.com/runloopai/api-client-python/issues/764)) ([f4b942b](https://github.com/runloopai/api-client-python/commit/f4b942b3329e98a4ea5182c04d6db90fd0b70308)) +* added Async vs Sync tabs, removed top-level class descriptions ([#783](https://github.com/runloopai/api-client-python/issues/783)) ([6a99c56](https://github.com/runloopai/api-client-python/commit/6a99c569914d93e07087ed5003ee7c77f2ab88ff)) +* restructure sphinx docs with async-first API reference and full type coverage ([#782](https://github.com/runloopai/api-client-python/issues/782)) ([514cf93](https://github.com/runloopai/api-client-python/commit/514cf93e8ff8b97bfde171a83cf9abf211193e7f)) +* show full field descriptions for SDK parameter types ([#784](https://github.com/runloopai/api-client-python/issues/784)) ([f04a506](https://github.com/runloopai/api-client-python/commit/f04a5069c33fceb7a4d3db634bfd77ab789314dc)) + ## 1.18.1 (2026-04-10) Full Changelog: [v1.17.0...v1.18.1](https://github.com/runloopai/api-client-python/compare/v1.17.0...v1.18.1) diff --git a/pyproject.toml b/pyproject.toml index 2c3dbdb53..64ca9ee03 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "runloop_api_client" -version = "1.18.1" +version = "1.19.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 4ca4fa13d..63342c137 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.18.1" # x-release-please-version +__version__ = "1.19.0" # x-release-please-version