diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 40293964..c8a8a4f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,6 @@ jobs: lint: name: lint runs-on: ubuntu-latest - steps: - uses: actions/checkout@v4 @@ -30,6 +29,7 @@ jobs: - name: Run lints run: ./scripts/lint + test: name: test runs-on: ubuntu-latest @@ -50,4 +50,3 @@ jobs: - name: Run tests run: ./scripts/test - diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 8f3e0a49..b4e9013b 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.15.0" + ".": "0.16.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 45bcc813..c981a627 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,2 +1,2 @@ -configured_endpoints: 61 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-2d76871c6215358c176295dc168fa7f571f81df1f8df37ecbd1e4e06e7220d67.yml +configured_endpoints: 66 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-5d84d2732400f196ddbe4b4e7c0e98b3f02bab9ce2ed750e69d399d898e496d1.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 7949b852..e1cf569d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 0.16.0 (2025-01-31) + +Full Changelog: [v0.15.0...v0.16.0](https://github.com/runloopai/api-client-python/compare/v0.15.0...v0.16.0) + +### Features + +* **api:** api update ([#510](https://github.com/runloopai/api-client-python/issues/510)) ([914ee46](https://github.com/runloopai/api-client-python/commit/914ee460ccdc6bbd58a7a263d5566716b4bbf9df)) + + +### Chores + +* **internal:** codegen related update ([#508](https://github.com/runloopai/api-client-python/issues/508)) ([278e736](https://github.com/runloopai/api-client-python/commit/278e736787b66e0d0a5fbc64743a386cfefd1994)) + ## 0.15.0 (2025-01-29) Full Changelog: [v0.14.0...v0.15.0](https://github.com/runloopai/api-client-python/compare/v0.14.0...v0.15.0) diff --git a/api.md b/api.md index 46135d10..8967b08e 100644 --- a/api.md +++ b/api.md @@ -100,6 +100,38 @@ Methods: - client.devboxes.upload_file(id, \*\*params) -> object - client.devboxes.write_file_contents(id, \*\*params) -> DevboxExecutionDetailView +## Browsers + +Types: + +```python +from runloop_api_client.types.devboxes import BrowserView +``` + +Methods: + +- client.devboxes.browsers.create() -> BrowserView + +## Computers + +Types: + +```python +from runloop_api_client.types.devboxes import ( + ComputerView, + ComputerKeyboardInteractionResponse, + ComputerMouseInteractionResponse, + ComputerScreenInteractionResponse, +) +``` + +Methods: + +- client.devboxes.computers.create(\*\*params) -> ComputerView +- client.devboxes.computers.keyboard_interaction(id, \*\*params) -> ComputerKeyboardInteractionResponse +- client.devboxes.computers.mouse_interaction(id, \*\*params) -> ComputerMouseInteractionResponse +- client.devboxes.computers.screen_interaction(id, \*\*params) -> ComputerScreenInteractionResponse + ## Lsp Types: diff --git a/pyproject.toml b/pyproject.toml index 87f3f45c..f02034ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "runloop_api_client" -version = "0.15.0" +version = "0.16.0" description = "The official Python library for the runloop API" dynamic = ["readme"] license = "MIT" @@ -129,6 +129,7 @@ testpaths = ["tests"] addopts = "--tb=short" xfail_strict = true asyncio_mode = "auto" +asyncio_default_fixture_loop_scope = "session" filterwarnings = [ "error" ] diff --git a/scripts/bootstrap b/scripts/bootstrap index 8c5c60eb..e84fe62c 100755 --- a/scripts/bootstrap +++ b/scripts/bootstrap @@ -4,7 +4,7 @@ set -e cd "$(dirname "$0")/.." -if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ]; then +if ! command -v rye >/dev/null 2>&1 && [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ]; then brew bundle check >/dev/null 2>&1 || { echo "==> Installing Homebrew dependencies…" brew bundle diff --git a/scripts/lint b/scripts/lint index e4375378..6736f5cf 100755 --- a/scripts/lint +++ b/scripts/lint @@ -9,4 +9,3 @@ rye run lint echo "==> Making sure it imports" rye run python -c 'import runloop_api_client' - diff --git a/src/runloop_api_client/_response.py b/src/runloop_api_client/_response.py index d765ed40..1e0ab213 100644 --- a/src/runloop_api_client/_response.py +++ b/src/runloop_api_client/_response.py @@ -136,6 +136,8 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: if cast_to and is_annotated_type(cast_to): cast_to = extract_type_arg(cast_to, 0) + origin = get_origin(cast_to) or cast_to + if self._is_sse_stream: if to: if not is_stream_class_type(to): @@ -195,8 +197,6 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: if cast_to == bool: return cast(R, response.text.lower() == "true") - origin = get_origin(cast_to) or cast_to - if origin == APIResponse: raise RuntimeError("Unexpected state - cast_to is `APIResponse`") diff --git a/src/runloop_api_client/_version.py b/src/runloop_api_client/_version.py index c75e8e65..e302279f 100644 --- a/src/runloop_api_client/_version.py +++ b/src/runloop_api_client/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "runloop_api_client" -__version__ = "0.15.0" # x-release-please-version +__version__ = "0.16.0" # x-release-please-version diff --git a/src/runloop_api_client/resources/benchmarks/benchmarks.py b/src/runloop_api_client/resources/benchmarks/benchmarks.py index 9b016c68..65049475 100644 --- a/src/runloop_api_client/resources/benchmarks/benchmarks.py +++ b/src/runloop_api_client/resources/benchmarks/benchmarks.py @@ -147,6 +147,7 @@ def list( self, *, limit: int | NotGiven = NOT_GIVEN, + public: bool | NotGiven = NOT_GIVEN, starting_after: str | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -161,6 +162,9 @@ def list( Args: limit: The limit of items to return. Default is 20. + public: List public benchmarks, e.g. SWE-bench. Defaults to false, i.e. only + user-defined benchmarks are listed. + starting_after: Load the next page of data starting after the item with the given ID. extra_headers: Send extra headers @@ -181,6 +185,7 @@ def list( query=maybe_transform( { "limit": limit, + "public": public, "starting_after": starting_after, }, benchmark_list_params.BenchmarkListParams, @@ -351,6 +356,7 @@ async def list( self, *, limit: int | NotGiven = NOT_GIVEN, + public: bool | NotGiven = NOT_GIVEN, starting_after: str | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -365,6 +371,9 @@ async def list( Args: limit: The limit of items to return. Default is 20. + public: List public benchmarks, e.g. SWE-bench. Defaults to false, i.e. only + user-defined benchmarks are listed. + starting_after: Load the next page of data starting after the item with the given ID. extra_headers: Send extra headers @@ -385,6 +394,7 @@ async def list( query=await async_maybe_transform( { "limit": limit, + "public": public, "starting_after": starting_after, }, benchmark_list_params.BenchmarkListParams, diff --git a/src/runloop_api_client/resources/benchmarks/runs.py b/src/runloop_api_client/resources/benchmarks/runs.py index 3a552734..3866eb99 100644 --- a/src/runloop_api_client/resources/benchmarks/runs.py +++ b/src/runloop_api_client/resources/benchmarks/runs.py @@ -81,6 +81,7 @@ def retrieve( def list( self, *, + benchmark_id: str | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, starting_after: str | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -94,6 +95,8 @@ def list( List all BenchmarkRuns matching filter. Args: + benchmark_id: The Benchmark ID to filter by. + limit: The limit of items to return. Default is 20. starting_after: Load the next page of data starting after the item with the given ID. @@ -115,6 +118,7 @@ def list( timeout=timeout, query=maybe_transform( { + "benchmark_id": benchmark_id, "limit": limit, "starting_after": starting_after, }, @@ -221,6 +225,7 @@ async def retrieve( async def list( self, *, + benchmark_id: str | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, starting_after: str | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -234,6 +239,8 @@ async def list( List all BenchmarkRuns matching filter. Args: + benchmark_id: The Benchmark ID to filter by. + limit: The limit of items to return. Default is 20. starting_after: Load the next page of data starting after the item with the given ID. @@ -255,6 +262,7 @@ async def list( timeout=timeout, query=await async_maybe_transform( { + "benchmark_id": benchmark_id, "limit": limit, "starting_after": starting_after, }, diff --git a/src/runloop_api_client/resources/devboxes/__init__.py b/src/runloop_api_client/resources/devboxes/__init__.py index d5378fb5..1e09245d 100644 --- a/src/runloop_api_client/resources/devboxes/__init__.py +++ b/src/runloop_api_client/resources/devboxes/__init__.py @@ -16,6 +16,14 @@ LogsResourceWithStreamingResponse, AsyncLogsResourceWithStreamingResponse, ) +from .browsers import ( + BrowsersResource, + AsyncBrowsersResource, + BrowsersResourceWithRawResponse, + AsyncBrowsersResourceWithRawResponse, + BrowsersResourceWithStreamingResponse, + AsyncBrowsersResourceWithStreamingResponse, +) from .devboxes import ( DevboxesResource, AsyncDevboxesResource, @@ -24,6 +32,14 @@ DevboxesResourceWithStreamingResponse, AsyncDevboxesResourceWithStreamingResponse, ) +from .computers import ( + ComputersResource, + AsyncComputersResource, + ComputersResourceWithRawResponse, + AsyncComputersResourceWithRawResponse, + ComputersResourceWithStreamingResponse, + AsyncComputersResourceWithStreamingResponse, +) from .executions import ( ExecutionsResource, AsyncExecutionsResource, @@ -34,6 +50,18 @@ ) __all__ = [ + "BrowsersResource", + "AsyncBrowsersResource", + "BrowsersResourceWithRawResponse", + "AsyncBrowsersResourceWithRawResponse", + "BrowsersResourceWithStreamingResponse", + "AsyncBrowsersResourceWithStreamingResponse", + "ComputersResource", + "AsyncComputersResource", + "ComputersResourceWithRawResponse", + "AsyncComputersResourceWithRawResponse", + "ComputersResourceWithStreamingResponse", + "AsyncComputersResourceWithStreamingResponse", "LspResource", "AsyncLspResource", "LspResourceWithRawResponse", diff --git a/src/runloop_api_client/resources/devboxes/browsers.py b/src/runloop_api_client/resources/devboxes/browsers.py new file mode 100644 index 00000000..59d264bc --- /dev/null +++ b/src/runloop_api_client/resources/devboxes/browsers.py @@ -0,0 +1,155 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..._base_client import make_request_options +from ...types.devboxes.browser_view import BrowserView + +__all__ = ["BrowsersResource", "AsyncBrowsersResource"] + + +class BrowsersResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> BrowsersResourceWithRawResponse: + """ + 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 BrowsersResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> BrowsersResourceWithStreamingResponse: + """ + 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 BrowsersResourceWithStreamingResponse(self) + + def create( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + idempotency_key: str | None = None, + ) -> BrowserView: + """Create a Devbox that has a managed Browser and begin the boot process. + + As part + of booting the Devbox, the browser will automatically be started with connection + utilities activated. + """ + return self._post( + "/v1/devboxes/browsers", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=BrowserView, + ) + + +class AsyncBrowsersResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncBrowsersResourceWithRawResponse: + """ + 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 AsyncBrowsersResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncBrowsersResourceWithStreamingResponse: + """ + 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 AsyncBrowsersResourceWithStreamingResponse(self) + + async def create( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + idempotency_key: str | None = None, + ) -> BrowserView: + """Create a Devbox that has a managed Browser and begin the boot process. + + As part + of booting the Devbox, the browser will automatically be started with connection + utilities activated. + """ + return await self._post( + "/v1/devboxes/browsers", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=BrowserView, + ) + + +class BrowsersResourceWithRawResponse: + def __init__(self, browsers: BrowsersResource) -> None: + self._browsers = browsers + + self.create = to_raw_response_wrapper( + browsers.create, + ) + + +class AsyncBrowsersResourceWithRawResponse: + def __init__(self, browsers: AsyncBrowsersResource) -> None: + self._browsers = browsers + + self.create = async_to_raw_response_wrapper( + browsers.create, + ) + + +class BrowsersResourceWithStreamingResponse: + def __init__(self, browsers: BrowsersResource) -> None: + self._browsers = browsers + + self.create = to_streamed_response_wrapper( + browsers.create, + ) + + +class AsyncBrowsersResourceWithStreamingResponse: + def __init__(self, browsers: AsyncBrowsersResource) -> None: + self._browsers = browsers + + self.create = async_to_streamed_response_wrapper( + browsers.create, + ) diff --git a/src/runloop_api_client/resources/devboxes/computers.py b/src/runloop_api_client/resources/devboxes/computers.py new file mode 100644 index 00000000..c1079130 --- /dev/null +++ b/src/runloop_api_client/resources/devboxes/computers.py @@ -0,0 +1,559 @@ +# 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 Literal + +import httpx + +from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ..._utils import ( + maybe_transform, + async_maybe_transform, +) +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..._base_client import make_request_options +from ...types.devboxes import ( + computer_create_params, + computer_mouse_interaction_params, + computer_screen_interaction_params, + computer_keyboard_interaction_params, +) +from ...types.devboxes.computer_view import ComputerView +from ...types.devboxes.computer_mouse_interaction_response import ComputerMouseInteractionResponse +from ...types.devboxes.computer_screen_interaction_response import ComputerScreenInteractionResponse +from ...types.devboxes.computer_keyboard_interaction_response import ComputerKeyboardInteractionResponse + +__all__ = ["ComputersResource", "AsyncComputersResource"] + + +class ComputersResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ComputersResourceWithRawResponse: + """ + 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 ComputersResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ComputersResourceWithStreamingResponse: + """ + 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 ComputersResourceWithStreamingResponse(self) + + def create( + self, + *, + display_dimensions: Optional[computer_create_params.DisplayDimensions] | NotGiven = NOT_GIVEN, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + idempotency_key: str | None = None, + ) -> ComputerView: + """Create a Computer and begin the boot process. + + The Computer will initially launch + in the 'provisioning' state while Runloop allocates the necessary + infrastructure. It will transition to the 'initializing' state while the booted + Computer runs any Runloop or user defined set up scripts. Finally, the Computer + will transition to the 'running' state when it is ready for use. + + Args: + display_dimensions: Customize the dimensions of the computer display. + + 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/devboxes/computers", + body=maybe_transform( + {"display_dimensions": display_dimensions}, computer_create_params.ComputerCreateParams + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=ComputerView, + ) + + def keyboard_interaction( + self, + id: str, + *, + action: Literal["key", "type"], + text: Optional[str] | NotGiven = NOT_GIVEN, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + idempotency_key: str | None = None, + ) -> ComputerKeyboardInteractionResponse: + """ + Perform the specified keyboard interaction on the Computer identified by the + given ID. + + Args: + action: The keyboard action to perform. + + text: The text to type or the key (with optional modifier) to press. + + 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/devboxes/computers/{id}/keyboard_interaction", + body=maybe_transform( + { + "action": action, + "text": text, + }, + computer_keyboard_interaction_params.ComputerKeyboardInteractionParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=ComputerKeyboardInteractionResponse, + ) + + def mouse_interaction( + self, + id: str, + *, + action: Literal["mouse_move", "left_click", "left_click_drag", "right_click", "middle_click", "double_click"], + coordinate: Optional[computer_mouse_interaction_params.Coordinate] | NotGiven = NOT_GIVEN, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + idempotency_key: str | None = None, + ) -> ComputerMouseInteractionResponse: + """ + Perform the specified mouse interaction on the Computer identified by the given + ID. + + Args: + action: The mouse action to perform. + + coordinate: The x (pixels from the left) and y (pixels from the top) coordinates for the + mouse to move or click-drag. Required only by `action=mouse_move` or + `action=left_click_drag` + + 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/devboxes/computers/{id}/mouse_interaction", + body=maybe_transform( + { + "action": action, + "coordinate": coordinate, + }, + computer_mouse_interaction_params.ComputerMouseInteractionParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=ComputerMouseInteractionResponse, + ) + + def screen_interaction( + self, + id: str, + *, + action: Literal["screenshot", "cursor_position"], + # 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, + ) -> ComputerScreenInteractionResponse: + """ + Perform the specified screen interaction on the Computer identified by the given + ID. + + Args: + action: The screen action to perform. + + 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/devboxes/computers/{id}/screen_interaction", + body=maybe_transform( + {"action": action}, computer_screen_interaction_params.ComputerScreenInteractionParams + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=ComputerScreenInteractionResponse, + ) + + +class AsyncComputersResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncComputersResourceWithRawResponse: + """ + 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 AsyncComputersResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncComputersResourceWithStreamingResponse: + """ + 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 AsyncComputersResourceWithStreamingResponse(self) + + async def create( + self, + *, + display_dimensions: Optional[computer_create_params.DisplayDimensions] | NotGiven = NOT_GIVEN, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + idempotency_key: str | None = None, + ) -> ComputerView: + """Create a Computer and begin the boot process. + + The Computer will initially launch + in the 'provisioning' state while Runloop allocates the necessary + infrastructure. It will transition to the 'initializing' state while the booted + Computer runs any Runloop or user defined set up scripts. Finally, the Computer + will transition to the 'running' state when it is ready for use. + + Args: + display_dimensions: Customize the dimensions of the computer display. + + 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/devboxes/computers", + body=await async_maybe_transform( + {"display_dimensions": display_dimensions}, computer_create_params.ComputerCreateParams + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=ComputerView, + ) + + async def keyboard_interaction( + self, + id: str, + *, + action: Literal["key", "type"], + text: Optional[str] | NotGiven = NOT_GIVEN, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + idempotency_key: str | None = None, + ) -> ComputerKeyboardInteractionResponse: + """ + Perform the specified keyboard interaction on the Computer identified by the + given ID. + + Args: + action: The keyboard action to perform. + + text: The text to type or the key (with optional modifier) to press. + + 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/devboxes/computers/{id}/keyboard_interaction", + body=await async_maybe_transform( + { + "action": action, + "text": text, + }, + computer_keyboard_interaction_params.ComputerKeyboardInteractionParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=ComputerKeyboardInteractionResponse, + ) + + async def mouse_interaction( + self, + id: str, + *, + action: Literal["mouse_move", "left_click", "left_click_drag", "right_click", "middle_click", "double_click"], + coordinate: Optional[computer_mouse_interaction_params.Coordinate] | NotGiven = NOT_GIVEN, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + idempotency_key: str | None = None, + ) -> ComputerMouseInteractionResponse: + """ + Perform the specified mouse interaction on the Computer identified by the given + ID. + + Args: + action: The mouse action to perform. + + coordinate: The x (pixels from the left) and y (pixels from the top) coordinates for the + mouse to move or click-drag. Required only by `action=mouse_move` or + `action=left_click_drag` + + 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/devboxes/computers/{id}/mouse_interaction", + body=await async_maybe_transform( + { + "action": action, + "coordinate": coordinate, + }, + computer_mouse_interaction_params.ComputerMouseInteractionParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=ComputerMouseInteractionResponse, + ) + + async def screen_interaction( + self, + id: str, + *, + action: Literal["screenshot", "cursor_position"], + # 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, + ) -> ComputerScreenInteractionResponse: + """ + Perform the specified screen interaction on the Computer identified by the given + ID. + + Args: + action: The screen action to perform. + + 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/devboxes/computers/{id}/screen_interaction", + body=await async_maybe_transform( + {"action": action}, computer_screen_interaction_params.ComputerScreenInteractionParams + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=ComputerScreenInteractionResponse, + ) + + +class ComputersResourceWithRawResponse: + def __init__(self, computers: ComputersResource) -> None: + self._computers = computers + + self.create = to_raw_response_wrapper( + computers.create, + ) + self.keyboard_interaction = to_raw_response_wrapper( + computers.keyboard_interaction, + ) + self.mouse_interaction = to_raw_response_wrapper( + computers.mouse_interaction, + ) + self.screen_interaction = to_raw_response_wrapper( + computers.screen_interaction, + ) + + +class AsyncComputersResourceWithRawResponse: + def __init__(self, computers: AsyncComputersResource) -> None: + self._computers = computers + + self.create = async_to_raw_response_wrapper( + computers.create, + ) + self.keyboard_interaction = async_to_raw_response_wrapper( + computers.keyboard_interaction, + ) + self.mouse_interaction = async_to_raw_response_wrapper( + computers.mouse_interaction, + ) + self.screen_interaction = async_to_raw_response_wrapper( + computers.screen_interaction, + ) + + +class ComputersResourceWithStreamingResponse: + def __init__(self, computers: ComputersResource) -> None: + self._computers = computers + + self.create = to_streamed_response_wrapper( + computers.create, + ) + self.keyboard_interaction = to_streamed_response_wrapper( + computers.keyboard_interaction, + ) + self.mouse_interaction = to_streamed_response_wrapper( + computers.mouse_interaction, + ) + self.screen_interaction = to_streamed_response_wrapper( + computers.screen_interaction, + ) + + +class AsyncComputersResourceWithStreamingResponse: + def __init__(self, computers: AsyncComputersResource) -> None: + self._computers = computers + + self.create = async_to_streamed_response_wrapper( + computers.create, + ) + self.keyboard_interaction = async_to_streamed_response_wrapper( + computers.keyboard_interaction, + ) + self.mouse_interaction = async_to_streamed_response_wrapper( + computers.mouse_interaction, + ) + self.screen_interaction = async_to_streamed_response_wrapper( + computers.screen_interaction, + ) diff --git a/src/runloop_api_client/resources/devboxes/devboxes.py b/src/runloop_api_client/resources/devboxes/devboxes.py index 3d27b145..14bd900a 100644 --- a/src/runloop_api_client/resources/devboxes/devboxes.py +++ b/src/runloop_api_client/resources/devboxes/devboxes.py @@ -44,7 +44,23 @@ deepcopy_minimal, async_maybe_transform, ) +from .browsers import ( + BrowsersResource, + AsyncBrowsersResource, + BrowsersResourceWithRawResponse, + AsyncBrowsersResourceWithRawResponse, + BrowsersResourceWithStreamingResponse, + AsyncBrowsersResourceWithStreamingResponse, +) from ..._compat import cached_property +from .computers import ( + ComputersResource, + AsyncComputersResource, + ComputersResourceWithRawResponse, + AsyncComputersResourceWithRawResponse, + ComputersResourceWithStreamingResponse, + AsyncComputersResourceWithStreamingResponse, +) from .executions import ( ExecutionsResource, AsyncExecutionsResource, @@ -92,6 +108,14 @@ DEVBOX_BOOTING_STATES = frozenset(('provisioning', 'initializing')) class DevboxesResource(SyncAPIResource): + @cached_property + def browsers(self) -> BrowsersResource: + return BrowsersResource(self._client) + + @cached_property + def computers(self) -> ComputersResource: + return ComputersResource(self._client) + @cached_property def lsp(self) -> LspResource: return LspResource(self._client) @@ -1215,6 +1239,14 @@ def write_file_contents( class AsyncDevboxesResource(AsyncAPIResource): + @cached_property + def browsers(self) -> AsyncBrowsersResource: + return AsyncBrowsersResource(self._client) + + @cached_property + def computers(self) -> AsyncComputersResource: + return AsyncComputersResource(self._client) + @cached_property def lsp(self) -> AsyncLspResource: return AsyncLspResource(self._client) @@ -2397,6 +2429,14 @@ def __init__(self, devboxes: DevboxesResource) -> None: devboxes.write_file_contents, ) + @cached_property + def browsers(self) -> BrowsersResourceWithRawResponse: + return BrowsersResourceWithRawResponse(self._devboxes.browsers) + + @cached_property + def computers(self) -> ComputersResourceWithRawResponse: + return ComputersResourceWithRawResponse(self._devboxes.computers) + @cached_property def lsp(self) -> LspResourceWithRawResponse: return LspResourceWithRawResponse(self._devboxes.lsp) @@ -2473,6 +2513,14 @@ def __init__(self, devboxes: AsyncDevboxesResource) -> None: devboxes.write_file_contents, ) + @cached_property + def browsers(self) -> AsyncBrowsersResourceWithRawResponse: + return AsyncBrowsersResourceWithRawResponse(self._devboxes.browsers) + + @cached_property + def computers(self) -> AsyncComputersResourceWithRawResponse: + return AsyncComputersResourceWithRawResponse(self._devboxes.computers) + @cached_property def lsp(self) -> AsyncLspResourceWithRawResponse: return AsyncLspResourceWithRawResponse(self._devboxes.lsp) @@ -2549,6 +2597,14 @@ def __init__(self, devboxes: DevboxesResource) -> None: devboxes.write_file_contents, ) + @cached_property + def browsers(self) -> BrowsersResourceWithStreamingResponse: + return BrowsersResourceWithStreamingResponse(self._devboxes.browsers) + + @cached_property + def computers(self) -> ComputersResourceWithStreamingResponse: + return ComputersResourceWithStreamingResponse(self._devboxes.computers) + @cached_property def lsp(self) -> LspResourceWithStreamingResponse: return LspResourceWithStreamingResponse(self._devboxes.lsp) @@ -2625,6 +2681,14 @@ def __init__(self, devboxes: AsyncDevboxesResource) -> None: devboxes.write_file_contents, ) + @cached_property + def browsers(self) -> AsyncBrowsersResourceWithStreamingResponse: + return AsyncBrowsersResourceWithStreamingResponse(self._devboxes.browsers) + + @cached_property + def computers(self) -> AsyncComputersResourceWithStreamingResponse: + return AsyncComputersResourceWithStreamingResponse(self._devboxes.computers) + @cached_property def lsp(self) -> AsyncLspResourceWithStreamingResponse: return AsyncLspResourceWithStreamingResponse(self._devboxes.lsp) diff --git a/src/runloop_api_client/resources/scenarios/runs.py b/src/runloop_api_client/resources/scenarios/runs.py index cec29302..2226123a 100644 --- a/src/runloop_api_client/resources/scenarios/runs.py +++ b/src/runloop_api_client/resources/scenarios/runs.py @@ -82,6 +82,7 @@ def list( self, *, limit: int | NotGiven = NOT_GIVEN, + scenario_id: bool | NotGiven = NOT_GIVEN, starting_after: str | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -96,6 +97,8 @@ def list( Args: limit: The limit of items to return. Default is 20. + scenario_id: Filter runs associated to Scenario given ID + starting_after: Load the next page of data starting after the item with the given ID. extra_headers: Send extra headers @@ -116,6 +119,7 @@ def list( query=maybe_transform( { "limit": limit, + "scenario_id": scenario_id, "starting_after": starting_after, }, run_list_params.RunListParams, @@ -262,6 +266,7 @@ async def list( self, *, limit: int | NotGiven = NOT_GIVEN, + scenario_id: bool | NotGiven = NOT_GIVEN, starting_after: str | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -276,6 +281,8 @@ async def list( Args: limit: The limit of items to return. Default is 20. + scenario_id: Filter runs associated to Scenario given ID + starting_after: Load the next page of data starting after the item with the given ID. extra_headers: Send extra headers @@ -296,6 +303,7 @@ async def list( query=await async_maybe_transform( { "limit": limit, + "scenario_id": scenario_id, "starting_after": starting_after, }, run_list_params.RunListParams, diff --git a/src/runloop_api_client/types/benchmark_list_params.py b/src/runloop_api_client/types/benchmark_list_params.py index 51b2b132..3eef4e03 100644 --- a/src/runloop_api_client/types/benchmark_list_params.py +++ b/src/runloop_api_client/types/benchmark_list_params.py @@ -11,5 +11,11 @@ class BenchmarkListParams(TypedDict, total=False): limit: int """The limit of items to return. Default is 20.""" + public: bool + """List public benchmarks, e.g. + + SWE-bench. Defaults to false, i.e. only user-defined benchmarks are listed. + """ + 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/benchmark_run_view.py b/src/runloop_api_client/types/benchmark_run_view.py index 08507340..bce32b0c 100644 --- a/src/runloop_api_client/types/benchmark_run_view.py +++ b/src/runloop_api_client/types/benchmark_run_view.py @@ -20,3 +20,9 @@ class BenchmarkRunView(BaseModel): name: Optional[str] = None """The name of the BenchmarkRun.""" + + score: Optional[float] = None + """The final score across the BenchmarkRun, present once completed. + + Calculated as sum of scenario scores / number of scenario runs. + """ diff --git a/src/runloop_api_client/types/benchmarks/run_list_params.py b/src/runloop_api_client/types/benchmarks/run_list_params.py index a8be85a7..f93695b2 100644 --- a/src/runloop_api_client/types/benchmarks/run_list_params.py +++ b/src/runloop_api_client/types/benchmarks/run_list_params.py @@ -8,6 +8,9 @@ class RunListParams(TypedDict, total=False): + benchmark_id: str + """The Benchmark ID to filter by.""" + limit: int """The limit of items to return. Default is 20.""" diff --git a/src/runloop_api_client/types/devbox_view.py b/src/runloop_api_client/types/devbox_view.py index 838a5ea7..f645afde 100644 --- a/src/runloop_api_client/types/devbox_view.py +++ b/src/runloop_api_client/types/devbox_view.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Dict, Optional +from typing import Dict, List, Optional from typing_extensions import Literal from .._models import BaseModel @@ -13,6 +13,13 @@ class DevboxView(BaseModel): id: str """The ID of the Devbox.""" + capabilities: List[Literal["unknown", "computer_usage", "browser_usage"]] + """A list of capability groups this devbox has access to. + + This allows devboxes to be compatible with certain tools sets like computer + usage APIs. + """ + create_time_ms: int """Creation time of the Devbox (Unix timestamp milliseconds).""" diff --git a/src/runloop_api_client/types/devboxes/__init__.py b/src/runloop_api_client/types/devboxes/__init__.py index 30d13b0f..92ea981d 100644 --- a/src/runloop_api_client/types/devboxes/__init__.py +++ b/src/runloop_api_client/types/devboxes/__init__.py @@ -16,8 +16,10 @@ from .symbol_kind import SymbolKind as SymbolKind from .symbol_type import SymbolType as SymbolType from .base_command import BaseCommand as BaseCommand +from .browser_view import BrowserView as BrowserView from .document_uri import DocumentUri as DocumentUri from .base_location import BaseLocation as BaseLocation +from .computer_view import ComputerView as ComputerView from .base_signature import BaseSignature as BaseSignature from .diagnostic_tag import DiagnosticTag as DiagnosticTag from .l_sp_any_param import LSpAnyParam as LSpAnyParam @@ -44,6 +46,7 @@ from .lsp_references_params import LspReferencesParams as LspReferencesParams from .watched_file_response import WatchedFileResponse as WatchedFileResponse from .code_description_param import CodeDescriptionParam as CodeDescriptionParam +from .computer_create_params import ComputerCreateParams as ComputerCreateParams from .file_contents_response import FileContentsResponse as FileContentsResponse from .health_status_response import HealthStatusResponse as HealthStatusResponse from .lsp_diagnostics_params import LspDiagnosticsParams as LspDiagnosticsParams @@ -66,8 +69,16 @@ from .lsp_set_watch_directory_params import LspSetWatchDirectoryParams as LspSetWatchDirectoryParams from .lsp_get_code_segment_info_params import LspGetCodeSegmentInfoParams as LspGetCodeSegmentInfoParams from .lsp_set_watch_directory_response import LspSetWatchDirectoryResponse as LspSetWatchDirectoryResponse +from .computer_mouse_interaction_params import ComputerMouseInteractionParams as ComputerMouseInteractionParams +from .computer_screen_interaction_params import ComputerScreenInteractionParams as ComputerScreenInteractionParams +from .computer_mouse_interaction_response import ComputerMouseInteractionResponse as ComputerMouseInteractionResponse from .record_string_text_edit_array_param import RecordStringTextEditArrayParam as RecordStringTextEditArrayParam +from .computer_keyboard_interaction_params import ComputerKeyboardInteractionParams as ComputerKeyboardInteractionParams +from .computer_screen_interaction_response import ComputerScreenInteractionResponse as ComputerScreenInteractionResponse from .diagnostic_related_information_param import DiagnosticRelatedInformationParam as DiagnosticRelatedInformationParam +from .computer_keyboard_interaction_response import ( + ComputerKeyboardInteractionResponse as ComputerKeyboardInteractionResponse, +) from .lsp_get_code_actions_for_diagnostic_params import ( LspGetCodeActionsForDiagnosticParams as LspGetCodeActionsForDiagnosticParams, ) diff --git a/src/runloop_api_client/types/devboxes/browser_view.py b/src/runloop_api_client/types/devboxes/browser_view.py new file mode 100644 index 00000000..ef9303f0 --- /dev/null +++ b/src/runloop_api_client/types/devboxes/browser_view.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + + +from ..._models import BaseModel +from ..devbox_view import DevboxView + +__all__ = ["BrowserView"] + + +class BrowserView(BaseModel): + connection_url: str + """ + The url to enable remote connection from browser automation tools like + playwright. + """ + + devbox: DevboxView + """The underlying devbox the browser setup is running on.""" + + live_view_url: str + """ + The url to view the browser window and enable user interactions via their own + browser. + """ diff --git a/src/runloop_api_client/types/devboxes/computer_create_params.py b/src/runloop_api_client/types/devboxes/computer_create_params.py new file mode 100644 index 00000000..15c00cff --- /dev/null +++ b/src/runloop_api_client/types/devboxes/computer_create_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 import Optional +from typing_extensions import Required, TypedDict + +__all__ = ["ComputerCreateParams", "DisplayDimensions"] + + +class ComputerCreateParams(TypedDict, total=False): + display_dimensions: Optional[DisplayDimensions] + """Customize the dimensions of the computer display.""" + + +class DisplayDimensions(TypedDict, total=False): + display_height_px: Required[int] + """The height of the display being controlled by the model in pixels.""" + + display_width_px: Required[int] + """The width of the display being controlled by the model in pixels.""" diff --git a/src/runloop_api_client/types/devboxes/computer_keyboard_interaction_params.py b/src/runloop_api_client/types/devboxes/computer_keyboard_interaction_params.py new file mode 100644 index 00000000..c7c2a8f3 --- /dev/null +++ b/src/runloop_api_client/types/devboxes/computer_keyboard_interaction_params.py @@ -0,0 +1,16 @@ +# 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 Literal, Required, TypedDict + +__all__ = ["ComputerKeyboardInteractionParams"] + + +class ComputerKeyboardInteractionParams(TypedDict, total=False): + action: Required[Literal["key", "type"]] + """The keyboard action to perform.""" + + text: Optional[str] + """The text to type or the key (with optional modifier) to press.""" diff --git a/src/runloop_api_client/types/devboxes/computer_keyboard_interaction_response.py b/src/runloop_api_client/types/devboxes/computer_keyboard_interaction_response.py new file mode 100644 index 00000000..6990bc74 --- /dev/null +++ b/src/runloop_api_client/types/devboxes/computer_keyboard_interaction_response.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel + +__all__ = ["ComputerKeyboardInteractionResponse"] + + +class ComputerKeyboardInteractionResponse(BaseModel): + error: Optional[str] = None + + latest_screenshot_base64_img: Optional[str] = None + + output: Optional[str] = None diff --git a/src/runloop_api_client/types/devboxes/computer_mouse_interaction_params.py b/src/runloop_api_client/types/devboxes/computer_mouse_interaction_params.py new file mode 100644 index 00000000..b28a0723 --- /dev/null +++ b/src/runloop_api_client/types/devboxes/computer_mouse_interaction_params.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ComputerMouseInteractionParams", "Coordinate"] + + +class ComputerMouseInteractionParams(TypedDict, total=False): + action: Required[ + Literal["mouse_move", "left_click", "left_click_drag", "right_click", "middle_click", "double_click"] + ] + """The mouse action to perform.""" + + coordinate: Optional[Coordinate] + """ + The x (pixels from the left) and y (pixels from the top) coordinates for the + mouse to move or click-drag. Required only by `action=mouse_move` or + `action=left_click_drag` + """ + + +class Coordinate(TypedDict, total=False): + x: Required[int] + """The x coordinate (pixels from the left) for the mouse to move or click-drag.""" + + y: Required[int] + """The y coordinate (pixels from the top) for the mouse to move or click-drag.""" diff --git a/src/runloop_api_client/types/devboxes/computer_mouse_interaction_response.py b/src/runloop_api_client/types/devboxes/computer_mouse_interaction_response.py new file mode 100644 index 00000000..cbaab9dc --- /dev/null +++ b/src/runloop_api_client/types/devboxes/computer_mouse_interaction_response.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel + +__all__ = ["ComputerMouseInteractionResponse"] + + +class ComputerMouseInteractionResponse(BaseModel): + error: Optional[str] = None + + latest_screenshot_base64_img: Optional[str] = None + + output: Optional[str] = None diff --git a/src/runloop_api_client/types/devboxes/computer_screen_interaction_params.py b/src/runloop_api_client/types/devboxes/computer_screen_interaction_params.py new file mode 100644 index 00000000..8423249b --- /dev/null +++ b/src/runloop_api_client/types/devboxes/computer_screen_interaction_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ComputerScreenInteractionParams"] + + +class ComputerScreenInteractionParams(TypedDict, total=False): + action: Required[Literal["screenshot", "cursor_position"]] + """The screen action to perform.""" diff --git a/src/runloop_api_client/types/devboxes/computer_screen_interaction_response.py b/src/runloop_api_client/types/devboxes/computer_screen_interaction_response.py new file mode 100644 index 00000000..3ad4cb5c --- /dev/null +++ b/src/runloop_api_client/types/devboxes/computer_screen_interaction_response.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel + +__all__ = ["ComputerScreenInteractionResponse"] + + +class ComputerScreenInteractionResponse(BaseModel): + error: Optional[str] = None + + latest_screenshot_base64_img: Optional[str] = None + + output: Optional[str] = None diff --git a/src/runloop_api_client/types/devboxes/computer_view.py b/src/runloop_api_client/types/devboxes/computer_view.py new file mode 100644 index 00000000..858aa27a --- /dev/null +++ b/src/runloop_api_client/types/devboxes/computer_view.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + + +from ..._models import BaseModel +from ..devbox_view import DevboxView + +__all__ = ["ComputerView"] + + +class ComputerView(BaseModel): + devbox: DevboxView + """The underlying devbox the computer setup is running on.""" + + live_screen_url: str + """The http tunnel to connect and view the live screen of the computer.""" diff --git a/src/runloop_api_client/types/scenario_run_view.py b/src/runloop_api_client/types/scenario_run_view.py index 33c18100..33da1722 100644 --- a/src/runloop_api_client/types/scenario_run_view.py +++ b/src/runloop_api_client/types/scenario_run_view.py @@ -19,7 +19,7 @@ class ScenarioRunView(BaseModel): scenario_id: str """ID of the Scenario that has been run.""" - state: Literal["running", "scoring", "completed", "canceled", "timeout", "failed"] + state: Literal["running", "scoring", "scored", "completed", "canceled", "timeout", "failed"] """The state of the ScenarioRun.""" benchmark_run_id: Optional[str] = None @@ -28,5 +28,8 @@ class ScenarioRunView(BaseModel): duration_ms: Optional[int] = None """Duration scenario took to run.""" + name: Optional[str] = None + """Optional name of ScenarioRun.""" + scoring_contract_result: Optional[ScoringContractResultView] = None """The input context for the Scenario.""" diff --git a/src/runloop_api_client/types/scenarios/run_list_params.py b/src/runloop_api_client/types/scenarios/run_list_params.py index a8be85a7..86a3b1ef 100644 --- a/src/runloop_api_client/types/scenarios/run_list_params.py +++ b/src/runloop_api_client/types/scenarios/run_list_params.py @@ -11,5 +11,8 @@ class RunListParams(TypedDict, total=False): limit: int """The limit of items to return. Default is 20.""" + scenario_id: bool + """Filter runs associated to Scenario given ID""" + starting_after: str """Load the next page of data starting after the item with the given ID.""" diff --git a/tests/api_resources/benchmarks/test_runs.py b/tests/api_resources/benchmarks/test_runs.py index 6f7ac0b8..31ca76fe 100644 --- a/tests/api_resources/benchmarks/test_runs.py +++ b/tests/api_resources/benchmarks/test_runs.py @@ -63,6 +63,7 @@ def test_method_list(self, client: Runloop) -> None: @parametrize def test_method_list_with_all_params(self, client: Runloop) -> None: run = client.benchmarks.runs.list( + benchmark_id="benchmark_id", limit=0, starting_after="starting_after", ) @@ -176,6 +177,7 @@ async def test_method_list(self, async_client: AsyncRunloop) -> None: @parametrize async def test_method_list_with_all_params(self, async_client: AsyncRunloop) -> None: run = await async_client.benchmarks.runs.list( + benchmark_id="benchmark_id", limit=0, starting_after="starting_after", ) diff --git a/tests/api_resources/devboxes/test_browsers.py b/tests/api_resources/devboxes/test_browsers.py new file mode 100644 index 00000000..9b1a51a2 --- /dev/null +++ b/tests/api_resources/devboxes/test_browsers.py @@ -0,0 +1,72 @@ +# 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.devboxes import BrowserView + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestBrowsers: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: Runloop) -> None: + browser = client.devboxes.browsers.create() + assert_matches_type(BrowserView, browser, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: Runloop) -> None: + response = client.devboxes.browsers.with_raw_response.create() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + browser = response.parse() + assert_matches_type(BrowserView, browser, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: Runloop) -> None: + with client.devboxes.browsers.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + browser = response.parse() + assert_matches_type(BrowserView, browser, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncBrowsers: + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + async def test_method_create(self, async_client: AsyncRunloop) -> None: + browser = await async_client.devboxes.browsers.create() + assert_matches_type(BrowserView, browser, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncRunloop) -> None: + response = await async_client.devboxes.browsers.with_raw_response.create() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + browser = await response.parse() + assert_matches_type(BrowserView, browser, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncRunloop) -> None: + async with async_client.devboxes.browsers.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + browser = await response.parse() + assert_matches_type(BrowserView, browser, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/devboxes/test_computers.py b/tests/api_resources/devboxes/test_computers.py new file mode 100644 index 00000000..09b08358 --- /dev/null +++ b/tests/api_resources/devboxes/test_computers.py @@ -0,0 +1,391 @@ +# 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.devboxes import ( + ComputerView, + ComputerMouseInteractionResponse, + ComputerScreenInteractionResponse, + ComputerKeyboardInteractionResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestComputers: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: Runloop) -> None: + computer = client.devboxes.computers.create() + assert_matches_type(ComputerView, computer, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: Runloop) -> None: + computer = client.devboxes.computers.create( + display_dimensions={ + "display_height_px": 0, + "display_width_px": 0, + }, + ) + assert_matches_type(ComputerView, computer, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: Runloop) -> None: + response = client.devboxes.computers.with_raw_response.create() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + computer = response.parse() + assert_matches_type(ComputerView, computer, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: Runloop) -> None: + with client.devboxes.computers.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + computer = response.parse() + assert_matches_type(ComputerView, computer, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_keyboard_interaction(self, client: Runloop) -> None: + computer = client.devboxes.computers.keyboard_interaction( + id="id", + action="key", + ) + assert_matches_type(ComputerKeyboardInteractionResponse, computer, path=["response"]) + + @parametrize + def test_method_keyboard_interaction_with_all_params(self, client: Runloop) -> None: + computer = client.devboxes.computers.keyboard_interaction( + id="id", + action="key", + text="text", + ) + assert_matches_type(ComputerKeyboardInteractionResponse, computer, path=["response"]) + + @parametrize + def test_raw_response_keyboard_interaction(self, client: Runloop) -> None: + response = client.devboxes.computers.with_raw_response.keyboard_interaction( + id="id", + action="key", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + computer = response.parse() + assert_matches_type(ComputerKeyboardInteractionResponse, computer, path=["response"]) + + @parametrize + def test_streaming_response_keyboard_interaction(self, client: Runloop) -> None: + with client.devboxes.computers.with_streaming_response.keyboard_interaction( + id="id", + action="key", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + computer = response.parse() + assert_matches_type(ComputerKeyboardInteractionResponse, computer, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_keyboard_interaction(self, client: Runloop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.devboxes.computers.with_raw_response.keyboard_interaction( + id="", + action="key", + ) + + @parametrize + def test_method_mouse_interaction(self, client: Runloop) -> None: + computer = client.devboxes.computers.mouse_interaction( + id="id", + action="mouse_move", + ) + assert_matches_type(ComputerMouseInteractionResponse, computer, path=["response"]) + + @parametrize + def test_method_mouse_interaction_with_all_params(self, client: Runloop) -> None: + computer = client.devboxes.computers.mouse_interaction( + id="id", + action="mouse_move", + coordinate={ + "x": 0, + "y": 0, + }, + ) + assert_matches_type(ComputerMouseInteractionResponse, computer, path=["response"]) + + @parametrize + def test_raw_response_mouse_interaction(self, client: Runloop) -> None: + response = client.devboxes.computers.with_raw_response.mouse_interaction( + id="id", + action="mouse_move", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + computer = response.parse() + assert_matches_type(ComputerMouseInteractionResponse, computer, path=["response"]) + + @parametrize + def test_streaming_response_mouse_interaction(self, client: Runloop) -> None: + with client.devboxes.computers.with_streaming_response.mouse_interaction( + id="id", + action="mouse_move", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + computer = response.parse() + assert_matches_type(ComputerMouseInteractionResponse, computer, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_mouse_interaction(self, client: Runloop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.devboxes.computers.with_raw_response.mouse_interaction( + id="", + action="mouse_move", + ) + + @parametrize + def test_method_screen_interaction(self, client: Runloop) -> None: + computer = client.devboxes.computers.screen_interaction( + id="id", + action="screenshot", + ) + assert_matches_type(ComputerScreenInteractionResponse, computer, path=["response"]) + + @parametrize + def test_raw_response_screen_interaction(self, client: Runloop) -> None: + response = client.devboxes.computers.with_raw_response.screen_interaction( + id="id", + action="screenshot", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + computer = response.parse() + assert_matches_type(ComputerScreenInteractionResponse, computer, path=["response"]) + + @parametrize + def test_streaming_response_screen_interaction(self, client: Runloop) -> None: + with client.devboxes.computers.with_streaming_response.screen_interaction( + id="id", + action="screenshot", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + computer = response.parse() + assert_matches_type(ComputerScreenInteractionResponse, computer, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_screen_interaction(self, client: Runloop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.devboxes.computers.with_raw_response.screen_interaction( + id="", + action="screenshot", + ) + + +class TestAsyncComputers: + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + async def test_method_create(self, async_client: AsyncRunloop) -> None: + computer = await async_client.devboxes.computers.create() + assert_matches_type(ComputerView, computer, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncRunloop) -> None: + computer = await async_client.devboxes.computers.create( + display_dimensions={ + "display_height_px": 0, + "display_width_px": 0, + }, + ) + assert_matches_type(ComputerView, computer, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncRunloop) -> None: + response = await async_client.devboxes.computers.with_raw_response.create() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + computer = await response.parse() + assert_matches_type(ComputerView, computer, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncRunloop) -> None: + async with async_client.devboxes.computers.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + computer = await response.parse() + assert_matches_type(ComputerView, computer, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_keyboard_interaction(self, async_client: AsyncRunloop) -> None: + computer = await async_client.devboxes.computers.keyboard_interaction( + id="id", + action="key", + ) + assert_matches_type(ComputerKeyboardInteractionResponse, computer, path=["response"]) + + @parametrize + async def test_method_keyboard_interaction_with_all_params(self, async_client: AsyncRunloop) -> None: + computer = await async_client.devboxes.computers.keyboard_interaction( + id="id", + action="key", + text="text", + ) + assert_matches_type(ComputerKeyboardInteractionResponse, computer, path=["response"]) + + @parametrize + async def test_raw_response_keyboard_interaction(self, async_client: AsyncRunloop) -> None: + response = await async_client.devboxes.computers.with_raw_response.keyboard_interaction( + id="id", + action="key", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + computer = await response.parse() + assert_matches_type(ComputerKeyboardInteractionResponse, computer, path=["response"]) + + @parametrize + async def test_streaming_response_keyboard_interaction(self, async_client: AsyncRunloop) -> None: + async with async_client.devboxes.computers.with_streaming_response.keyboard_interaction( + id="id", + action="key", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + computer = await response.parse() + assert_matches_type(ComputerKeyboardInteractionResponse, computer, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_keyboard_interaction(self, async_client: AsyncRunloop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.devboxes.computers.with_raw_response.keyboard_interaction( + id="", + action="key", + ) + + @parametrize + async def test_method_mouse_interaction(self, async_client: AsyncRunloop) -> None: + computer = await async_client.devboxes.computers.mouse_interaction( + id="id", + action="mouse_move", + ) + assert_matches_type(ComputerMouseInteractionResponse, computer, path=["response"]) + + @parametrize + async def test_method_mouse_interaction_with_all_params(self, async_client: AsyncRunloop) -> None: + computer = await async_client.devboxes.computers.mouse_interaction( + id="id", + action="mouse_move", + coordinate={ + "x": 0, + "y": 0, + }, + ) + assert_matches_type(ComputerMouseInteractionResponse, computer, path=["response"]) + + @parametrize + async def test_raw_response_mouse_interaction(self, async_client: AsyncRunloop) -> None: + response = await async_client.devboxes.computers.with_raw_response.mouse_interaction( + id="id", + action="mouse_move", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + computer = await response.parse() + assert_matches_type(ComputerMouseInteractionResponse, computer, path=["response"]) + + @parametrize + async def test_streaming_response_mouse_interaction(self, async_client: AsyncRunloop) -> None: + async with async_client.devboxes.computers.with_streaming_response.mouse_interaction( + id="id", + action="mouse_move", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + computer = await response.parse() + assert_matches_type(ComputerMouseInteractionResponse, computer, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_mouse_interaction(self, async_client: AsyncRunloop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.devboxes.computers.with_raw_response.mouse_interaction( + id="", + action="mouse_move", + ) + + @parametrize + async def test_method_screen_interaction(self, async_client: AsyncRunloop) -> None: + computer = await async_client.devboxes.computers.screen_interaction( + id="id", + action="screenshot", + ) + assert_matches_type(ComputerScreenInteractionResponse, computer, path=["response"]) + + @parametrize + async def test_raw_response_screen_interaction(self, async_client: AsyncRunloop) -> None: + response = await async_client.devboxes.computers.with_raw_response.screen_interaction( + id="id", + action="screenshot", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + computer = await response.parse() + assert_matches_type(ComputerScreenInteractionResponse, computer, path=["response"]) + + @parametrize + async def test_streaming_response_screen_interaction(self, async_client: AsyncRunloop) -> None: + async with async_client.devboxes.computers.with_streaming_response.screen_interaction( + id="id", + action="screenshot", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + computer = await response.parse() + assert_matches_type(ComputerScreenInteractionResponse, computer, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_screen_interaction(self, async_client: AsyncRunloop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.devboxes.computers.with_raw_response.screen_interaction( + id="", + action="screenshot", + ) diff --git a/tests/api_resources/scenarios/test_runs.py b/tests/api_resources/scenarios/test_runs.py index 0fa9873e..8a83be2c 100644 --- a/tests/api_resources/scenarios/test_runs.py +++ b/tests/api_resources/scenarios/test_runs.py @@ -64,6 +64,7 @@ def test_method_list(self, client: Runloop) -> None: def test_method_list_with_all_params(self, client: Runloop) -> None: run = client.scenarios.runs.list( limit=0, + scenario_id=True, starting_after="starting_after", ) assert_matches_type(ScenarioRunListView, run, path=["response"]) @@ -215,6 +216,7 @@ async def test_method_list(self, async_client: AsyncRunloop) -> None: async def test_method_list_with_all_params(self, async_client: AsyncRunloop) -> None: run = await async_client.scenarios.runs.list( limit=0, + scenario_id=True, starting_after="starting_after", ) assert_matches_type(ScenarioRunListView, run, path=["response"]) diff --git a/tests/api_resources/test_benchmarks.py b/tests/api_resources/test_benchmarks.py index 0e1735e0..b3ac18fc 100644 --- a/tests/api_resources/test_benchmarks.py +++ b/tests/api_resources/test_benchmarks.py @@ -107,6 +107,7 @@ def test_method_list(self, client: Runloop) -> None: def test_method_list_with_all_params(self, client: Runloop) -> None: benchmark = client.benchmarks.list( limit=0, + public=True, starting_after="starting_after", ) assert_matches_type(BenchmarkListView, benchmark, path=["response"]) @@ -260,6 +261,7 @@ async def test_method_list(self, async_client: AsyncRunloop) -> None: async def test_method_list_with_all_params(self, async_client: AsyncRunloop) -> None: benchmark = await async_client.benchmarks.list( limit=0, + public=True, starting_after="starting_after", ) assert_matches_type(BenchmarkListView, benchmark, path=["response"])