diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 4a2f7e609..cce9d1c6d 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.5.1" + ".": "1.7.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 81d520c67..73c283357 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-b493cadf2e3b9658163a6bcf8f51e088dda169f12d68469c4441d17e889f5556.yml -openapi_spec_hash: b27ec3822d88d10efa268f1681fddff3 -config_hash: 6c26299fd9ef01fb4713612a9a2ad17c +configured_endpoints: 117 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-15b7fd06098d8cb3639efb3b401a03d7d97a8ab4960b08077aa871f0cacb33d3.yml +openapi_spec_hash: 93ab2fe88f9e57d8f262ad6d1179190e +config_hash: eb28692edd68a6ae95cf92af931c9976 diff --git a/CHANGELOG.md b/CHANGELOG.md index 55d18da5c..569b852a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Changelog +## 1.7.0 (2026-02-05) + +Full Changelog: [v1.5.1...v1.7.0](https://github.com/runloopai/api-client-python/compare/v1.5.1...v1.7.0) + +### Features + +* add api to query devbox usage ([#7296](https://github.com/runloopai/api-client-python/issues/7296)) ([ab903f5](https://github.com/runloopai/api-client-python/commit/ab903f557ecc8be15c8d150068a82dfaaac23f4e)) +* adding MCP Config routes ([#7331](https://github.com/runloopai/api-client-python/issues/7331)) ([79d05c0](https://github.com/runloopai/api-client-python/commit/79d05c00f9bb85a458cc300c5b065b6249a16a88)) +* **devbox:** add mcp configuration to devbox create ([#7341](https://github.com/runloopai/api-client-python/issues/7341)) ([0b81e31](https://github.com/runloopai/api-client-python/commit/0b81e3151e3df41f667923cac4e714ba6d7ca3bc)) +* **devbox:** adding gateway config ([#736](https://github.com/runloopai/api-client-python/issues/736)) ([9f4b49d](https://github.com/runloopai/api-client-python/commit/9f4b49daa13dd3aac5a8ee26a262fd7b5b705ff1)) + + +### Bug Fixes + +* **sdk:** fixed pagination for scorer, scenario and network policy list methods ([#731](https://github.com/runloopai/api-client-python/issues/731)) ([d55fceb](https://github.com/runloopai/api-client-python/commit/d55fceb7d42877cd0ac69a934ef300815f371342)) + + +### Chores + +* Mark legacy tunnel methods deprecated ([#737](https://github.com/runloopai/api-client-python/issues/737)) ([764b445](https://github.com/runloopai/api-client-python/commit/764b445018ec50db3105c0dd8ea58b02e4690e12)) + ## 1.5.1 (2026-01-30) Full Changelog: [v1.5.0...v1.5.1](https://github.com/runloopai/api-client-python/compare/v1.5.0...v1.5.1) diff --git a/api.md b/api.md index 2720824ad..6cbe1e604 100644 --- a/api.md +++ b/api.md @@ -126,6 +126,7 @@ from runloop_api_client.types import ( DevboxExecutionDetailView, DevboxKillExecutionRequest, DevboxListView, + DevboxResourceUsageView, DevboxSendStdInRequest, DevboxSendStdInResult, DevboxSnapshotListView, @@ -158,6 +159,7 @@ Methods: - client.devboxes.read_file_contents(id, \*\*params) -> str - client.devboxes.remove_tunnel(id, \*\*params) -> object - client.devboxes.resume(id) -> DevboxView +- client.devboxes.retrieve_resource_usage(id) -> DevboxResourceUsageView - client.devboxes.shutdown(id) -> DevboxView - client.devboxes.snapshot_disk(id, \*\*params) -> DevboxSnapshotView - client.devboxes.snapshot_disk_async(id, \*\*params) -> DevboxSnapshotView @@ -299,7 +301,6 @@ from runloop_api_client.types.scenarios import ( ScorerRetrieveResponse, ScorerUpdateResponse, ScorerListResponse, - ScorerValidateResponse, ) ``` @@ -309,7 +310,6 @@ Methods: - client.scenarios.scorers.retrieve(id) -> ScorerRetrieveResponse - client.scenarios.scorers.update(id, \*\*params) -> ScorerUpdateResponse - client.scenarios.scorers.list(\*\*params) -> SyncScenarioScorersCursorIDPage[ScorerListResponse] -- client.scenarios.scorers.validate(id, \*\*params) -> ScorerValidateResponse # Objects @@ -420,3 +420,24 @@ Methods: - client.gateway_configs.update(id, \*\*params) -> GatewayConfigView - client.gateway_configs.list(\*\*params) -> SyncGatewayConfigsCursorIDPage[GatewayConfigView] - client.gateway_configs.delete(id) -> GatewayConfigView + +# McpConfigs + +Types: + +```python +from runloop_api_client.types import ( + McpConfigCreateParameters, + McpConfigListView, + McpConfigUpdateParameters, + McpConfigView, +) +``` + +Methods: + +- client.mcp_configs.create(\*\*params) -> McpConfigView +- client.mcp_configs.retrieve(id) -> McpConfigView +- client.mcp_configs.update(id, \*\*params) -> McpConfigView +- client.mcp_configs.list(\*\*params) -> SyncMcpConfigsCursorIDPage[McpConfigView] +- client.mcp_configs.delete(id) -> McpConfigView diff --git a/docs/sdk/types.rst b/docs/sdk/types.rst index 9d2983cd0..ab6abafee 100644 --- a/docs/sdk/types.rst +++ b/docs/sdk/types.rst @@ -81,7 +81,7 @@ These TypeDicts define parameters for storage object creation, listing, and down Scorer Parameters ----------------- -These TypeDicts define parameters for scorer creation, listing, updating, and validation. +These TypeDicts define parameters for scorer creation, listing, and updating. .. autotypeddict:: runloop_api_client.sdk._types.SDKScorerCreateParams @@ -89,8 +89,6 @@ These TypeDicts define parameters for scorer creation, listing, updating, and va .. autotypeddict:: runloop_api_client.sdk._types.SDKScorerUpdateParams -.. autotypeddict:: runloop_api_client.sdk._types.SDKScorerValidateParams - Core Request Options -------------------- diff --git a/pyproject.toml b/pyproject.toml index 60897f964..44b3bde57 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "runloop_api_client" -version = "1.5.1" +version = "1.7.0" description = "The official Python library for the runloop API" dynamic = ["readme"] license = "MIT" diff --git a/src/runloop_api_client/_client.py b/src/runloop_api_client/_client.py index 32561bc0f..0f626950b 100644 --- a/src/runloop_api_client/_client.py +++ b/src/runloop_api_client/_client.py @@ -39,6 +39,7 @@ scenarios, benchmarks, blueprints, + mcp_configs, repositories, benchmark_jobs, benchmark_runs, @@ -50,6 +51,7 @@ from .resources.secrets import SecretsResource, AsyncSecretsResource from .resources.benchmarks import BenchmarksResource, AsyncBenchmarksResource from .resources.blueprints import BlueprintsResource, AsyncBlueprintsResource + from .resources.mcp_configs import McpConfigsResource, AsyncMcpConfigsResource from .resources.repositories import RepositoriesResource, AsyncRepositoriesResource from .resources.benchmark_jobs import BenchmarkJobsResource, AsyncBenchmarkJobsResource from .resources.benchmark_runs import BenchmarkRunsResource, AsyncBenchmarkRunsResource @@ -190,6 +192,12 @@ def gateway_configs(self) -> GatewayConfigsResource: return GatewayConfigsResource(self) + @cached_property + def mcp_configs(self) -> McpConfigsResource: + from .resources.mcp_configs import McpConfigsResource + + return McpConfigsResource(self) + @cached_property def with_raw_response(self) -> RunloopWithRawResponse: return RunloopWithRawResponse(self) @@ -432,6 +440,12 @@ def gateway_configs(self) -> AsyncGatewayConfigsResource: return AsyncGatewayConfigsResource(self) + @cached_property + def mcp_configs(self) -> AsyncMcpConfigsResource: + from .resources.mcp_configs import AsyncMcpConfigsResource + + return AsyncMcpConfigsResource(self) + @cached_property def with_raw_response(self) -> AsyncRunloopWithRawResponse: return AsyncRunloopWithRawResponse(self) @@ -623,6 +637,12 @@ def gateway_configs(self) -> gateway_configs.GatewayConfigsResourceWithRawRespon return GatewayConfigsResourceWithRawResponse(self._client.gateway_configs) + @cached_property + def mcp_configs(self) -> mcp_configs.McpConfigsResourceWithRawResponse: + from .resources.mcp_configs import McpConfigsResourceWithRawResponse + + return McpConfigsResourceWithRawResponse(self._client.mcp_configs) + class AsyncRunloopWithRawResponse: _client: AsyncRunloop @@ -702,6 +722,12 @@ def gateway_configs(self) -> gateway_configs.AsyncGatewayConfigsResourceWithRawR return AsyncGatewayConfigsResourceWithRawResponse(self._client.gateway_configs) + @cached_property + def mcp_configs(self) -> mcp_configs.AsyncMcpConfigsResourceWithRawResponse: + from .resources.mcp_configs import AsyncMcpConfigsResourceWithRawResponse + + return AsyncMcpConfigsResourceWithRawResponse(self._client.mcp_configs) + class RunloopWithStreamedResponse: _client: Runloop @@ -781,6 +807,12 @@ def gateway_configs(self) -> gateway_configs.GatewayConfigsResourceWithStreaming return GatewayConfigsResourceWithStreamingResponse(self._client.gateway_configs) + @cached_property + def mcp_configs(self) -> mcp_configs.McpConfigsResourceWithStreamingResponse: + from .resources.mcp_configs import McpConfigsResourceWithStreamingResponse + + return McpConfigsResourceWithStreamingResponse(self._client.mcp_configs) + class AsyncRunloopWithStreamedResponse: _client: AsyncRunloop @@ -860,6 +892,12 @@ def gateway_configs(self) -> gateway_configs.AsyncGatewayConfigsResourceWithStre return AsyncGatewayConfigsResourceWithStreamingResponse(self._client.gateway_configs) + @cached_property + def mcp_configs(self) -> mcp_configs.AsyncMcpConfigsResourceWithStreamingResponse: + from .resources.mcp_configs import AsyncMcpConfigsResourceWithStreamingResponse + + return AsyncMcpConfigsResourceWithStreamingResponse(self._client.mcp_configs) + Client = Runloop diff --git a/src/runloop_api_client/_version.py b/src/runloop_api_client/_version.py index 962984a45..da0fd9d41 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.5.1" # x-release-please-version +__version__ = "1.7.0" # x-release-please-version diff --git a/src/runloop_api_client/pagination.py b/src/runloop_api_client/pagination.py index e937259d7..e084ef68e 100644 --- a/src/runloop_api_client/pagination.py +++ b/src/runloop_api_client/pagination.py @@ -32,6 +32,8 @@ "AsyncNetworkPoliciesCursorIDPage", "SyncGatewayConfigsCursorIDPage", "AsyncGatewayConfigsCursorIDPage", + "SyncMcpConfigsCursorIDPage", + "AsyncMcpConfigsCursorIDPage", ] _T = TypeVar("_T") @@ -102,6 +104,11 @@ class GatewayConfigsCursorIDPageItem(Protocol): id: str +@runtime_checkable +class McpConfigsCursorIDPageItem(Protocol): + id: str + + class SyncBlueprintsCursorIDPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]): blueprints: List[_T] has_more: Optional[bool] = None @@ -984,3 +991,71 @@ def next_page_info(self) -> Optional[PageInfo]: return None return PageInfo(params={"starting_after": item.id}) + + +class SyncMcpConfigsCursorIDPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]): + mcp_configs: List[_T] + has_more: Optional[bool] = None + total_count: Optional[int] = None + + @override + def _get_page_items(self) -> List[_T]: + mcp_configs = self.mcp_configs + if not mcp_configs: + return [] + return mcp_configs + + @override + def has_next_page(self) -> bool: + has_more = self.has_more + if has_more is not None and has_more is False: + return False + + return super().has_next_page() + + @override + def next_page_info(self) -> Optional[PageInfo]: + mcp_configs = self.mcp_configs + if not mcp_configs: + return None + + item = cast(Any, mcp_configs[-1]) + if not isinstance(item, McpConfigsCursorIDPageItem) or item.id is None: # pyright: ignore[reportUnnecessaryComparison] + # TODO emit warning log + return None + + return PageInfo(params={"starting_after": item.id}) + + +class AsyncMcpConfigsCursorIDPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]): + mcp_configs: List[_T] + has_more: Optional[bool] = None + total_count: Optional[int] = None + + @override + def _get_page_items(self) -> List[_T]: + mcp_configs = self.mcp_configs + if not mcp_configs: + return [] + return mcp_configs + + @override + def has_next_page(self) -> bool: + has_more = self.has_more + if has_more is not None and has_more is False: + return False + + return super().has_next_page() + + @override + def next_page_info(self) -> Optional[PageInfo]: + mcp_configs = self.mcp_configs + if not mcp_configs: + return None + + item = cast(Any, mcp_configs[-1]) + if not isinstance(item, McpConfigsCursorIDPageItem) or item.id is None: # pyright: ignore[reportUnnecessaryComparison] + # TODO emit warning log + return None + + return PageInfo(params={"starting_after": item.id}) diff --git a/src/runloop_api_client/resources/__init__.py b/src/runloop_api_client/resources/__init__.py index 877822444..2e0584f25 100644 --- a/src/runloop_api_client/resources/__init__.py +++ b/src/runloop_api_client/resources/__init__.py @@ -56,6 +56,14 @@ BlueprintsResourceWithStreamingResponse, AsyncBlueprintsResourceWithStreamingResponse, ) +from .mcp_configs import ( + McpConfigsResource, + AsyncMcpConfigsResource, + McpConfigsResourceWithRawResponse, + AsyncMcpConfigsResourceWithRawResponse, + McpConfigsResourceWithStreamingResponse, + AsyncMcpConfigsResourceWithStreamingResponse, +) from .repositories import ( RepositoriesResource, AsyncRepositoriesResource, @@ -170,4 +178,10 @@ "AsyncGatewayConfigsResourceWithRawResponse", "GatewayConfigsResourceWithStreamingResponse", "AsyncGatewayConfigsResourceWithStreamingResponse", + "McpConfigsResource", + "AsyncMcpConfigsResource", + "McpConfigsResourceWithRawResponse", + "AsyncMcpConfigsResourceWithRawResponse", + "McpConfigsResourceWithStreamingResponse", + "AsyncMcpConfigsResourceWithStreamingResponse", ] diff --git a/src/runloop_api_client/resources/devboxes/devboxes.py b/src/runloop_api_client/resources/devboxes/devboxes.py index 37c4b994a..34b118ed8 100644 --- a/src/runloop_api_client/resources/devboxes/devboxes.py +++ b/src/runloop_api_client/resources/devboxes/devboxes.py @@ -105,6 +105,7 @@ from ...types.shared_params.mount import Mount from ...types.devbox_snapshot_view import DevboxSnapshotView from ...types.shared.launch_parameters import LaunchParameters as SharedLaunchParameters +from ...types.devbox_resource_usage_view import DevboxResourceUsageView from ...types.devbox_execution_detail_view import DevboxExecutionDetailView from ...types.devbox_create_ssh_key_response import DevboxCreateSSHKeyResponse from ...types.shared_params.launch_parameters import LaunchParameters @@ -189,6 +190,7 @@ def create( file_mounts: Optional[Dict[str, str]] | Omit = omit, gateways: Optional[Dict[str, devbox_create_params.Gateways]] | Omit = omit, launch_parameters: Optional[LaunchParameters] | Omit = omit, + mcp: Optional[Iterable[devbox_create_params.Mcp]] | Omit = omit, metadata: Optional[Dict[str, str]] | Omit = omit, mounts: Optional[Iterable[Mount]] | Omit = omit, name: Optional[str] | Omit = omit, @@ -239,6 +241,11 @@ def create( launch_parameters: Parameters to configure the resources and launch time behavior of the Devbox. + mcp: [Beta] (Optional) MCP specifications for MCP server access. Each spec links an + MCP config to a secret. The devbox will receive environment variables + (RL_MCP_URL, RL_MCP_TOKEN) for accessing MCP servers through the MCP hub. + Example: [{'mcp_config': 'github-readonly', 'secret': 'MY_GITHUB_TOKEN'}] + metadata: User defined metadata to attach to the devbox for organization. mounts: A list of mounts to be included in the Devbox. @@ -281,6 +288,7 @@ def create( "file_mounts": file_mounts, "gateways": gateways, "launch_parameters": launch_parameters, + "mcp": mcp, "metadata": metadata, "mounts": mounts, "name": name, @@ -498,6 +506,7 @@ def create_and_await_running( file_mounts: Optional[Dict[str, str]] | Omit = omit, gateways: Optional[Dict[str, devbox_create_params.Gateways]] | Omit = omit, launch_parameters: Optional[LaunchParameters] | Omit = omit, + mcp: Optional[Iterable[devbox_create_params.Mcp]] | Omit = omit, metadata: Optional[Dict[str, str]] | Omit = omit, mounts: Optional[Iterable[Mount]] | Omit = omit, name: Optional[str] | Omit = omit, @@ -539,6 +548,7 @@ def create_and_await_running( file_mounts=file_mounts, gateways=gateways, launch_parameters=launch_parameters, + mcp=mcp, metadata=metadata, mounts=mounts, name=name, @@ -1364,6 +1374,43 @@ def resume( cast_to=DevboxView, ) + def retrieve_resource_usage( + 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, + ) -> DevboxResourceUsageView: + """Get resource usage metrics for a specific Devbox. + + Returns CPU, memory, and disk + consumption calculated from the Devbox's lifecycle, excluding any suspended + periods for CPU and memory. Disk usage includes the full elapsed time since + storage is consumed even when suspended. + + 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( + f"/v1/devboxes/{id}/usage", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DevboxResourceUsageView, + ) + def shutdown( self, id: str, @@ -1805,6 +1852,7 @@ async def create( file_mounts: Optional[Dict[str, str]] | Omit = omit, gateways: Optional[Dict[str, devbox_create_params.Gateways]] | Omit = omit, launch_parameters: Optional[LaunchParameters] | Omit = omit, + mcp: Optional[Iterable[devbox_create_params.Mcp]] | Omit = omit, metadata: Optional[Dict[str, str]] | Omit = omit, mounts: Optional[Iterable[Mount]] | Omit = omit, name: Optional[str] | Omit = omit, @@ -1855,6 +1903,11 @@ async def create( launch_parameters: Parameters to configure the resources and launch time behavior of the Devbox. + mcp: [Beta] (Optional) MCP specifications for MCP server access. Each spec links an + MCP config to a secret. The devbox will receive environment variables + (RL_MCP_URL, RL_MCP_TOKEN) for accessing MCP servers through the MCP hub. + Example: [{'mcp_config': 'github-readonly', 'secret': 'MY_GITHUB_TOKEN'}] + metadata: User defined metadata to attach to the devbox for organization. mounts: A list of mounts to be included in the Devbox. @@ -1897,6 +1950,7 @@ async def create( "file_mounts": file_mounts, "gateways": gateways, "launch_parameters": launch_parameters, + "mcp": mcp, "metadata": metadata, "mounts": mounts, "name": name, @@ -1961,6 +2015,7 @@ async def create_and_await_running( file_mounts: Optional[Dict[str, str]] | Omit = omit, gateways: Optional[Dict[str, devbox_create_params.Gateways]] | Omit = omit, launch_parameters: Optional[LaunchParameters] | Omit = omit, + mcp: Optional[Iterable[devbox_create_params.Mcp]] | Omit = omit, metadata: Optional[Dict[str, str]] | Omit = omit, mounts: Optional[Iterable[Mount]] | Omit = omit, name: Optional[str] | Omit = omit, @@ -2003,6 +2058,7 @@ async def create_and_await_running( file_mounts=file_mounts, gateways=gateways, launch_parameters=launch_parameters, + mcp=mcp, metadata=metadata, mounts=mounts, name=name, @@ -2980,6 +3036,43 @@ async def resume( cast_to=DevboxView, ) + async def retrieve_resource_usage( + 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, + ) -> DevboxResourceUsageView: + """Get resource usage metrics for a specific Devbox. + + Returns CPU, memory, and disk + consumption calculated from the Devbox's lifecycle, excluding any suspended + periods for CPU and memory. Disk usage includes the full elapsed time since + storage is consumed even when suspended. + + 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( + f"/v1/devboxes/{id}/usage", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DevboxResourceUsageView, + ) + async def shutdown( self, id: str, @@ -3434,6 +3527,9 @@ def __init__(self, devboxes: DevboxesResource) -> None: self.resume = to_raw_response_wrapper( devboxes.resume, ) + self.retrieve_resource_usage = to_raw_response_wrapper( + devboxes.retrieve_resource_usage, + ) self.shutdown = to_raw_response_wrapper( devboxes.shutdown, ) @@ -3539,6 +3635,9 @@ def __init__(self, devboxes: AsyncDevboxesResource) -> None: self.resume = async_to_raw_response_wrapper( devboxes.resume, ) + self.retrieve_resource_usage = async_to_raw_response_wrapper( + devboxes.retrieve_resource_usage, + ) self.shutdown = async_to_raw_response_wrapper( devboxes.shutdown, ) @@ -3644,6 +3743,9 @@ def __init__(self, devboxes: DevboxesResource) -> None: self.resume = to_streamed_response_wrapper( devboxes.resume, ) + self.retrieve_resource_usage = to_streamed_response_wrapper( + devboxes.retrieve_resource_usage, + ) self.shutdown = to_streamed_response_wrapper( devboxes.shutdown, ) @@ -3749,6 +3851,9 @@ def __init__(self, devboxes: AsyncDevboxesResource) -> None: self.resume = async_to_streamed_response_wrapper( devboxes.resume, ) + self.retrieve_resource_usage = async_to_streamed_response_wrapper( + devboxes.retrieve_resource_usage, + ) self.shutdown = async_to_streamed_response_wrapper( devboxes.shutdown, ) diff --git a/src/runloop_api_client/resources/mcp_configs.py b/src/runloop_api_client/resources/mcp_configs.py new file mode 100644 index 000000000..24b05cc2d --- /dev/null +++ b/src/runloop_api_client/resources/mcp_configs.py @@ -0,0 +1,662 @@ +# 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 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 .._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 ..pagination import SyncMcpConfigsCursorIDPage, AsyncMcpConfigsCursorIDPage +from .._base_client import AsyncPaginator, make_request_options +from ..types.mcp_config_view import McpConfigView + +__all__ = ["McpConfigsResource", "AsyncMcpConfigsResource"] + + +class McpConfigsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> McpConfigsResourceWithRawResponse: + """ + 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 McpConfigsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> McpConfigsResourceWithStreamingResponse: + """ + 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 McpConfigsResourceWithStreamingResponse(self) + + def create( + self, + *, + allowed_tools: SequenceNotStr[str], + endpoint: str, + name: str, + description: 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, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> McpConfigView: + """ + [Beta] Create a new McpConfig to connect to an upstream MCP (Model Context + Protocol) server. The config specifies the target endpoint and which tools are + allowed. + + Args: + allowed_tools: + Glob patterns specifying which tools are allowed from this MCP server. Examples: + ['*'] for all tools, ['github.search_*', 'github.get_*'] for specific patterns. + + endpoint: The target MCP server endpoint URL (e.g., 'https://mcp.example.com'). + + name: The human-readable name for the McpConfig. Must be unique within your account. + The first segment before '-' is used as the service name for tool routing (e.g., + 'github-readonly' uses 'github' as the service name). + + description: Optional description for this MCP configuration. + + 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/mcp-configs", + body=maybe_transform( + { + "allowed_tools": allowed_tools, + "endpoint": endpoint, + "name": name, + "description": description, + }, + mcp_config_create_params.McpConfigCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=McpConfigView, + ) + + 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, + ) -> McpConfigView: + """ + [Beta] Get a specific McpConfig by its unique identifier. + + 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( + f"/v1/mcp-configs/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=McpConfigView, + ) + + def update( + self, + id: str, + *, + allowed_tools: Optional[SequenceNotStr[str]] | Omit = omit, + description: Optional[str] | Omit = omit, + endpoint: Optional[str] | Omit = omit, + 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, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> McpConfigView: + """[Beta] Update an existing McpConfig. + + All fields are optional. + + Args: + allowed_tools: New glob patterns specifying which tools are allowed. Examples: ['*'] for all + tools, ['github.search_*'] for specific patterns. + + description: New description for this MCP configuration. + + endpoint: New target MCP server endpoint URL. + + name: New name for the McpConfig. Must be unique within your account. + + 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( + f"/v1/mcp-configs/{id}", + body=maybe_transform( + { + "allowed_tools": allowed_tools, + "description": description, + "endpoint": endpoint, + "name": name, + }, + mcp_config_update_params.McpConfigUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=McpConfigView, + ) + + def list( + self, + *, + id: str | Omit = omit, + limit: int | Omit = omit, + name: str | 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. + # 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, + ) -> SyncMcpConfigsCursorIDPage[McpConfigView]: + """ + [Beta] List all McpConfigs for the authenticated account. + + Args: + id: Filter by ID. + + limit: The limit of items to return. Default is 20. Max is 5000. + + name: Filter by name (prefix match supported). + + starting_after: Load the next page of data starting after the item with the given ID. + + 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/mcp-configs", + page=SyncMcpConfigsCursorIDPage[McpConfigView], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "id": id, + "limit": limit, + "name": name, + "starting_after": starting_after, + }, + mcp_config_list_params.McpConfigListParams, + ), + ), + model=McpConfigView, + ) + + 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, + ) -> McpConfigView: + """[Beta] Delete an existing McpConfig. + + This action is irreversible. + + 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( + f"/v1/mcp-configs/{id}/delete", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=McpConfigView, + ) + + +class AsyncMcpConfigsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncMcpConfigsResourceWithRawResponse: + """ + 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 AsyncMcpConfigsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncMcpConfigsResourceWithStreamingResponse: + """ + 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 AsyncMcpConfigsResourceWithStreamingResponse(self) + + async def create( + self, + *, + allowed_tools: SequenceNotStr[str], + endpoint: str, + name: str, + description: 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, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> McpConfigView: + """ + [Beta] Create a new McpConfig to connect to an upstream MCP (Model Context + Protocol) server. The config specifies the target endpoint and which tools are + allowed. + + Args: + allowed_tools: + Glob patterns specifying which tools are allowed from this MCP server. Examples: + ['*'] for all tools, ['github.search_*', 'github.get_*'] for specific patterns. + + endpoint: The target MCP server endpoint URL (e.g., 'https://mcp.example.com'). + + name: The human-readable name for the McpConfig. Must be unique within your account. + The first segment before '-' is used as the service name for tool routing (e.g., + 'github-readonly' uses 'github' as the service name). + + description: Optional description for this MCP configuration. + + 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/mcp-configs", + body=await async_maybe_transform( + { + "allowed_tools": allowed_tools, + "endpoint": endpoint, + "name": name, + "description": description, + }, + mcp_config_create_params.McpConfigCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=McpConfigView, + ) + + 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, + ) -> McpConfigView: + """ + [Beta] Get a specific McpConfig by its unique identifier. + + 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( + f"/v1/mcp-configs/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=McpConfigView, + ) + + async def update( + self, + id: str, + *, + allowed_tools: Optional[SequenceNotStr[str]] | Omit = omit, + description: Optional[str] | Omit = omit, + endpoint: Optional[str] | Omit = omit, + 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, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> McpConfigView: + """[Beta] Update an existing McpConfig. + + All fields are optional. + + Args: + allowed_tools: New glob patterns specifying which tools are allowed. Examples: ['*'] for all + tools, ['github.search_*'] for specific patterns. + + description: New description for this MCP configuration. + + endpoint: New target MCP server endpoint URL. + + name: New name for the McpConfig. Must be unique within your account. + + 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( + f"/v1/mcp-configs/{id}", + body=await async_maybe_transform( + { + "allowed_tools": allowed_tools, + "description": description, + "endpoint": endpoint, + "name": name, + }, + mcp_config_update_params.McpConfigUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=McpConfigView, + ) + + def list( + self, + *, + id: str | Omit = omit, + limit: int | Omit = omit, + name: str | 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. + # 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[McpConfigView, AsyncMcpConfigsCursorIDPage[McpConfigView]]: + """ + [Beta] List all McpConfigs for the authenticated account. + + Args: + id: Filter by ID. + + limit: The limit of items to return. Default is 20. Max is 5000. + + name: Filter by name (prefix match supported). + + starting_after: Load the next page of data starting after the item with the given ID. + + 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/mcp-configs", + page=AsyncMcpConfigsCursorIDPage[McpConfigView], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "id": id, + "limit": limit, + "name": name, + "starting_after": starting_after, + }, + mcp_config_list_params.McpConfigListParams, + ), + ), + model=McpConfigView, + ) + + 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, + ) -> McpConfigView: + """[Beta] Delete an existing McpConfig. + + This action is irreversible. + + 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( + f"/v1/mcp-configs/{id}/delete", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=McpConfigView, + ) + + +class McpConfigsResourceWithRawResponse: + def __init__(self, mcp_configs: McpConfigsResource) -> None: + self._mcp_configs = mcp_configs + + self.create = to_raw_response_wrapper( + mcp_configs.create, + ) + self.retrieve = to_raw_response_wrapper( + mcp_configs.retrieve, + ) + self.update = to_raw_response_wrapper( + mcp_configs.update, + ) + self.list = to_raw_response_wrapper( + mcp_configs.list, + ) + self.delete = to_raw_response_wrapper( + mcp_configs.delete, + ) + + +class AsyncMcpConfigsResourceWithRawResponse: + def __init__(self, mcp_configs: AsyncMcpConfigsResource) -> None: + self._mcp_configs = mcp_configs + + self.create = async_to_raw_response_wrapper( + mcp_configs.create, + ) + self.retrieve = async_to_raw_response_wrapper( + mcp_configs.retrieve, + ) + self.update = async_to_raw_response_wrapper( + mcp_configs.update, + ) + self.list = async_to_raw_response_wrapper( + mcp_configs.list, + ) + self.delete = async_to_raw_response_wrapper( + mcp_configs.delete, + ) + + +class McpConfigsResourceWithStreamingResponse: + def __init__(self, mcp_configs: McpConfigsResource) -> None: + self._mcp_configs = mcp_configs + + self.create = to_streamed_response_wrapper( + mcp_configs.create, + ) + self.retrieve = to_streamed_response_wrapper( + mcp_configs.retrieve, + ) + self.update = to_streamed_response_wrapper( + mcp_configs.update, + ) + self.list = to_streamed_response_wrapper( + mcp_configs.list, + ) + self.delete = to_streamed_response_wrapper( + mcp_configs.delete, + ) + + +class AsyncMcpConfigsResourceWithStreamingResponse: + def __init__(self, mcp_configs: AsyncMcpConfigsResource) -> None: + self._mcp_configs = mcp_configs + + self.create = async_to_streamed_response_wrapper( + mcp_configs.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + mcp_configs.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + mcp_configs.update, + ) + self.list = async_to_streamed_response_wrapper( + mcp_configs.list, + ) + self.delete = async_to_streamed_response_wrapper( + mcp_configs.delete, + ) diff --git a/src/runloop_api_client/resources/scenarios/scorers.py b/src/runloop_api_client/resources/scenarios/scorers.py index 9e5d5e198..cdb011dc7 100644 --- a/src/runloop_api_client/resources/scenarios/scorers.py +++ b/src/runloop_api_client/resources/scenarios/scorers.py @@ -16,13 +16,11 @@ ) from ...pagination import SyncScenarioScorersCursorIDPage, AsyncScenarioScorersCursorIDPage from ..._base_client import AsyncPaginator, make_request_options -from ...types.scenarios import scorer_list_params, scorer_create_params, scorer_update_params, scorer_validate_params -from ...types.scenario_environment_param import ScenarioEnvironmentParam +from ...types.scenarios import scorer_list_params, scorer_create_params, scorer_update_params from ...types.scenarios.scorer_list_response import ScorerListResponse from ...types.scenarios.scorer_create_response import ScorerCreateResponse from ...types.scenarios.scorer_update_response import ScorerUpdateResponse from ...types.scenarios.scorer_retrieve_response import ScorerRetrieveResponse -from ...types.scenarios.scorer_validate_response import ScorerValidateResponse __all__ = ["ScorersResource", "AsyncScorersResource"] @@ -232,59 +230,6 @@ def list( model=ScorerListResponse, ) - def validate( - self, - id: str, - *, - scoring_context: object, - environment_parameters: ScenarioEnvironmentParam | 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, - ) -> ScorerValidateResponse: - """ - Validate a scenario scorer. - - Args: - scoring_context: Json context that gets passed to the custom scorer - - environment_parameters: The Environment in which the Scenario will run. - - 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( - f"/v1/scenarios/scorers/{id}/validate", - body=maybe_transform( - { - "scoring_context": scoring_context, - "environment_parameters": environment_parameters, - }, - scorer_validate_params.ScorerValidateParams, - ), - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - idempotency_key=idempotency_key, - ), - cast_to=ScorerValidateResponse, - ) - class AsyncScorersResource(AsyncAPIResource): @cached_property @@ -491,59 +436,6 @@ def list( model=ScorerListResponse, ) - async def validate( - self, - id: str, - *, - scoring_context: object, - environment_parameters: ScenarioEnvironmentParam | 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, - ) -> ScorerValidateResponse: - """ - Validate a scenario scorer. - - Args: - scoring_context: Json context that gets passed to the custom scorer - - environment_parameters: The Environment in which the Scenario will run. - - 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( - f"/v1/scenarios/scorers/{id}/validate", - body=await async_maybe_transform( - { - "scoring_context": scoring_context, - "environment_parameters": environment_parameters, - }, - scorer_validate_params.ScorerValidateParams, - ), - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - idempotency_key=idempotency_key, - ), - cast_to=ScorerValidateResponse, - ) - class ScorersResourceWithRawResponse: def __init__(self, scorers: ScorersResource) -> None: @@ -561,9 +453,6 @@ def __init__(self, scorers: ScorersResource) -> None: self.list = to_raw_response_wrapper( scorers.list, ) - self.validate = to_raw_response_wrapper( - scorers.validate, - ) class AsyncScorersResourceWithRawResponse: @@ -582,9 +471,6 @@ def __init__(self, scorers: AsyncScorersResource) -> None: self.list = async_to_raw_response_wrapper( scorers.list, ) - self.validate = async_to_raw_response_wrapper( - scorers.validate, - ) class ScorersResourceWithStreamingResponse: @@ -603,9 +489,6 @@ def __init__(self, scorers: ScorersResource) -> None: self.list = to_streamed_response_wrapper( scorers.list, ) - self.validate = to_streamed_response_wrapper( - scorers.validate, - ) class AsyncScorersResourceWithStreamingResponse: @@ -624,6 +507,3 @@ def __init__(self, scorers: AsyncScorersResource) -> None: self.list = async_to_streamed_response_wrapper( scorers.list, ) - self.validate = async_to_streamed_response_wrapper( - scorers.validate, - ) diff --git a/src/runloop_api_client/sdk/_types.py b/src/runloop_api_client/sdk/_types.py index 9eb0526bc..9e7f8780e 100644 --- a/src/runloop_api_client/sdk/_types.py +++ b/src/runloop_api_client/sdk/_types.py @@ -37,7 +37,7 @@ from .._types import Body, Query, Headers, Timeout, NotGiven from ..lib.polling import PollingConfig from ..types.devboxes import DiskSnapshotListParams, DiskSnapshotUpdateParams -from ..types.scenarios import ScorerListParams, ScorerCreateParams, ScorerUpdateParams, ScorerValidateParams +from ..types.scenarios import ScorerListParams, ScorerCreateParams, ScorerUpdateParams from ..types.devbox_create_params import DevboxBaseCreateParams from ..types.scenario_start_run_params import ScenarioStartRunBaseParams from ..types.benchmark_start_run_params import BenchmarkSelfStartRunParams @@ -181,10 +181,6 @@ class SDKScorerUpdateParams(ScorerUpdateParams, LongRequestOptions): pass -class SDKScorerValidateParams(ScorerValidateParams, LongRequestOptions): - pass - - class SDKAgentCreateParams(AgentCreateParams, LongRequestOptions): pass diff --git a/src/runloop_api_client/sdk/async_scorer.py b/src/runloop_api_client/sdk/async_scorer.py index 91ced0c38..12df27033 100644 --- a/src/runloop_api_client/sdk/async_scorer.py +++ b/src/runloop_api_client/sdk/async_scorer.py @@ -7,10 +7,9 @@ from ._types import ( BaseRequestOptions, SDKScorerUpdateParams, - SDKScorerValidateParams, ) from .._client import AsyncRunloop -from ..types.scenarios import ScorerUpdateResponse, ScorerRetrieveResponse, ScorerValidateResponse +from ..types.scenarios import ScorerUpdateResponse, ScorerRetrieveResponse class AsyncScorer: @@ -66,12 +65,3 @@ async def update(self, **params: Unpack[SDKScorerUpdateParams]) -> ScorerUpdateR :rtype: ScorerUpdateResponse """ return await self._client.scenarios.scorers.update(self._id, **params) - - async def validate(self, **params: Unpack[SDKScorerValidateParams]) -> ScorerValidateResponse: - """Run the scorer against the provided context and return the result. - - :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScorerValidateParams` for available parameters - :return: Validation result with score - :rtype: ScorerValidateResponse - """ - return await self._client.scenarios.scorers.validate(self._id, **params) diff --git a/src/runloop_api_client/sdk/scorer.py b/src/runloop_api_client/sdk/scorer.py index 8df57ac05..86c31cd05 100644 --- a/src/runloop_api_client/sdk/scorer.py +++ b/src/runloop_api_client/sdk/scorer.py @@ -7,10 +7,9 @@ from ._types import ( BaseRequestOptions, SDKScorerUpdateParams, - SDKScorerValidateParams, ) from .._client import Runloop -from ..types.scenarios import ScorerUpdateResponse, ScorerRetrieveResponse, ScorerValidateResponse +from ..types.scenarios import ScorerUpdateResponse, ScorerRetrieveResponse class Scorer: @@ -66,12 +65,3 @@ def update(self, **params: Unpack[SDKScorerUpdateParams]) -> ScorerUpdateRespons :rtype: ScorerUpdateResponse """ return self._client.scenarios.scorers.update(self._id, **params) - - def validate(self, **params: Unpack[SDKScorerValidateParams]) -> ScorerValidateResponse: - """Run the scorer against the provided context and return the result. - - :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScorerValidateParams` for available parameters - :return: Validation result with score - :rtype: ScorerValidateResponse - """ - return self._client.scenarios.scorers.validate(self._id, **params) diff --git a/src/runloop_api_client/types/__init__.py b/src/runloop_api_client/types/__init__.py index 8fb72317f..e6543484f 100644 --- a/src/runloop_api_client/types/__init__.py +++ b/src/runloop_api_client/types/__init__.py @@ -22,6 +22,7 @@ from .benchmark_view import BenchmarkView as BenchmarkView from .blueprint_view import BlueprintView as BlueprintView from .agent_list_view import AgentListView as AgentListView +from .mcp_config_view import McpConfigView as McpConfigView from .devbox_list_view import DevboxListView as DevboxListView from .object_list_view import ObjectListView as ObjectListView from .scoring_contract import ScoringContract as ScoringContract @@ -44,6 +45,7 @@ from .devbox_create_params import DevboxCreateParams as DevboxCreateParams from .devbox_snapshot_view import DevboxSnapshotView as DevboxSnapshotView from .devbox_update_params import DevboxUpdateParams as DevboxUpdateParams +from .mcp_config_list_view import McpConfigListView as McpConfigListView from .object_create_params import ObjectCreateParams as ObjectCreateParams from .scenario_environment import ScenarioEnvironment as ScenarioEnvironment from .scenario_list_params import ScenarioListParams as ScenarioListParams @@ -53,6 +55,7 @@ from .blueprint_list_params import BlueprintListParams as BlueprintListParams from .devbox_execute_params import DevboxExecuteParams as DevboxExecuteParams from .blueprint_preview_view import BlueprintPreviewView as BlueprintPreviewView +from .mcp_config_list_params import McpConfigListParams as McpConfigListParams from .object_download_params import ObjectDownloadParams as ObjectDownloadParams from .repository_list_params import RepositoryListParams as RepositoryListParams from .scenario_create_params import ScenarioCreateParams as ScenarioCreateParams @@ -68,6 +71,8 @@ from .inspection_source_param import InspectionSourceParam as InspectionSourceParam 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 +from .mcp_config_update_params import McpConfigUpdateParams as McpConfigUpdateParams from .network_policy_list_view import NetworkPolicyListView as NetworkPolicyListView from .object_download_url_view import ObjectDownloadURLView as ObjectDownloadURLView from .repository_create_params import RepositoryCreateParams as RepositoryCreateParams @@ -84,6 +89,7 @@ from .benchmark_start_run_params import BenchmarkStartRunParams as BenchmarkStartRunParams from .blueprint_build_parameters import BlueprintBuildParameters as BlueprintBuildParameters from .devbox_execute_sync_params import DevboxExecuteSyncParams as DevboxExecuteSyncParams +from .devbox_resource_usage_view import DevboxResourceUsageView as DevboxResourceUsageView from .gateway_config_list_params import GatewayConfigListParams as GatewayConfigListParams from .input_context_update_param import InputContextUpdateParam as InputContextUpdateParam from .network_policy_list_params import NetworkPolicyListParams as NetworkPolicyListParams diff --git a/src/runloop_api_client/types/devbox_create_params.py b/src/runloop_api_client/types/devbox_create_params.py index b31efdfe7..ae2f0db59 100644 --- a/src/runloop_api_client/types/devbox_create_params.py +++ b/src/runloop_api_client/types/devbox_create_params.py @@ -9,7 +9,7 @@ from .shared_params.launch_parameters import LaunchParameters from .shared_params.code_mount_parameters import CodeMountParameters -__all__ = ["DevboxCreateParams", "Gateways", "Tunnel"] +__all__ = ["DevboxCreateParams", "Gateways", "Mcp", "Tunnel"] # We split up the original DevboxCreateParams into two nested types to enable us to # omit blueprint_id, blueprint_name, and snapshot_id when we unpack the TypedDict @@ -49,6 +49,14 @@ class DevboxBaseCreateParams(TypedDict, total=False): launch_parameters: Optional[LaunchParameters] """Parameters to configure the resources and launch time behavior of the Devbox.""" + mcp: Optional[Iterable[Mcp]] + """[Beta] (Optional) MCP specifications for MCP server access. + + Each spec links an MCP config to a secret. The devbox will receive environment + variables (RL_MCP_URL, RL_MCP_TOKEN) for accessing MCP servers through the MCP + hub. Example: [{'mcp_config': 'github-readonly', 'secret': 'MY_GITHUB_TOKEN'}] + """ + metadata: Optional[Dict[str, str]] """User defined metadata to attach to the devbox for organization.""" @@ -114,6 +122,18 @@ class Gateways(TypedDict, total=False): """The secret containing the credential. Can be a secret ID or name.""" +class Mcp(TypedDict, total=False): + """ + [Beta] McpSpec links an MCP configuration to a secret for MCP server access in a devbox. The MCP hub will proxy requests to upstream MCP servers using the specified credential, with tool-level access control based on the MCP config's allowed_tools. + """ + + mcp_config: Required[str] + """The MCP config to use. Can be an MCP config ID (mcp_xxx) or name.""" + + secret: Required[str] + """The secret containing the MCP server credential. Can be a secret ID or name.""" + + class Tunnel(TypedDict, total=False): """(Optional) Configuration for creating a V2 tunnel at Devbox launch time. diff --git a/src/runloop_api_client/types/devbox_resource_usage_view.py b/src/runloop_api_client/types/devbox_resource_usage_view.py new file mode 100644 index 000000000..fe5c83a57 --- /dev/null +++ b/src/runloop_api_client/types/devbox_resource_usage_view.py @@ -0,0 +1,48 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["DevboxResourceUsageView"] + + +class DevboxResourceUsageView(BaseModel): + id: str + """The devbox ID.""" + + disk_gb_seconds: int + """Disk usage in GB-seconds (total_elapsed_seconds multiplied by disk size in GB). + + Disk is billed for elapsed time since storage is consumed even when suspended. + """ + + memory_gb_seconds: int + """Memory usage in GB-seconds (total_active_seconds multiplied by memory in GB).""" + + start_time_ms: int + """The devbox creation time in milliseconds since epoch.""" + + status: str + """The current status of the devbox.""" + + total_active_seconds: int + """ + Total time in seconds the devbox was actively running (excludes time spent + suspended). + """ + + total_elapsed_seconds: int + """ + Total elapsed time in seconds from devbox creation to now (or end time if + terminated). Includes all time regardless of devbox state. + """ + + vcpu_seconds: int + """ + vCPU usage in vCPU-seconds (total_active_seconds multiplied by the number of + vCPUs). + """ + + end_time_ms: Optional[int] = None + """The devbox end time in milliseconds since epoch, or null if still running.""" diff --git a/src/runloop_api_client/types/mcp_config_create_params.py b/src/runloop_api_client/types/mcp_config_create_params.py new file mode 100644 index 000000000..4a2dcd9e2 --- /dev/null +++ b/src/runloop_api_client/types/mcp_config_create_params.py @@ -0,0 +1,33 @@ +# 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 Required, TypedDict + +from .._types import SequenceNotStr + +__all__ = ["McpConfigCreateParams"] + + +class McpConfigCreateParams(TypedDict, total=False): + allowed_tools: Required[SequenceNotStr[str]] + """Glob patterns specifying which tools are allowed from this MCP server. + + Examples: ['*'] for all tools, ['github.search_*', 'github.get_*'] for specific + patterns. + """ + + endpoint: Required[str] + """The target MCP server endpoint URL (e.g., 'https://mcp.example.com').""" + + name: Required[str] + """The human-readable name for the McpConfig. + + Must be unique within your account. The first segment before '-' is used as the + service name for tool routing (e.g., 'github-readonly' uses 'github' as the + service name). + """ + + description: Optional[str] + """Optional description for this MCP configuration.""" diff --git a/src/runloop_api_client/types/mcp_config_list_params.py b/src/runloop_api_client/types/mcp_config_list_params.py new file mode 100644 index 000000000..8b786ba13 --- /dev/null +++ b/src/runloop_api_client/types/mcp_config_list_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 TypedDict + +__all__ = ["McpConfigListParams"] + + +class McpConfigListParams(TypedDict, total=False): + id: str + """Filter by ID.""" + + limit: int + """The limit of items to return. Default is 20. Max is 5000.""" + + name: str + """Filter by name (prefix match supported).""" + + starting_after: str + """Load the next page of data starting after the item with the given ID.""" diff --git a/src/runloop_api_client/types/mcp_config_list_view.py b/src/runloop_api_client/types/mcp_config_list_view.py new file mode 100644 index 000000000..72075a255 --- /dev/null +++ b/src/runloop_api_client/types/mcp_config_list_view.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List + +from .._models import BaseModel +from .mcp_config_view import McpConfigView + +__all__ = ["McpConfigListView"] + + +class McpConfigListView(BaseModel): + """A paginated list of McpConfigs.""" + + has_more: bool + """Whether there are more results available beyond this page.""" + + mcp_configs: List[McpConfigView] + """The list of McpConfigs.""" + + total_count: int + """Total count of McpConfigs that match the query.""" diff --git a/src/runloop_api_client/types/mcp_config_update_params.py b/src/runloop_api_client/types/mcp_config_update_params.py new file mode 100644 index 000000000..234ca939f --- /dev/null +++ b/src/runloop_api_client/types/mcp_config_update_params.py @@ -0,0 +1,27 @@ +# 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 + +from .._types import SequenceNotStr + +__all__ = ["McpConfigUpdateParams"] + + +class McpConfigUpdateParams(TypedDict, total=False): + allowed_tools: Optional[SequenceNotStr[str]] + """New glob patterns specifying which tools are allowed. + + Examples: ['*'] for all tools, ['github.search_*'] for specific patterns. + """ + + description: Optional[str] + """New description for this MCP configuration.""" + + endpoint: Optional[str] + """New target MCP server endpoint URL.""" + + name: Optional[str] + """New name for the McpConfig. Must be unique within your account.""" diff --git a/src/runloop_api_client/types/mcp_config_view.py b/src/runloop_api_client/types/mcp_config_view.py new file mode 100644 index 000000000..988537a59 --- /dev/null +++ b/src/runloop_api_client/types/mcp_config_view.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["McpConfigView"] + + +class McpConfigView(BaseModel): + """ + An McpConfig defines a configuration for connecting to an upstream MCP (Model Context Protocol) server. It specifies the target endpoint and which tools are allowed. + """ + + id: str + """The unique identifier of the McpConfig.""" + + allowed_tools: List[str] + """ + Glob patterns specifying which tools are allowed from this MCP server (e.g., + ['github.search_*', 'github.get_*'] or ['*'] for all tools). + """ + + create_time_ms: int + """Creation time of the McpConfig (Unix timestamp in milliseconds).""" + + endpoint: str + """The target MCP server endpoint URL (e.g., 'https://mcp.example.com').""" + + name: str + """The human-readable name of the McpConfig. Unique per account.""" + + description: Optional[str] = None + """Optional description for this MCP configuration.""" diff --git a/src/runloop_api_client/types/scenarios/__init__.py b/src/runloop_api_client/types/scenarios/__init__.py index d25c85c4e..97efebd9d 100644 --- a/src/runloop_api_client/types/scenarios/__init__.py +++ b/src/runloop_api_client/types/scenarios/__init__.py @@ -9,6 +9,4 @@ from .scorer_update_params import ScorerUpdateParams as ScorerUpdateParams from .scorer_create_response import ScorerCreateResponse as ScorerCreateResponse from .scorer_update_response import ScorerUpdateResponse as ScorerUpdateResponse -from .scorer_validate_params import ScorerValidateParams as ScorerValidateParams from .scorer_retrieve_response import ScorerRetrieveResponse as ScorerRetrieveResponse -from .scorer_validate_response import ScorerValidateResponse as ScorerValidateResponse diff --git a/src/runloop_api_client/types/scenarios/scorer_validate_params.py b/src/runloop_api_client/types/scenarios/scorer_validate_params.py deleted file mode 100644 index 41215ea2d..000000000 --- a/src/runloop_api_client/types/scenarios/scorer_validate_params.py +++ /dev/null @@ -1,17 +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 - -from ..scenario_environment_param import ScenarioEnvironmentParam - -__all__ = ["ScorerValidateParams"] - - -class ScorerValidateParams(TypedDict, total=False): - scoring_context: Required[object] - """Json context that gets passed to the custom scorer""" - - environment_parameters: ScenarioEnvironmentParam - """The Environment in which the Scenario will run.""" diff --git a/src/runloop_api_client/types/scenarios/scorer_validate_response.py b/src/runloop_api_client/types/scenarios/scorer_validate_response.py deleted file mode 100644 index 6c2755e6c..000000000 --- a/src/runloop_api_client/types/scenarios/scorer_validate_response.py +++ /dev/null @@ -1,23 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Optional - -from ..._models import BaseModel -from ..scenario_environment import ScenarioEnvironment -from ..scoring_function_result_view import ScoringFunctionResultView - -__all__ = ["ScorerValidateResponse"] - - -class ScorerValidateResponse(BaseModel): - name: str - """Name of the custom scorer.""" - - scoring_context: object - """Json context that gets passed to the custom scorer""" - - scoring_result: ScoringFunctionResultView - """Result of the scoring function.""" - - environment_parameters: Optional[ScenarioEnvironment] = None - """The Environment in which the Scenario will run.""" diff --git a/tests/api_resources/scenarios/test_scorers.py b/tests/api_resources/scenarios/test_scorers.py index 73be902fd..359e0dcc7 100644 --- a/tests/api_resources/scenarios/test_scorers.py +++ b/tests/api_resources/scenarios/test_scorers.py @@ -15,7 +15,6 @@ ScorerCreateResponse, ScorerUpdateResponse, ScorerRetrieveResponse, - ScorerValidateResponse, ) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -175,81 +174,6 @@ def test_streaming_response_list(self, client: Runloop) -> None: assert cast(Any, response.is_closed) is True - @parametrize - def test_method_validate(self, client: Runloop) -> None: - scorer = client.scenarios.scorers.validate( - id="id", - scoring_context={}, - ) - assert_matches_type(ScorerValidateResponse, scorer, path=["response"]) - - @parametrize - def test_method_validate_with_all_params(self, client: Runloop) -> None: - scorer = client.scenarios.scorers.validate( - id="id", - scoring_context={}, - environment_parameters={ - "blueprint_id": "blueprint_id", - "launch_parameters": { - "after_idle": { - "idle_time_seconds": 0, - "on_idle": "shutdown", - }, - "architecture": "x86_64", - "available_ports": [0], - "custom_cpu_cores": 0, - "custom_disk_size": 0, - "custom_gb_memory": 0, - "keep_alive_time_seconds": 0, - "launch_commands": ["string"], - "network_policy_id": "network_policy_id", - "required_services": ["string"], - "resource_size_request": "X_SMALL", - "user_parameters": { - "uid": 0, - "username": "username", - }, - }, - "snapshot_id": "snapshot_id", - "working_directory": "working_directory", - }, - ) - assert_matches_type(ScorerValidateResponse, scorer, path=["response"]) - - @parametrize - def test_raw_response_validate(self, client: Runloop) -> None: - response = client.scenarios.scorers.with_raw_response.validate( - id="id", - scoring_context={}, - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - scorer = response.parse() - assert_matches_type(ScorerValidateResponse, scorer, path=["response"]) - - @parametrize - def test_streaming_response_validate(self, client: Runloop) -> None: - with client.scenarios.scorers.with_streaming_response.validate( - id="id", - scoring_context={}, - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - scorer = response.parse() - assert_matches_type(ScorerValidateResponse, scorer, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - def test_path_params_validate(self, client: Runloop) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.scenarios.scorers.with_raw_response.validate( - id="", - scoring_context={}, - ) - class TestAsyncScorers: parametrize = pytest.mark.parametrize( @@ -406,78 +330,3 @@ async def test_streaming_response_list(self, async_client: AsyncRunloop) -> None assert_matches_type(AsyncScenarioScorersCursorIDPage[ScorerListResponse], scorer, path=["response"]) assert cast(Any, response.is_closed) is True - - @parametrize - async def test_method_validate(self, async_client: AsyncRunloop) -> None: - scorer = await async_client.scenarios.scorers.validate( - id="id", - scoring_context={}, - ) - assert_matches_type(ScorerValidateResponse, scorer, path=["response"]) - - @parametrize - async def test_method_validate_with_all_params(self, async_client: AsyncRunloop) -> None: - scorer = await async_client.scenarios.scorers.validate( - id="id", - scoring_context={}, - environment_parameters={ - "blueprint_id": "blueprint_id", - "launch_parameters": { - "after_idle": { - "idle_time_seconds": 0, - "on_idle": "shutdown", - }, - "architecture": "x86_64", - "available_ports": [0], - "custom_cpu_cores": 0, - "custom_disk_size": 0, - "custom_gb_memory": 0, - "keep_alive_time_seconds": 0, - "launch_commands": ["string"], - "network_policy_id": "network_policy_id", - "required_services": ["string"], - "resource_size_request": "X_SMALL", - "user_parameters": { - "uid": 0, - "username": "username", - }, - }, - "snapshot_id": "snapshot_id", - "working_directory": "working_directory", - }, - ) - assert_matches_type(ScorerValidateResponse, scorer, path=["response"]) - - @parametrize - async def test_raw_response_validate(self, async_client: AsyncRunloop) -> None: - response = await async_client.scenarios.scorers.with_raw_response.validate( - id="id", - scoring_context={}, - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - scorer = await response.parse() - assert_matches_type(ScorerValidateResponse, scorer, path=["response"]) - - @parametrize - async def test_streaming_response_validate(self, async_client: AsyncRunloop) -> None: - async with async_client.scenarios.scorers.with_streaming_response.validate( - id="id", - scoring_context={}, - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - scorer = await response.parse() - assert_matches_type(ScorerValidateResponse, scorer, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_path_params_validate(self, async_client: AsyncRunloop) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.scenarios.scorers.with_raw_response.validate( - id="", - scoring_context={}, - ) diff --git a/tests/api_resources/test_devboxes.py b/tests/api_resources/test_devboxes.py index fcfc5c1de..eef022a71 100644 --- a/tests/api_resources/test_devboxes.py +++ b/tests/api_resources/test_devboxes.py @@ -17,6 +17,7 @@ TunnelView, DevboxTunnelView, DevboxSnapshotView, + DevboxResourceUsageView, DevboxExecutionDetailView, DevboxCreateSSHKeyResponse, DevboxAsyncExecutionDetailView, @@ -92,6 +93,12 @@ def test_method_create_with_all_params(self, client: Runloop) -> None: "username": "username", }, }, + mcp=[ + { + "mcp_config": "mcp_config", + "secret": "secret", + } + ], metadata={"foo": "string"}, mounts=[ { @@ -845,6 +852,44 @@ def test_path_params_resume(self, client: Runloop) -> None: "", ) + @parametrize + def test_method_retrieve_resource_usage(self, client: Runloop) -> None: + devbox = client.devboxes.retrieve_resource_usage( + "id", + ) + assert_matches_type(DevboxResourceUsageView, devbox, path=["response"]) + + @parametrize + def test_raw_response_retrieve_resource_usage(self, client: Runloop) -> None: + response = client.devboxes.with_raw_response.retrieve_resource_usage( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + devbox = response.parse() + assert_matches_type(DevboxResourceUsageView, devbox, path=["response"]) + + @parametrize + def test_streaming_response_retrieve_resource_usage(self, client: Runloop) -> None: + with client.devboxes.with_streaming_response.retrieve_resource_usage( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + devbox = response.parse() + assert_matches_type(DevboxResourceUsageView, devbox, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve_resource_usage(self, client: Runloop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.devboxes.with_raw_response.retrieve_resource_usage( + "", + ) + @parametrize def test_method_shutdown(self, client: Runloop) -> None: devbox = client.devboxes.shutdown( @@ -1704,6 +1749,12 @@ async def test_method_create_with_all_params(self, async_client: AsyncRunloop) - "username": "username", }, }, + mcp=[ + { + "mcp_config": "mcp_config", + "secret": "secret", + } + ], metadata={"foo": "string"}, mounts=[ { @@ -2457,6 +2508,44 @@ async def test_path_params_resume(self, async_client: AsyncRunloop) -> None: "", ) + @parametrize + async def test_method_retrieve_resource_usage(self, async_client: AsyncRunloop) -> None: + devbox = await async_client.devboxes.retrieve_resource_usage( + "id", + ) + assert_matches_type(DevboxResourceUsageView, devbox, path=["response"]) + + @parametrize + async def test_raw_response_retrieve_resource_usage(self, async_client: AsyncRunloop) -> None: + response = await async_client.devboxes.with_raw_response.retrieve_resource_usage( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + devbox = await response.parse() + assert_matches_type(DevboxResourceUsageView, devbox, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve_resource_usage(self, async_client: AsyncRunloop) -> None: + async with async_client.devboxes.with_streaming_response.retrieve_resource_usage( + "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(DevboxResourceUsageView, devbox, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve_resource_usage(self, async_client: AsyncRunloop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.devboxes.with_raw_response.retrieve_resource_usage( + "", + ) + @parametrize async def test_method_shutdown(self, async_client: AsyncRunloop) -> None: devbox = await async_client.devboxes.shutdown( diff --git a/tests/api_resources/test_mcp_configs.py b/tests/api_resources/test_mcp_configs.py new file mode 100644 index 000000000..0d3135668 --- /dev/null +++ b/tests/api_resources/test_mcp_configs.py @@ -0,0 +1,441 @@ +# 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 ( + McpConfigView, +) +from runloop_api_client.pagination import SyncMcpConfigsCursorIDPage, AsyncMcpConfigsCursorIDPage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestMcpConfigs: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: Runloop) -> None: + mcp_config = client.mcp_configs.create( + allowed_tools=["string"], + endpoint="endpoint", + name="name", + ) + assert_matches_type(McpConfigView, mcp_config, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: Runloop) -> None: + mcp_config = client.mcp_configs.create( + allowed_tools=["string"], + endpoint="endpoint", + name="name", + description="description", + ) + assert_matches_type(McpConfigView, mcp_config, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: Runloop) -> None: + response = client.mcp_configs.with_raw_response.create( + allowed_tools=["string"], + endpoint="endpoint", + name="name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + mcp_config = response.parse() + assert_matches_type(McpConfigView, mcp_config, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: Runloop) -> None: + with client.mcp_configs.with_streaming_response.create( + allowed_tools=["string"], + endpoint="endpoint", + name="name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + mcp_config = response.parse() + assert_matches_type(McpConfigView, mcp_config, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_retrieve(self, client: Runloop) -> None: + mcp_config = client.mcp_configs.retrieve( + "id", + ) + assert_matches_type(McpConfigView, mcp_config, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Runloop) -> None: + response = client.mcp_configs.with_raw_response.retrieve( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + mcp_config = response.parse() + assert_matches_type(McpConfigView, mcp_config, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Runloop) -> None: + with client.mcp_configs.with_streaming_response.retrieve( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + mcp_config = response.parse() + assert_matches_type(McpConfigView, mcp_config, 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.mcp_configs.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_update(self, client: Runloop) -> None: + mcp_config = client.mcp_configs.update( + id="id", + ) + assert_matches_type(McpConfigView, mcp_config, path=["response"]) + + @parametrize + def test_method_update_with_all_params(self, client: Runloop) -> None: + mcp_config = client.mcp_configs.update( + id="id", + allowed_tools=["string"], + description="description", + endpoint="endpoint", + name="name", + ) + assert_matches_type(McpConfigView, mcp_config, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: Runloop) -> None: + response = client.mcp_configs.with_raw_response.update( + id="id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + mcp_config = response.parse() + assert_matches_type(McpConfigView, mcp_config, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: Runloop) -> None: + with client.mcp_configs.with_streaming_response.update( + id="id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + mcp_config = response.parse() + assert_matches_type(McpConfigView, mcp_config, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: Runloop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.mcp_configs.with_raw_response.update( + id="", + ) + + @parametrize + def test_method_list(self, client: Runloop) -> None: + mcp_config = client.mcp_configs.list() + assert_matches_type(SyncMcpConfigsCursorIDPage[McpConfigView], mcp_config, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Runloop) -> None: + mcp_config = client.mcp_configs.list( + id="id", + limit=0, + name="name", + starting_after="starting_after", + ) + assert_matches_type(SyncMcpConfigsCursorIDPage[McpConfigView], mcp_config, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Runloop) -> None: + response = client.mcp_configs.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + mcp_config = response.parse() + assert_matches_type(SyncMcpConfigsCursorIDPage[McpConfigView], mcp_config, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Runloop) -> None: + with client.mcp_configs.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + mcp_config = response.parse() + assert_matches_type(SyncMcpConfigsCursorIDPage[McpConfigView], mcp_config, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_delete(self, client: Runloop) -> None: + mcp_config = client.mcp_configs.delete( + "id", + ) + assert_matches_type(McpConfigView, mcp_config, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: Runloop) -> None: + response = client.mcp_configs.with_raw_response.delete( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + mcp_config = response.parse() + assert_matches_type(McpConfigView, mcp_config, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: Runloop) -> None: + with client.mcp_configs.with_streaming_response.delete( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + mcp_config = response.parse() + assert_matches_type(McpConfigView, mcp_config, 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.mcp_configs.with_raw_response.delete( + "", + ) + + +class TestAsyncMcpConfigs: + 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: + mcp_config = await async_client.mcp_configs.create( + allowed_tools=["string"], + endpoint="endpoint", + name="name", + ) + assert_matches_type(McpConfigView, mcp_config, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncRunloop) -> None: + mcp_config = await async_client.mcp_configs.create( + allowed_tools=["string"], + endpoint="endpoint", + name="name", + description="description", + ) + assert_matches_type(McpConfigView, mcp_config, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncRunloop) -> None: + response = await async_client.mcp_configs.with_raw_response.create( + allowed_tools=["string"], + endpoint="endpoint", + name="name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + mcp_config = await response.parse() + assert_matches_type(McpConfigView, mcp_config, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncRunloop) -> None: + async with async_client.mcp_configs.with_streaming_response.create( + allowed_tools=["string"], + endpoint="endpoint", + name="name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + mcp_config = await response.parse() + assert_matches_type(McpConfigView, mcp_config, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncRunloop) -> None: + mcp_config = await async_client.mcp_configs.retrieve( + "id", + ) + assert_matches_type(McpConfigView, mcp_config, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncRunloop) -> None: + response = await async_client.mcp_configs.with_raw_response.retrieve( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + mcp_config = await response.parse() + assert_matches_type(McpConfigView, mcp_config, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncRunloop) -> None: + async with async_client.mcp_configs.with_streaming_response.retrieve( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + mcp_config = await response.parse() + assert_matches_type(McpConfigView, mcp_config, 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.mcp_configs.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_update(self, async_client: AsyncRunloop) -> None: + mcp_config = await async_client.mcp_configs.update( + id="id", + ) + assert_matches_type(McpConfigView, mcp_config, path=["response"]) + + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncRunloop) -> None: + mcp_config = await async_client.mcp_configs.update( + id="id", + allowed_tools=["string"], + description="description", + endpoint="endpoint", + name="name", + ) + assert_matches_type(McpConfigView, mcp_config, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncRunloop) -> None: + response = await async_client.mcp_configs.with_raw_response.update( + id="id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + mcp_config = await response.parse() + assert_matches_type(McpConfigView, mcp_config, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncRunloop) -> None: + async with async_client.mcp_configs.with_streaming_response.update( + id="id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + mcp_config = await response.parse() + assert_matches_type(McpConfigView, mcp_config, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncRunloop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.mcp_configs.with_raw_response.update( + id="", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncRunloop) -> None: + mcp_config = await async_client.mcp_configs.list() + assert_matches_type(AsyncMcpConfigsCursorIDPage[McpConfigView], mcp_config, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncRunloop) -> None: + mcp_config = await async_client.mcp_configs.list( + id="id", + limit=0, + name="name", + starting_after="starting_after", + ) + assert_matches_type(AsyncMcpConfigsCursorIDPage[McpConfigView], mcp_config, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncRunloop) -> None: + response = await async_client.mcp_configs.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + mcp_config = await response.parse() + assert_matches_type(AsyncMcpConfigsCursorIDPage[McpConfigView], mcp_config, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncRunloop) -> None: + async with async_client.mcp_configs.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + mcp_config = await response.parse() + assert_matches_type(AsyncMcpConfigsCursorIDPage[McpConfigView], mcp_config, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_delete(self, async_client: AsyncRunloop) -> None: + mcp_config = await async_client.mcp_configs.delete( + "id", + ) + assert_matches_type(McpConfigView, mcp_config, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncRunloop) -> None: + response = await async_client.mcp_configs.with_raw_response.delete( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + mcp_config = await response.parse() + assert_matches_type(McpConfigView, mcp_config, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncRunloop) -> None: + async with async_client.mcp_configs.with_streaming_response.delete( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + mcp_config = await response.parse() + assert_matches_type(McpConfigView, mcp_config, 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.mcp_configs.with_raw_response.delete( + "", + ) diff --git a/tests/sdk/test_async_scorer.py b/tests/sdk/test_async_scorer.py index 253ae9585..23b6628c7 100644 --- a/tests/sdk/test_async_scorer.py +++ b/tests/sdk/test_async_scorer.py @@ -49,21 +49,3 @@ async def test_update(self, mock_async_client: AsyncMock) -> None: assert result == update_response mock_async_client.scenarios.scorers.update.assert_awaited_once() - - @pytest.mark.asyncio - async def test_validate(self, mock_async_client: AsyncMock) -> None: - """Test validate method.""" - validate_response = SimpleNamespace( - name="test_scorer", - scoring_context={}, - scoring_result=SimpleNamespace(score=0.95), - ) - mock_async_client.scenarios.scorers.validate = AsyncMock(return_value=validate_response) - - scorer = AsyncScorer(mock_async_client, "sco_123") - result = await scorer.validate( - scoring_context={"test": "context"}, - ) - - assert result == validate_response - mock_async_client.scenarios.scorers.validate.assert_awaited_once() diff --git a/tests/sdk/test_scorer.py b/tests/sdk/test_scorer.py index 91b430db0..e3363c6dc 100644 --- a/tests/sdk/test_scorer.py +++ b/tests/sdk/test_scorer.py @@ -49,23 +49,3 @@ def test_update(self, mock_client: Mock) -> None: type="updated_scorer", bash_script="echo 'score=1.0'", ) - - def test_validate(self, mock_client: Mock) -> None: - """Test validate method.""" - validate_response = SimpleNamespace( - name="test_scorer", - scoring_context={}, - scoring_result=SimpleNamespace(score=0.95), - ) - mock_client.scenarios.scorers.validate.return_value = validate_response - - scorer = Scorer(mock_client, "sco_123") - result = scorer.validate( - scoring_context={"test": "context"}, - ) - - assert result == validate_response - mock_client.scenarios.scorers.validate.assert_called_once_with( - "sco_123", - scoring_context={"test": "context"}, - ) diff --git a/tests/smoketests/sdk/test_async_scorer.py b/tests/smoketests/sdk/test_async_scorer.py index ce6603d64..a98ae677f 100644 --- a/tests/smoketests/sdk/test_async_scorer.py +++ b/tests/smoketests/sdk/test_async_scorer.py @@ -4,7 +4,6 @@ import pytest -from runloop_api_client import InternalServerError from runloop_api_client.sdk import AsyncRunloopSDK from tests.smoketests.utils import unique_name @@ -71,24 +70,6 @@ async def test_scorer_update(self, async_sdk_client: AsyncRunloopSDK) -> None: assert info.type == updated_type assert info.bash_script == "echo 'score=1.0'" - @pytest.mark.timeout(ONE_MINUTE_TIMEOUT) - async def test_scorer_validate(self, async_sdk_client: AsyncRunloopSDK) -> None: - """Test validating a scorer.""" - scorer_type = unique_name("sdk-async-scorer-validate") - scorer = await async_sdk_client.scorer.create( - type=scorer_type, - bash_script="echo 'score=1.0'", - ) - - try: - result = await scorer.validate( - scoring_context={}, - ) - assert result is not None - except InternalServerError: - # Backend may return 500 for validate endpoint - skip if this happens - pytest.skip("Backend returned 500 for scorer validate endpoint") - class TestAsyncScorerListing: """Test async scorer listing and retrieval operations.""" diff --git a/tests/smoketests/sdk/test_scorer.py b/tests/smoketests/sdk/test_scorer.py index 01df84df9..700cfb1f7 100644 --- a/tests/smoketests/sdk/test_scorer.py +++ b/tests/smoketests/sdk/test_scorer.py @@ -4,7 +4,6 @@ import pytest -from runloop_api_client import InternalServerError from runloop_api_client.sdk import RunloopSDK from tests.smoketests.utils import unique_name @@ -71,24 +70,6 @@ def test_scorer_update(self, sdk_client: RunloopSDK) -> None: assert info.type == updated_type assert info.bash_script == "echo 'score=1.0'" - @pytest.mark.timeout(ONE_MINUTE_TIMEOUT) - def test_scorer_validate(self, sdk_client: RunloopSDK) -> None: - """Test validating a scorer.""" - scorer_type = unique_name("sdk-scorer-validate") - scorer = sdk_client.scorer.create( - type=scorer_type, - bash_script="echo 'score=1.0'", - ) - - try: - result = scorer.validate( - scoring_context={}, - ) - assert result is not None - except InternalServerError: - # Backend may return 500 for validate endpoint - skip if this happens - pytest.skip("Backend returned 500 for scorer validate endpoint") - class TestScorerListing: """Test scorer listing and retrieval operations."""