From fff64d001d2c759967f08e7a1932e1fb7d84b126 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 22 Aug 2025 05:46:38 +0000 Subject: [PATCH 1/6] chore: update github action --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1692944e..fd673562 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: run: ./scripts/lint build: - if: github.repository == 'stainless-sdks/kernel-python' && (github.event_name == 'push' || github.event.pull_request.head.repo.fork) + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork timeout-minutes: 10 name: build permissions: @@ -61,12 +61,14 @@ jobs: run: rye build - name: Get GitHub OIDC Token + if: github.repository == 'stainless-sdks/kernel-python' id: github-oidc uses: actions/github-script@v6 with: script: core.setOutput('github_token', await core.getIDToken()); - name: Upload tarball + if: github.repository == 'stainless-sdks/kernel-python' env: URL: https://pkg.stainless.com/s AUTH: ${{ steps.github-oidc.outputs.github_token }} From 3a2969d035b9e0bb4fa39dc27de2db6d4edad6dd Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 04:30:02 +0000 Subject: [PATCH 2/6] chore(internal): change ci workflow machines --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fd673562..795bdad6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,7 @@ jobs: permissions: contents: read id-token: write - runs-on: depot-ubuntu-24.04 + runs-on: ${{ github.repository == 'stainless-sdks/kernel-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} steps: - uses: actions/checkout@v4 From 9ec7c40b34b264709b904f36e309624bd1161413 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 27 Aug 2025 06:13:12 +0000 Subject: [PATCH 3/6] fix: avoid newer type syntax --- src/kernel/_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kernel/_models.py b/src/kernel/_models.py index b8387ce9..92f7c10b 100644 --- a/src/kernel/_models.py +++ b/src/kernel/_models.py @@ -304,7 +304,7 @@ def model_dump( exclude_none=exclude_none, ) - return cast(dict[str, Any], json_safe(dumped)) if mode == "json" else dumped + return cast("dict[str, Any]", json_safe(dumped)) if mode == "json" else dumped @override def model_dump_json( From 39439aaad72c92aa9f4bb74ac055b929c93b6060 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 27 Aug 2025 06:19:28 +0000 Subject: [PATCH 4/6] chore(internal): update pyright exclude list --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index ddb4fbb1..584c3677 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -148,6 +148,7 @@ exclude = [ "_dev", ".venv", ".nox", + ".git", ] reportImplicitOverride = true From 48a39b4cc1ab32b4375ad4e33e5f9e4349502072 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 27 Aug 2025 17:38:43 +0000 Subject: [PATCH 5/6] feat(api): new process, fs, and log endpoints New endpoints for executing processes on browser instances, uploading / downloading whole directories as zip files, and streaming any log file on a browser instance --- .stats.yml | 8 +- api.md | 33 + src/kernel/resources/browsers/__init__.py | 28 + src/kernel/resources/browsers/browsers.py | 64 ++ src/kernel/resources/browsers/fs/fs.py | 319 +++++++- src/kernel/resources/browsers/logs.py | 214 +++++ src/kernel/resources/browsers/process.py | 742 ++++++++++++++++++ src/kernel/types/browsers/__init__.py | 14 + .../browsers/f_download_dir_zip_params.py | 12 + src/kernel/types/browsers/f_upload_params.py | 21 + .../types/browsers/f_upload_zip_params.py | 16 + .../types/browsers/log_stream_params.py | 19 + .../types/browsers/process_exec_params.py | 31 + .../types/browsers/process_exec_response.py | 21 + .../types/browsers/process_kill_params.py | 14 + .../types/browsers/process_kill_response.py | 10 + .../types/browsers/process_spawn_params.py | 31 + .../types/browsers/process_spawn_response.py | 19 + .../types/browsers/process_status_response.py | 22 + .../types/browsers/process_stdin_params.py | 14 + .../types/browsers/process_stdin_response.py | 12 + .../process_stdout_stream_response.py | 22 + tests/api_resources/browsers/test_fs.py | 340 ++++++++ tests/api_resources/browsers/test_logs.py | 136 ++++ tests/api_resources/browsers/test_process.py | 708 +++++++++++++++++ 25 files changed, 2864 insertions(+), 6 deletions(-) create mode 100644 src/kernel/resources/browsers/logs.py create mode 100644 src/kernel/resources/browsers/process.py create mode 100644 src/kernel/types/browsers/f_download_dir_zip_params.py create mode 100644 src/kernel/types/browsers/f_upload_params.py create mode 100644 src/kernel/types/browsers/f_upload_zip_params.py create mode 100644 src/kernel/types/browsers/log_stream_params.py create mode 100644 src/kernel/types/browsers/process_exec_params.py create mode 100644 src/kernel/types/browsers/process_exec_response.py create mode 100644 src/kernel/types/browsers/process_kill_params.py create mode 100644 src/kernel/types/browsers/process_kill_response.py create mode 100644 src/kernel/types/browsers/process_spawn_params.py create mode 100644 src/kernel/types/browsers/process_spawn_response.py create mode 100644 src/kernel/types/browsers/process_status_response.py create mode 100644 src/kernel/types/browsers/process_stdin_params.py create mode 100644 src/kernel/types/browsers/process_stdin_response.py create mode 100644 src/kernel/types/browsers/process_stdout_stream_response.py create mode 100644 tests/api_resources/browsers/test_logs.py create mode 100644 tests/api_resources/browsers/test_process.py diff --git a/.stats.yml b/.stats.yml index f6de080e..b791078d 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 31 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-b55c3e0424fa7733487139488b9fff00ad8949ff02ee3160ee36b9334e84b134.yml -openapi_spec_hash: 17f36677e3dc0a3aeb419654c8d5cae3 -config_hash: f67e4b33b2fb30c1405ee2fff8096320 +configured_endpoints: 41 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-a7c1df5070fe59642d7a1f168aa902a468227752bfc930cbf38930f7c205dbb6.yml +openapi_spec_hash: eab65e39aef4f0a0952b82adeecf6b5b +config_hash: 5de78bc29ac060562575cb54bb26826c diff --git a/api.md b/api.md index f03855dc..7e07a7b0 100644 --- a/api.md +++ b/api.md @@ -108,11 +108,14 @@ Methods: - client.browsers.fs.create_directory(id, \*\*params) -> None - client.browsers.fs.delete_directory(id, \*\*params) -> None - client.browsers.fs.delete_file(id, \*\*params) -> None +- client.browsers.fs.download_dir_zip(id, \*\*params) -> BinaryAPIResponse - client.browsers.fs.file_info(id, \*\*params) -> FFileInfoResponse - client.browsers.fs.list_files(id, \*\*params) -> FListFilesResponse - client.browsers.fs.move(id, \*\*params) -> None - client.browsers.fs.read_file(id, \*\*params) -> BinaryAPIResponse - client.browsers.fs.set_file_permissions(id, \*\*params) -> None +- client.browsers.fs.upload(id, \*\*params) -> None +- client.browsers.fs.upload_zip(id, \*\*params) -> None - client.browsers.fs.write_file(id, contents, \*\*params) -> None ### Watch @@ -128,3 +131,33 @@ Methods: - client.browsers.fs.watch.events(watch_id, \*, id) -> WatchEventsResponse - client.browsers.fs.watch.start(id, \*\*params) -> WatchStartResponse - client.browsers.fs.watch.stop(watch_id, \*, id) -> None + +## Process + +Types: + +```python +from kernel.types.browsers import ( + ProcessExecResponse, + ProcessKillResponse, + ProcessSpawnResponse, + ProcessStatusResponse, + ProcessStdinResponse, + ProcessStdoutStreamResponse, +) +``` + +Methods: + +- client.browsers.process.exec(id, \*\*params) -> ProcessExecResponse +- client.browsers.process.kill(process_id, \*, id, \*\*params) -> ProcessKillResponse +- client.browsers.process.spawn(id, \*\*params) -> ProcessSpawnResponse +- client.browsers.process.status(process_id, \*, id) -> ProcessStatusResponse +- client.browsers.process.stdin(process_id, \*, id, \*\*params) -> ProcessStdinResponse +- client.browsers.process.stdout_stream(process_id, \*, id) -> ProcessStdoutStreamResponse + +## Logs + +Methods: + +- client.browsers.logs.stream(id, \*\*params) -> LogEvent diff --git a/src/kernel/resources/browsers/__init__.py b/src/kernel/resources/browsers/__init__.py index 41452e9d..97c987e4 100644 --- a/src/kernel/resources/browsers/__init__.py +++ b/src/kernel/resources/browsers/__init__.py @@ -8,6 +8,22 @@ FsResourceWithStreamingResponse, AsyncFsResourceWithStreamingResponse, ) +from .logs import ( + LogsResource, + AsyncLogsResource, + LogsResourceWithRawResponse, + AsyncLogsResourceWithRawResponse, + LogsResourceWithStreamingResponse, + AsyncLogsResourceWithStreamingResponse, +) +from .process import ( + ProcessResource, + AsyncProcessResource, + ProcessResourceWithRawResponse, + AsyncProcessResourceWithRawResponse, + ProcessResourceWithStreamingResponse, + AsyncProcessResourceWithStreamingResponse, +) from .replays import ( ReplaysResource, AsyncReplaysResource, @@ -38,6 +54,18 @@ "AsyncFsResourceWithRawResponse", "FsResourceWithStreamingResponse", "AsyncFsResourceWithStreamingResponse", + "ProcessResource", + "AsyncProcessResource", + "ProcessResourceWithRawResponse", + "AsyncProcessResourceWithRawResponse", + "ProcessResourceWithStreamingResponse", + "AsyncProcessResourceWithStreamingResponse", + "LogsResource", + "AsyncLogsResource", + "LogsResourceWithRawResponse", + "AsyncLogsResourceWithRawResponse", + "LogsResourceWithStreamingResponse", + "AsyncLogsResourceWithStreamingResponse", "BrowsersResource", "AsyncBrowsersResource", "BrowsersResourceWithRawResponse", diff --git a/src/kernel/resources/browsers/browsers.py b/src/kernel/resources/browsers/browsers.py index 559e0948..80afc60d 100644 --- a/src/kernel/resources/browsers/browsers.py +++ b/src/kernel/resources/browsers/browsers.py @@ -4,6 +4,14 @@ import httpx +from .logs import ( + LogsResource, + AsyncLogsResource, + LogsResourceWithRawResponse, + AsyncLogsResourceWithRawResponse, + LogsResourceWithStreamingResponse, + AsyncLogsResourceWithStreamingResponse, +) from .fs.fs import ( FsResource, AsyncFsResource, @@ -13,6 +21,14 @@ AsyncFsResourceWithStreamingResponse, ) from ...types import browser_create_params, browser_delete_params +from .process import ( + ProcessResource, + AsyncProcessResource, + ProcessResourceWithRawResponse, + AsyncProcessResourceWithRawResponse, + ProcessResourceWithStreamingResponse, + AsyncProcessResourceWithStreamingResponse, +) from .replays import ( ReplaysResource, AsyncReplaysResource, @@ -49,6 +65,14 @@ def replays(self) -> ReplaysResource: def fs(self) -> FsResource: return FsResource(self._client) + @cached_property + def process(self) -> ProcessResource: + return ProcessResource(self._client) + + @cached_property + def logs(self) -> LogsResource: + return LogsResource(self._client) + @cached_property def with_raw_response(self) -> BrowsersResourceWithRawResponse: """ @@ -261,6 +285,14 @@ def replays(self) -> AsyncReplaysResource: def fs(self) -> AsyncFsResource: return AsyncFsResource(self._client) + @cached_property + def process(self) -> AsyncProcessResource: + return AsyncProcessResource(self._client) + + @cached_property + def logs(self) -> AsyncLogsResource: + return AsyncLogsResource(self._client) + @cached_property def with_raw_response(self) -> AsyncBrowsersResourceWithRawResponse: """ @@ -494,6 +526,14 @@ def replays(self) -> ReplaysResourceWithRawResponse: def fs(self) -> FsResourceWithRawResponse: return FsResourceWithRawResponse(self._browsers.fs) + @cached_property + def process(self) -> ProcessResourceWithRawResponse: + return ProcessResourceWithRawResponse(self._browsers.process) + + @cached_property + def logs(self) -> LogsResourceWithRawResponse: + return LogsResourceWithRawResponse(self._browsers.logs) + class AsyncBrowsersResourceWithRawResponse: def __init__(self, browsers: AsyncBrowsersResource) -> None: @@ -523,6 +563,14 @@ def replays(self) -> AsyncReplaysResourceWithRawResponse: def fs(self) -> AsyncFsResourceWithRawResponse: return AsyncFsResourceWithRawResponse(self._browsers.fs) + @cached_property + def process(self) -> AsyncProcessResourceWithRawResponse: + return AsyncProcessResourceWithRawResponse(self._browsers.process) + + @cached_property + def logs(self) -> AsyncLogsResourceWithRawResponse: + return AsyncLogsResourceWithRawResponse(self._browsers.logs) + class BrowsersResourceWithStreamingResponse: def __init__(self, browsers: BrowsersResource) -> None: @@ -552,6 +600,14 @@ def replays(self) -> ReplaysResourceWithStreamingResponse: def fs(self) -> FsResourceWithStreamingResponse: return FsResourceWithStreamingResponse(self._browsers.fs) + @cached_property + def process(self) -> ProcessResourceWithStreamingResponse: + return ProcessResourceWithStreamingResponse(self._browsers.process) + + @cached_property + def logs(self) -> LogsResourceWithStreamingResponse: + return LogsResourceWithStreamingResponse(self._browsers.logs) + class AsyncBrowsersResourceWithStreamingResponse: def __init__(self, browsers: AsyncBrowsersResource) -> None: @@ -580,3 +636,11 @@ def replays(self) -> AsyncReplaysResourceWithStreamingResponse: @cached_property def fs(self) -> AsyncFsResourceWithStreamingResponse: return AsyncFsResourceWithStreamingResponse(self._browsers.fs) + + @cached_property + def process(self) -> AsyncProcessResourceWithStreamingResponse: + return AsyncProcessResourceWithStreamingResponse(self._browsers.process) + + @cached_property + def logs(self) -> AsyncLogsResourceWithStreamingResponse: + return AsyncLogsResourceWithStreamingResponse(self._browsers.logs) diff --git a/src/kernel/resources/browsers/fs/fs.py b/src/kernel/resources/browsers/fs/fs.py index 3563c7cb..cb7b3010 100644 --- a/src/kernel/resources/browsers/fs/fs.py +++ b/src/kernel/resources/browsers/fs/fs.py @@ -2,6 +2,8 @@ from __future__ import annotations +from typing import Mapping, Iterable, cast + import httpx from .watch import ( @@ -13,8 +15,8 @@ AsyncWatchResourceWithStreamingResponse, ) from ...._files import read_file_content, async_read_file_content -from ...._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven, FileContent -from ...._utils import maybe_transform, async_maybe_transform +from ...._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven, FileTypes, FileContent +from ...._utils import extract_files, maybe_transform, deepcopy_minimal, async_maybe_transform from ...._compat import cached_property from ...._resource import SyncAPIResource, AsyncAPIResource from ...._response import ( @@ -34,13 +36,16 @@ from ...._base_client import make_request_options from ....types.browsers import ( f_move_params, + f_upload_params, f_file_info_params, f_read_file_params, f_list_files_params, + f_upload_zip_params, f_write_file_params, f_delete_file_params, f_create_directory_params, f_delete_directory_params, + f_download_dir_zip_params, f_set_file_permissions_params, ) from ....types.browsers.f_file_info_response import FFileInfoResponse @@ -196,6 +201,47 @@ def delete_file( cast_to=NoneType, ) + def download_dir_zip( + self, + id: str, + *, + path: 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, + ) -> BinaryAPIResponse: + """ + Returns a ZIP file containing the contents of the specified directory. + + Args: + path: Absolute directory path to archive and download. + + 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}") + extra_headers = {"Accept": "application/zip", **(extra_headers or {})} + return self._get( + f"/browsers/{id}/fs/download_dir_zip", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"path": path}, f_download_dir_zip_params.FDownloadDirZipParams), + ), + cast_to=BinaryAPIResponse, + ) + def file_info( self, id: str, @@ -419,6 +465,100 @@ def set_file_permissions( cast_to=NoneType, ) + def upload( + self, + id: str, + *, + files: Iterable[f_upload_params.File], + # 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, + ) -> None: + """ + Allows uploading single or multiple files to the remote filesystem. + + 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}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + body = deepcopy_minimal({"files": files}) + extracted_files = extract_files(cast(Mapping[str, object], body), paths=[["files", "", "file"]]) + # It should be noted that the actual Content-Type header that will be + # sent to the server will contain a `boundary` parameter, e.g. + # multipart/form-data; boundary=---abc-- + extra_headers["Content-Type"] = "multipart/form-data" + return self._post( + f"/browsers/{id}/fs/upload", + body=maybe_transform(body, f_upload_params.FUploadParams), + files=extracted_files, + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + def upload_zip( + self, + id: str, + *, + dest_path: str, + zip_file: FileTypes, + # 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, + ) -> None: + """ + Upload a zip file and extract its contents to the specified destination path. + + Args: + dest_path: Absolute destination directory to extract the archive to. + + 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}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + body = deepcopy_minimal( + { + "dest_path": dest_path, + "zip_file": zip_file, + } + ) + files = extract_files(cast(Mapping[str, object], body), paths=[["zip_file"]]) + # It should be noted that the actual Content-Type header that will be + # sent to the server will contain a `boundary` parameter, e.g. + # multipart/form-data; boundary=---abc-- + extra_headers["Content-Type"] = "multipart/form-data" + return self._post( + f"/browsers/{id}/fs/upload_zip", + body=maybe_transform(body, f_upload_zip_params.FUploadZipParams), + files=files, + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + def write_file( self, id: str, @@ -620,6 +760,47 @@ async def delete_file( cast_to=NoneType, ) + async def download_dir_zip( + self, + id: str, + *, + path: 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, + ) -> AsyncBinaryAPIResponse: + """ + Returns a ZIP file containing the contents of the specified directory. + + Args: + path: Absolute directory path to archive and download. + + 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}") + extra_headers = {"Accept": "application/zip", **(extra_headers or {})} + return await self._get( + f"/browsers/{id}/fs/download_dir_zip", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform({"path": path}, f_download_dir_zip_params.FDownloadDirZipParams), + ), + cast_to=AsyncBinaryAPIResponse, + ) + async def file_info( self, id: str, @@ -843,6 +1024,100 @@ async def set_file_permissions( cast_to=NoneType, ) + async def upload( + self, + id: str, + *, + files: Iterable[f_upload_params.File], + # 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, + ) -> None: + """ + Allows uploading single or multiple files to the remote filesystem. + + 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}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + body = deepcopy_minimal({"files": files}) + extracted_files = extract_files(cast(Mapping[str, object], body), paths=[["files", "", "file"]]) + # It should be noted that the actual Content-Type header that will be + # sent to the server will contain a `boundary` parameter, e.g. + # multipart/form-data; boundary=---abc-- + extra_headers["Content-Type"] = "multipart/form-data" + return await self._post( + f"/browsers/{id}/fs/upload", + body=await async_maybe_transform(body, f_upload_params.FUploadParams), + files=extracted_files, + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + async def upload_zip( + self, + id: str, + *, + dest_path: str, + zip_file: FileTypes, + # 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, + ) -> None: + """ + Upload a zip file and extract its contents to the specified destination path. + + Args: + dest_path: Absolute destination directory to extract the archive to. + + 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}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + body = deepcopy_minimal( + { + "dest_path": dest_path, + "zip_file": zip_file, + } + ) + files = extract_files(cast(Mapping[str, object], body), paths=[["zip_file"]]) + # It should be noted that the actual Content-Type header that will be + # sent to the server will contain a `boundary` parameter, e.g. + # multipart/form-data; boundary=---abc-- + extra_headers["Content-Type"] = "multipart/form-data" + return await self._post( + f"/browsers/{id}/fs/upload_zip", + body=await async_maybe_transform(body, f_upload_zip_params.FUploadZipParams), + files=files, + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + async def write_file( self, id: str, @@ -910,6 +1185,10 @@ def __init__(self, fs: FsResource) -> None: self.delete_file = to_raw_response_wrapper( fs.delete_file, ) + self.download_dir_zip = to_custom_raw_response_wrapper( + fs.download_dir_zip, + BinaryAPIResponse, + ) self.file_info = to_raw_response_wrapper( fs.file_info, ) @@ -926,6 +1205,12 @@ def __init__(self, fs: FsResource) -> None: self.set_file_permissions = to_raw_response_wrapper( fs.set_file_permissions, ) + self.upload = to_raw_response_wrapper( + fs.upload, + ) + self.upload_zip = to_raw_response_wrapper( + fs.upload_zip, + ) self.write_file = to_raw_response_wrapper( fs.write_file, ) @@ -948,6 +1233,10 @@ def __init__(self, fs: AsyncFsResource) -> None: self.delete_file = async_to_raw_response_wrapper( fs.delete_file, ) + self.download_dir_zip = async_to_custom_raw_response_wrapper( + fs.download_dir_zip, + AsyncBinaryAPIResponse, + ) self.file_info = async_to_raw_response_wrapper( fs.file_info, ) @@ -964,6 +1253,12 @@ def __init__(self, fs: AsyncFsResource) -> None: self.set_file_permissions = async_to_raw_response_wrapper( fs.set_file_permissions, ) + self.upload = async_to_raw_response_wrapper( + fs.upload, + ) + self.upload_zip = async_to_raw_response_wrapper( + fs.upload_zip, + ) self.write_file = async_to_raw_response_wrapper( fs.write_file, ) @@ -986,6 +1281,10 @@ def __init__(self, fs: FsResource) -> None: self.delete_file = to_streamed_response_wrapper( fs.delete_file, ) + self.download_dir_zip = to_custom_streamed_response_wrapper( + fs.download_dir_zip, + StreamedBinaryAPIResponse, + ) self.file_info = to_streamed_response_wrapper( fs.file_info, ) @@ -1002,6 +1301,12 @@ def __init__(self, fs: FsResource) -> None: self.set_file_permissions = to_streamed_response_wrapper( fs.set_file_permissions, ) + self.upload = to_streamed_response_wrapper( + fs.upload, + ) + self.upload_zip = to_streamed_response_wrapper( + fs.upload_zip, + ) self.write_file = to_streamed_response_wrapper( fs.write_file, ) @@ -1024,6 +1329,10 @@ def __init__(self, fs: AsyncFsResource) -> None: self.delete_file = async_to_streamed_response_wrapper( fs.delete_file, ) + self.download_dir_zip = async_to_custom_streamed_response_wrapper( + fs.download_dir_zip, + AsyncStreamedBinaryAPIResponse, + ) self.file_info = async_to_streamed_response_wrapper( fs.file_info, ) @@ -1040,6 +1349,12 @@ def __init__(self, fs: AsyncFsResource) -> None: self.set_file_permissions = async_to_streamed_response_wrapper( fs.set_file_permissions, ) + self.upload = async_to_streamed_response_wrapper( + fs.upload, + ) + self.upload_zip = async_to_streamed_response_wrapper( + fs.upload_zip, + ) self.write_file = async_to_streamed_response_wrapper( fs.write_file, ) diff --git a/src/kernel/resources/browsers/logs.py b/src/kernel/resources/browsers/logs.py new file mode 100644 index 00000000..fbbe14a5 --- /dev/null +++ b/src/kernel/resources/browsers/logs.py @@ -0,0 +1,214 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +from ..._types import 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 ..._streaming import Stream, AsyncStream +from ..._base_client import make_request_options +from ...types.browsers import log_stream_params +from ...types.shared.log_event import LogEvent + +__all__ = ["LogsResource", "AsyncLogsResource"] + + +class LogsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> LogsResourceWithRawResponse: + """ + 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/onkernel/kernel-python-sdk#accessing-raw-response-data-eg-headers + """ + return LogsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> LogsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/onkernel/kernel-python-sdk#with_streaming_response + """ + return LogsResourceWithStreamingResponse(self) + + def stream( + self, + id: str, + *, + source: Literal["path", "supervisor"], + follow: bool | NotGiven = NOT_GIVEN, + path: str | NotGiven = NOT_GIVEN, + supervisor_process: 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, + ) -> Stream[LogEvent]: + """ + Stream log files on the browser instance via SSE + + Args: + path: only required if source is path + + supervisor_process: only required if source is supervisor + + 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}") + extra_headers = {"Accept": "text/event-stream", **(extra_headers or {})} + return self._get( + f"/browsers/{id}/logs/stream", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "source": source, + "follow": follow, + "path": path, + "supervisor_process": supervisor_process, + }, + log_stream_params.LogStreamParams, + ), + ), + cast_to=LogEvent, + stream=True, + stream_cls=Stream[LogEvent], + ) + + +class AsyncLogsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncLogsResourceWithRawResponse: + """ + 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/onkernel/kernel-python-sdk#accessing-raw-response-data-eg-headers + """ + return AsyncLogsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncLogsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/onkernel/kernel-python-sdk#with_streaming_response + """ + return AsyncLogsResourceWithStreamingResponse(self) + + async def stream( + self, + id: str, + *, + source: Literal["path", "supervisor"], + follow: bool | NotGiven = NOT_GIVEN, + path: str | NotGiven = NOT_GIVEN, + supervisor_process: 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, + ) -> AsyncStream[LogEvent]: + """ + Stream log files on the browser instance via SSE + + Args: + path: only required if source is path + + supervisor_process: only required if source is supervisor + + 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}") + extra_headers = {"Accept": "text/event-stream", **(extra_headers or {})} + return await self._get( + f"/browsers/{id}/logs/stream", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "source": source, + "follow": follow, + "path": path, + "supervisor_process": supervisor_process, + }, + log_stream_params.LogStreamParams, + ), + ), + cast_to=LogEvent, + stream=True, + stream_cls=AsyncStream[LogEvent], + ) + + +class LogsResourceWithRawResponse: + def __init__(self, logs: LogsResource) -> None: + self._logs = logs + + self.stream = to_raw_response_wrapper( + logs.stream, + ) + + +class AsyncLogsResourceWithRawResponse: + def __init__(self, logs: AsyncLogsResource) -> None: + self._logs = logs + + self.stream = async_to_raw_response_wrapper( + logs.stream, + ) + + +class LogsResourceWithStreamingResponse: + def __init__(self, logs: LogsResource) -> None: + self._logs = logs + + self.stream = to_streamed_response_wrapper( + logs.stream, + ) + + +class AsyncLogsResourceWithStreamingResponse: + def __init__(self, logs: AsyncLogsResource) -> None: + self._logs = logs + + self.stream = async_to_streamed_response_wrapper( + logs.stream, + ) diff --git a/src/kernel/resources/browsers/process.py b/src/kernel/resources/browsers/process.py new file mode 100644 index 00000000..d3f5eca5 --- /dev/null +++ b/src/kernel/resources/browsers/process.py @@ -0,0 +1,742 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, List, 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 ..._streaming import Stream, AsyncStream +from ..._base_client import make_request_options +from ...types.browsers import process_exec_params, process_kill_params, process_spawn_params, process_stdin_params +from ...types.browsers.process_exec_response import ProcessExecResponse +from ...types.browsers.process_kill_response import ProcessKillResponse +from ...types.browsers.process_spawn_response import ProcessSpawnResponse +from ...types.browsers.process_stdin_response import ProcessStdinResponse +from ...types.browsers.process_status_response import ProcessStatusResponse +from ...types.browsers.process_stdout_stream_response import ProcessStdoutStreamResponse + +__all__ = ["ProcessResource", "AsyncProcessResource"] + + +class ProcessResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ProcessResourceWithRawResponse: + """ + 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/onkernel/kernel-python-sdk#accessing-raw-response-data-eg-headers + """ + return ProcessResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ProcessResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/onkernel/kernel-python-sdk#with_streaming_response + """ + return ProcessResourceWithStreamingResponse(self) + + def exec( + self, + id: str, + *, + command: str, + args: List[str] | NotGiven = NOT_GIVEN, + as_root: bool | NotGiven = NOT_GIVEN, + as_user: Optional[str] | NotGiven = NOT_GIVEN, + cwd: Optional[str] | NotGiven = NOT_GIVEN, + env: Dict[str, str] | NotGiven = NOT_GIVEN, + timeout_sec: Optional[int] | 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, + ) -> ProcessExecResponse: + """ + Execute a command synchronously + + Args: + command: Executable or shell command to run. + + args: Command arguments. + + as_root: Run the process with root privileges. + + as_user: Run the process as this user. + + cwd: Working directory (absolute path) to run the command in. + + env: Environment variables to set for the process. + + timeout_sec: Maximum execution time in seconds. + + 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._post( + f"/browsers/{id}/process/exec", + body=maybe_transform( + { + "command": command, + "args": args, + "as_root": as_root, + "as_user": as_user, + "cwd": cwd, + "env": env, + "timeout_sec": timeout_sec, + }, + process_exec_params.ProcessExecParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ProcessExecResponse, + ) + + def kill( + self, + process_id: str, + *, + id: str, + signal: Literal["TERM", "KILL", "INT", "HUP"], + # 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, + ) -> ProcessKillResponse: + """ + Send signal to process + + Args: + signal: Signal to send. + + 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}") + if not process_id: + raise ValueError(f"Expected a non-empty value for `process_id` but received {process_id!r}") + return self._post( + f"/browsers/{id}/process/{process_id}/kill", + body=maybe_transform({"signal": signal}, process_kill_params.ProcessKillParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ProcessKillResponse, + ) + + def spawn( + self, + id: str, + *, + command: str, + args: List[str] | NotGiven = NOT_GIVEN, + as_root: bool | NotGiven = NOT_GIVEN, + as_user: Optional[str] | NotGiven = NOT_GIVEN, + cwd: Optional[str] | NotGiven = NOT_GIVEN, + env: Dict[str, str] | NotGiven = NOT_GIVEN, + timeout_sec: Optional[int] | 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, + ) -> ProcessSpawnResponse: + """ + Execute a command asynchronously + + Args: + command: Executable or shell command to run. + + args: Command arguments. + + as_root: Run the process with root privileges. + + as_user: Run the process as this user. + + cwd: Working directory (absolute path) to run the command in. + + env: Environment variables to set for the process. + + timeout_sec: Maximum execution time in seconds. + + 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._post( + f"/browsers/{id}/process/spawn", + body=maybe_transform( + { + "command": command, + "args": args, + "as_root": as_root, + "as_user": as_user, + "cwd": cwd, + "env": env, + "timeout_sec": timeout_sec, + }, + process_spawn_params.ProcessSpawnParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ProcessSpawnResponse, + ) + + def status( + self, + process_id: str, + *, + 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, + ) -> ProcessStatusResponse: + """ + Get process status + + 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}") + if not process_id: + raise ValueError(f"Expected a non-empty value for `process_id` but received {process_id!r}") + return self._get( + f"/browsers/{id}/process/{process_id}/status", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ProcessStatusResponse, + ) + + def stdin( + self, + process_id: str, + *, + id: str, + data_b64: 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, + ) -> ProcessStdinResponse: + """ + Write to process stdin + + Args: + data_b64: Base64-encoded data to write. + + 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}") + if not process_id: + raise ValueError(f"Expected a non-empty value for `process_id` but received {process_id!r}") + return self._post( + f"/browsers/{id}/process/{process_id}/stdin", + body=maybe_transform({"data_b64": data_b64}, process_stdin_params.ProcessStdinParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ProcessStdinResponse, + ) + + def stdout_stream( + self, + process_id: str, + *, + id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> Stream[ProcessStdoutStreamResponse]: + """ + Stream process stdout via SSE + + 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}") + if not process_id: + raise ValueError(f"Expected a non-empty value for `process_id` but received {process_id!r}") + extra_headers = {"Accept": "text/event-stream", **(extra_headers or {})} + return self._get( + f"/browsers/{id}/process/{process_id}/stdout/stream", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ProcessStdoutStreamResponse, + stream=True, + stream_cls=Stream[ProcessStdoutStreamResponse], + ) + + +class AsyncProcessResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncProcessResourceWithRawResponse: + """ + 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/onkernel/kernel-python-sdk#accessing-raw-response-data-eg-headers + """ + return AsyncProcessResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncProcessResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/onkernel/kernel-python-sdk#with_streaming_response + """ + return AsyncProcessResourceWithStreamingResponse(self) + + async def exec( + self, + id: str, + *, + command: str, + args: List[str] | NotGiven = NOT_GIVEN, + as_root: bool | NotGiven = NOT_GIVEN, + as_user: Optional[str] | NotGiven = NOT_GIVEN, + cwd: Optional[str] | NotGiven = NOT_GIVEN, + env: Dict[str, str] | NotGiven = NOT_GIVEN, + timeout_sec: Optional[int] | 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, + ) -> ProcessExecResponse: + """ + Execute a command synchronously + + Args: + command: Executable or shell command to run. + + args: Command arguments. + + as_root: Run the process with root privileges. + + as_user: Run the process as this user. + + cwd: Working directory (absolute path) to run the command in. + + env: Environment variables to set for the process. + + timeout_sec: Maximum execution time in seconds. + + 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._post( + f"/browsers/{id}/process/exec", + body=await async_maybe_transform( + { + "command": command, + "args": args, + "as_root": as_root, + "as_user": as_user, + "cwd": cwd, + "env": env, + "timeout_sec": timeout_sec, + }, + process_exec_params.ProcessExecParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ProcessExecResponse, + ) + + async def kill( + self, + process_id: str, + *, + id: str, + signal: Literal["TERM", "KILL", "INT", "HUP"], + # 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, + ) -> ProcessKillResponse: + """ + Send signal to process + + Args: + signal: Signal to send. + + 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}") + if not process_id: + raise ValueError(f"Expected a non-empty value for `process_id` but received {process_id!r}") + return await self._post( + f"/browsers/{id}/process/{process_id}/kill", + body=await async_maybe_transform({"signal": signal}, process_kill_params.ProcessKillParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ProcessKillResponse, + ) + + async def spawn( + self, + id: str, + *, + command: str, + args: List[str] | NotGiven = NOT_GIVEN, + as_root: bool | NotGiven = NOT_GIVEN, + as_user: Optional[str] | NotGiven = NOT_GIVEN, + cwd: Optional[str] | NotGiven = NOT_GIVEN, + env: Dict[str, str] | NotGiven = NOT_GIVEN, + timeout_sec: Optional[int] | 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, + ) -> ProcessSpawnResponse: + """ + Execute a command asynchronously + + Args: + command: Executable or shell command to run. + + args: Command arguments. + + as_root: Run the process with root privileges. + + as_user: Run the process as this user. + + cwd: Working directory (absolute path) to run the command in. + + env: Environment variables to set for the process. + + timeout_sec: Maximum execution time in seconds. + + 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._post( + f"/browsers/{id}/process/spawn", + body=await async_maybe_transform( + { + "command": command, + "args": args, + "as_root": as_root, + "as_user": as_user, + "cwd": cwd, + "env": env, + "timeout_sec": timeout_sec, + }, + process_spawn_params.ProcessSpawnParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ProcessSpawnResponse, + ) + + async def status( + self, + process_id: str, + *, + 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, + ) -> ProcessStatusResponse: + """ + Get process status + + 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}") + if not process_id: + raise ValueError(f"Expected a non-empty value for `process_id` but received {process_id!r}") + return await self._get( + f"/browsers/{id}/process/{process_id}/status", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ProcessStatusResponse, + ) + + async def stdin( + self, + process_id: str, + *, + id: str, + data_b64: 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, + ) -> ProcessStdinResponse: + """ + Write to process stdin + + Args: + data_b64: Base64-encoded data to write. + + 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}") + if not process_id: + raise ValueError(f"Expected a non-empty value for `process_id` but received {process_id!r}") + return await self._post( + f"/browsers/{id}/process/{process_id}/stdin", + body=await async_maybe_transform({"data_b64": data_b64}, process_stdin_params.ProcessStdinParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ProcessStdinResponse, + ) + + async def stdout_stream( + self, + process_id: str, + *, + id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> AsyncStream[ProcessStdoutStreamResponse]: + """ + Stream process stdout via SSE + + 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}") + if not process_id: + raise ValueError(f"Expected a non-empty value for `process_id` but received {process_id!r}") + extra_headers = {"Accept": "text/event-stream", **(extra_headers or {})} + return await self._get( + f"/browsers/{id}/process/{process_id}/stdout/stream", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ProcessStdoutStreamResponse, + stream=True, + stream_cls=AsyncStream[ProcessStdoutStreamResponse], + ) + + +class ProcessResourceWithRawResponse: + def __init__(self, process: ProcessResource) -> None: + self._process = process + + self.exec = to_raw_response_wrapper( + process.exec, + ) + self.kill = to_raw_response_wrapper( + process.kill, + ) + self.spawn = to_raw_response_wrapper( + process.spawn, + ) + self.status = to_raw_response_wrapper( + process.status, + ) + self.stdin = to_raw_response_wrapper( + process.stdin, + ) + self.stdout_stream = to_raw_response_wrapper( + process.stdout_stream, + ) + + +class AsyncProcessResourceWithRawResponse: + def __init__(self, process: AsyncProcessResource) -> None: + self._process = process + + self.exec = async_to_raw_response_wrapper( + process.exec, + ) + self.kill = async_to_raw_response_wrapper( + process.kill, + ) + self.spawn = async_to_raw_response_wrapper( + process.spawn, + ) + self.status = async_to_raw_response_wrapper( + process.status, + ) + self.stdin = async_to_raw_response_wrapper( + process.stdin, + ) + self.stdout_stream = async_to_raw_response_wrapper( + process.stdout_stream, + ) + + +class ProcessResourceWithStreamingResponse: + def __init__(self, process: ProcessResource) -> None: + self._process = process + + self.exec = to_streamed_response_wrapper( + process.exec, + ) + self.kill = to_streamed_response_wrapper( + process.kill, + ) + self.spawn = to_streamed_response_wrapper( + process.spawn, + ) + self.status = to_streamed_response_wrapper( + process.status, + ) + self.stdin = to_streamed_response_wrapper( + process.stdin, + ) + self.stdout_stream = to_streamed_response_wrapper( + process.stdout_stream, + ) + + +class AsyncProcessResourceWithStreamingResponse: + def __init__(self, process: AsyncProcessResource) -> None: + self._process = process + + self.exec = async_to_streamed_response_wrapper( + process.exec, + ) + self.kill = async_to_streamed_response_wrapper( + process.kill, + ) + self.spawn = async_to_streamed_response_wrapper( + process.spawn, + ) + self.status = async_to_streamed_response_wrapper( + process.status, + ) + self.stdin = async_to_streamed_response_wrapper( + process.stdin, + ) + self.stdout_stream = async_to_streamed_response_wrapper( + process.stdout_stream, + ) diff --git a/src/kernel/types/browsers/__init__.py b/src/kernel/types/browsers/__init__.py index c4e80a89..d0b6b383 100644 --- a/src/kernel/types/browsers/__init__.py +++ b/src/kernel/types/browsers/__init__.py @@ -3,16 +3,30 @@ from __future__ import annotations from .f_move_params import FMoveParams as FMoveParams +from .f_upload_params import FUploadParams as FUploadParams +from .log_stream_params import LogStreamParams as LogStreamParams from .f_file_info_params import FFileInfoParams as FFileInfoParams from .f_read_file_params import FReadFileParams as FReadFileParams from .f_list_files_params import FListFilesParams as FListFilesParams +from .f_upload_zip_params import FUploadZipParams as FUploadZipParams from .f_write_file_params import FWriteFileParams as FWriteFileParams +from .process_exec_params import ProcessExecParams as ProcessExecParams +from .process_kill_params import ProcessKillParams as ProcessKillParams from .replay_start_params import ReplayStartParams as ReplayStartParams from .f_delete_file_params import FDeleteFileParams as FDeleteFileParams from .f_file_info_response import FFileInfoResponse as FFileInfoResponse +from .process_spawn_params import ProcessSpawnParams as ProcessSpawnParams +from .process_stdin_params import ProcessStdinParams as ProcessStdinParams from .replay_list_response import ReplayListResponse as ReplayListResponse from .f_list_files_response import FListFilesResponse as FListFilesResponse +from .process_exec_response import ProcessExecResponse as ProcessExecResponse +from .process_kill_response import ProcessKillResponse as ProcessKillResponse from .replay_start_response import ReplayStartResponse as ReplayStartResponse +from .process_spawn_response import ProcessSpawnResponse as ProcessSpawnResponse +from .process_stdin_response import ProcessStdinResponse as ProcessStdinResponse +from .process_status_response import ProcessStatusResponse as ProcessStatusResponse from .f_create_directory_params import FCreateDirectoryParams as FCreateDirectoryParams from .f_delete_directory_params import FDeleteDirectoryParams as FDeleteDirectoryParams +from .f_download_dir_zip_params import FDownloadDirZipParams as FDownloadDirZipParams from .f_set_file_permissions_params import FSetFilePermissionsParams as FSetFilePermissionsParams +from .process_stdout_stream_response import ProcessStdoutStreamResponse as ProcessStdoutStreamResponse diff --git a/src/kernel/types/browsers/f_download_dir_zip_params.py b/src/kernel/types/browsers/f_download_dir_zip_params.py new file mode 100644 index 00000000..88212c64 --- /dev/null +++ b/src/kernel/types/browsers/f_download_dir_zip_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 Required, TypedDict + +__all__ = ["FDownloadDirZipParams"] + + +class FDownloadDirZipParams(TypedDict, total=False): + path: Required[str] + """Absolute directory path to archive and download.""" diff --git a/src/kernel/types/browsers/f_upload_params.py b/src/kernel/types/browsers/f_upload_params.py new file mode 100644 index 00000000..8f6534bd --- /dev/null +++ b/src/kernel/types/browsers/f_upload_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 Iterable +from typing_extensions import Required, TypedDict + +from ..._types import FileTypes + +__all__ = ["FUploadParams", "File"] + + +class FUploadParams(TypedDict, total=False): + files: Required[Iterable[File]] + + +class File(TypedDict, total=False): + dest_path: Required[str] + """Absolute destination path to write the file.""" + + file: Required[FileTypes] diff --git a/src/kernel/types/browsers/f_upload_zip_params.py b/src/kernel/types/browsers/f_upload_zip_params.py new file mode 100644 index 00000000..4646e05f --- /dev/null +++ b/src/kernel/types/browsers/f_upload_zip_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_extensions import Required, TypedDict + +from ..._types import FileTypes + +__all__ = ["FUploadZipParams"] + + +class FUploadZipParams(TypedDict, total=False): + dest_path: Required[str] + """Absolute destination directory to extract the archive to.""" + + zip_file: Required[FileTypes] diff --git a/src/kernel/types/browsers/log_stream_params.py b/src/kernel/types/browsers/log_stream_params.py new file mode 100644 index 00000000..2eeb9b32 --- /dev/null +++ b/src/kernel/types/browsers/log_stream_params.py @@ -0,0 +1,19 @@ +# 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__ = ["LogStreamParams"] + + +class LogStreamParams(TypedDict, total=False): + source: Required[Literal["path", "supervisor"]] + + follow: bool + + path: str + """only required if source is path""" + + supervisor_process: str + """only required if source is supervisor""" diff --git a/src/kernel/types/browsers/process_exec_params.py b/src/kernel/types/browsers/process_exec_params.py new file mode 100644 index 00000000..3dd3ad59 --- /dev/null +++ b/src/kernel/types/browsers/process_exec_params.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, List, Optional +from typing_extensions import Required, TypedDict + +__all__ = ["ProcessExecParams"] + + +class ProcessExecParams(TypedDict, total=False): + command: Required[str] + """Executable or shell command to run.""" + + args: List[str] + """Command arguments.""" + + as_root: bool + """Run the process with root privileges.""" + + as_user: Optional[str] + """Run the process as this user.""" + + cwd: Optional[str] + """Working directory (absolute path) to run the command in.""" + + env: Dict[str, str] + """Environment variables to set for the process.""" + + timeout_sec: Optional[int] + """Maximum execution time in seconds.""" diff --git a/src/kernel/types/browsers/process_exec_response.py b/src/kernel/types/browsers/process_exec_response.py new file mode 100644 index 00000000..02588de0 --- /dev/null +++ b/src/kernel/types/browsers/process_exec_response.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel + +__all__ = ["ProcessExecResponse"] + + +class ProcessExecResponse(BaseModel): + duration_ms: Optional[int] = None + """Execution duration in milliseconds.""" + + exit_code: Optional[int] = None + """Process exit code.""" + + stderr_b64: Optional[str] = None + """Base64-encoded stderr buffer.""" + + stdout_b64: Optional[str] = None + """Base64-encoded stdout buffer.""" diff --git a/src/kernel/types/browsers/process_kill_params.py b/src/kernel/types/browsers/process_kill_params.py new file mode 100644 index 00000000..84be2770 --- /dev/null +++ b/src/kernel/types/browsers/process_kill_params.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ProcessKillParams"] + + +class ProcessKillParams(TypedDict, total=False): + id: Required[str] + + signal: Required[Literal["TERM", "KILL", "INT", "HUP"]] + """Signal to send.""" diff --git a/src/kernel/types/browsers/process_kill_response.py b/src/kernel/types/browsers/process_kill_response.py new file mode 100644 index 00000000..ed128a78 --- /dev/null +++ b/src/kernel/types/browsers/process_kill_response.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ..._models import BaseModel + +__all__ = ["ProcessKillResponse"] + + +class ProcessKillResponse(BaseModel): + ok: bool + """Indicates success.""" diff --git a/src/kernel/types/browsers/process_spawn_params.py b/src/kernel/types/browsers/process_spawn_params.py new file mode 100644 index 00000000..a468c473 --- /dev/null +++ b/src/kernel/types/browsers/process_spawn_params.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, List, Optional +from typing_extensions import Required, TypedDict + +__all__ = ["ProcessSpawnParams"] + + +class ProcessSpawnParams(TypedDict, total=False): + command: Required[str] + """Executable or shell command to run.""" + + args: List[str] + """Command arguments.""" + + as_root: bool + """Run the process with root privileges.""" + + as_user: Optional[str] + """Run the process as this user.""" + + cwd: Optional[str] + """Working directory (absolute path) to run the command in.""" + + env: Dict[str, str] + """Environment variables to set for the process.""" + + timeout_sec: Optional[int] + """Maximum execution time in seconds.""" diff --git a/src/kernel/types/browsers/process_spawn_response.py b/src/kernel/types/browsers/process_spawn_response.py new file mode 100644 index 00000000..23444da2 --- /dev/null +++ b/src/kernel/types/browsers/process_spawn_response.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime + +from ..._models import BaseModel + +__all__ = ["ProcessSpawnResponse"] + + +class ProcessSpawnResponse(BaseModel): + pid: Optional[int] = None + """OS process ID.""" + + process_id: Optional[str] = None + """Server-assigned identifier for the process.""" + + started_at: Optional[datetime] = None + """Timestamp when the process started.""" diff --git a/src/kernel/types/browsers/process_status_response.py b/src/kernel/types/browsers/process_status_response.py new file mode 100644 index 00000000..67626fe9 --- /dev/null +++ b/src/kernel/types/browsers/process_status_response.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ProcessStatusResponse"] + + +class ProcessStatusResponse(BaseModel): + cpu_pct: Optional[float] = None + """Estimated CPU usage percentage.""" + + exit_code: Optional[int] = None + """Exit code if the process has exited.""" + + mem_bytes: Optional[int] = None + """Estimated resident memory usage in bytes.""" + + state: Optional[Literal["running", "exited"]] = None + """Process state.""" diff --git a/src/kernel/types/browsers/process_stdin_params.py b/src/kernel/types/browsers/process_stdin_params.py new file mode 100644 index 00000000..9ece9a57 --- /dev/null +++ b/src/kernel/types/browsers/process_stdin_params.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["ProcessStdinParams"] + + +class ProcessStdinParams(TypedDict, total=False): + id: Required[str] + + data_b64: Required[str] + """Base64-encoded data to write.""" diff --git a/src/kernel/types/browsers/process_stdin_response.py b/src/kernel/types/browsers/process_stdin_response.py new file mode 100644 index 00000000..d137a962 --- /dev/null +++ b/src/kernel/types/browsers/process_stdin_response.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel + +__all__ = ["ProcessStdinResponse"] + + +class ProcessStdinResponse(BaseModel): + written_bytes: Optional[int] = None + """Number of bytes written.""" diff --git a/src/kernel/types/browsers/process_stdout_stream_response.py b/src/kernel/types/browsers/process_stdout_stream_response.py new file mode 100644 index 00000000..0b1d0a8e --- /dev/null +++ b/src/kernel/types/browsers/process_stdout_stream_response.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ProcessStdoutStreamResponse"] + + +class ProcessStdoutStreamResponse(BaseModel): + data_b64: Optional[str] = None + """Base64-encoded data from the process stream.""" + + event: Optional[Literal["exit"]] = None + """Lifecycle event type.""" + + exit_code: Optional[int] = None + """Exit code when the event is "exit".""" + + stream: Optional[Literal["stdout", "stderr"]] = None + """Source stream of the data chunk.""" diff --git a/tests/api_resources/browsers/test_fs.py b/tests/api_resources/browsers/test_fs.py index 24860c90..38e07b78 100644 --- a/tests/api_resources/browsers/test_fs.py +++ b/tests/api_resources/browsers/test_fs.py @@ -176,6 +176,60 @@ def test_path_params_delete_file(self, client: Kernel) -> None: path="/J!", ) + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_method_download_dir_zip(self, client: Kernel, respx_mock: MockRouter) -> None: + respx_mock.get("/browsers/id/fs/download_dir_zip").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + f = client.browsers.fs.download_dir_zip( + id="id", + path="/J!", + ) + assert f.is_closed + assert f.json() == {"foo": "bar"} + assert cast(Any, f.is_closed) is True + assert isinstance(f, BinaryAPIResponse) + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_raw_response_download_dir_zip(self, client: Kernel, respx_mock: MockRouter) -> None: + respx_mock.get("/browsers/id/fs/download_dir_zip").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + + f = client.browsers.fs.with_raw_response.download_dir_zip( + id="id", + path="/J!", + ) + + assert f.is_closed is True + assert f.http_request.headers.get("X-Stainless-Lang") == "python" + assert f.json() == {"foo": "bar"} + assert isinstance(f, BinaryAPIResponse) + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_streaming_response_download_dir_zip(self, client: Kernel, respx_mock: MockRouter) -> None: + respx_mock.get("/browsers/id/fs/download_dir_zip").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + with client.browsers.fs.with_streaming_response.download_dir_zip( + id="id", + path="/J!", + ) as f: + assert not f.is_closed + assert f.http_request.headers.get("X-Stainless-Lang") == "python" + + assert f.json() == {"foo": "bar"} + assert cast(Any, f.is_closed) is True + assert isinstance(f, StreamedBinaryAPIResponse) + + assert cast(Any, f.is_closed) is True + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_path_params_download_dir_zip(self, client: Kernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.browsers.fs.with_raw_response.download_dir_zip( + id="", + path="/J!", + ) + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_file_info(self, client: Kernel) -> None: @@ -434,6 +488,122 @@ def test_path_params_set_file_permissions(self, client: Kernel) -> None: path="/J!", ) + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_upload(self, client: Kernel) -> None: + f = client.browsers.fs.upload( + id="id", + files=[ + { + "dest_path": "/J!", + "file": b"raw file contents", + } + ], + ) + assert f is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_upload(self, client: Kernel) -> None: + response = client.browsers.fs.with_raw_response.upload( + id="id", + files=[ + { + "dest_path": "/J!", + "file": b"raw file contents", + } + ], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + f = response.parse() + assert f is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_upload(self, client: Kernel) -> None: + with client.browsers.fs.with_streaming_response.upload( + id="id", + files=[ + { + "dest_path": "/J!", + "file": b"raw file contents", + } + ], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + f = response.parse() + assert f is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_upload(self, client: Kernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.browsers.fs.with_raw_response.upload( + id="", + files=[ + { + "dest_path": "/J!", + "file": b"raw file contents", + } + ], + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_upload_zip(self, client: Kernel) -> None: + f = client.browsers.fs.upload_zip( + id="id", + dest_path="/J!", + zip_file=b"raw file contents", + ) + assert f is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_upload_zip(self, client: Kernel) -> None: + response = client.browsers.fs.with_raw_response.upload_zip( + id="id", + dest_path="/J!", + zip_file=b"raw file contents", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + f = response.parse() + assert f is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_upload_zip(self, client: Kernel) -> None: + with client.browsers.fs.with_streaming_response.upload_zip( + id="id", + dest_path="/J!", + zip_file=b"raw file contents", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + f = response.parse() + assert f is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_upload_zip(self, client: Kernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.browsers.fs.with_raw_response.upload_zip( + id="", + dest_path="/J!", + zip_file=b"raw file contents", + ) + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_write_file(self, client: Kernel) -> None: @@ -649,6 +819,60 @@ async def test_path_params_delete_file(self, async_client: AsyncKernel) -> None: path="/J!", ) + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_method_download_dir_zip(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None: + respx_mock.get("/browsers/id/fs/download_dir_zip").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + f = await async_client.browsers.fs.download_dir_zip( + id="id", + path="/J!", + ) + assert f.is_closed + assert await f.json() == {"foo": "bar"} + assert cast(Any, f.is_closed) is True + assert isinstance(f, AsyncBinaryAPIResponse) + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_raw_response_download_dir_zip(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None: + respx_mock.get("/browsers/id/fs/download_dir_zip").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + + f = await async_client.browsers.fs.with_raw_response.download_dir_zip( + id="id", + path="/J!", + ) + + assert f.is_closed is True + assert f.http_request.headers.get("X-Stainless-Lang") == "python" + assert await f.json() == {"foo": "bar"} + assert isinstance(f, AsyncBinaryAPIResponse) + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_streaming_response_download_dir_zip(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None: + respx_mock.get("/browsers/id/fs/download_dir_zip").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + async with async_client.browsers.fs.with_streaming_response.download_dir_zip( + id="id", + path="/J!", + ) as f: + assert not f.is_closed + assert f.http_request.headers.get("X-Stainless-Lang") == "python" + + assert await f.json() == {"foo": "bar"} + assert cast(Any, f.is_closed) is True + assert isinstance(f, AsyncStreamedBinaryAPIResponse) + + assert cast(Any, f.is_closed) is True + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_path_params_download_dir_zip(self, async_client: AsyncKernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.browsers.fs.with_raw_response.download_dir_zip( + id="", + path="/J!", + ) + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_file_info(self, async_client: AsyncKernel) -> None: @@ -907,6 +1131,122 @@ async def test_path_params_set_file_permissions(self, async_client: AsyncKernel) path="/J!", ) + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_upload(self, async_client: AsyncKernel) -> None: + f = await async_client.browsers.fs.upload( + id="id", + files=[ + { + "dest_path": "/J!", + "file": b"raw file contents", + } + ], + ) + assert f is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_upload(self, async_client: AsyncKernel) -> None: + response = await async_client.browsers.fs.with_raw_response.upload( + id="id", + files=[ + { + "dest_path": "/J!", + "file": b"raw file contents", + } + ], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + f = await response.parse() + assert f is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_upload(self, async_client: AsyncKernel) -> None: + async with async_client.browsers.fs.with_streaming_response.upload( + id="id", + files=[ + { + "dest_path": "/J!", + "file": b"raw file contents", + } + ], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + f = await response.parse() + assert f is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_upload(self, async_client: AsyncKernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.browsers.fs.with_raw_response.upload( + id="", + files=[ + { + "dest_path": "/J!", + "file": b"raw file contents", + } + ], + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_upload_zip(self, async_client: AsyncKernel) -> None: + f = await async_client.browsers.fs.upload_zip( + id="id", + dest_path="/J!", + zip_file=b"raw file contents", + ) + assert f is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_upload_zip(self, async_client: AsyncKernel) -> None: + response = await async_client.browsers.fs.with_raw_response.upload_zip( + id="id", + dest_path="/J!", + zip_file=b"raw file contents", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + f = await response.parse() + assert f is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_upload_zip(self, async_client: AsyncKernel) -> None: + async with async_client.browsers.fs.with_streaming_response.upload_zip( + id="id", + dest_path="/J!", + zip_file=b"raw file contents", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + f = await response.parse() + assert f is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_upload_zip(self, async_client: AsyncKernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.browsers.fs.with_raw_response.upload_zip( + id="", + dest_path="/J!", + zip_file=b"raw file contents", + ) + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_write_file(self, async_client: AsyncKernel) -> None: diff --git a/tests/api_resources/browsers/test_logs.py b/tests/api_resources/browsers/test_logs.py new file mode 100644 index 00000000..6aac62f6 --- /dev/null +++ b/tests/api_resources/browsers/test_logs.py @@ -0,0 +1,136 @@ +# 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 kernel import Kernel, AsyncKernel + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestLogs: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses") + @parametrize + def test_method_stream(self, client: Kernel) -> None: + log_stream = client.browsers.logs.stream( + id="id", + source="path", + ) + log_stream.response.close() + + @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses") + @parametrize + def test_method_stream_with_all_params(self, client: Kernel) -> None: + log_stream = client.browsers.logs.stream( + id="id", + source="path", + follow=True, + path="path", + supervisor_process="supervisor_process", + ) + log_stream.response.close() + + @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses") + @parametrize + def test_raw_response_stream(self, client: Kernel) -> None: + response = client.browsers.logs.with_raw_response.stream( + id="id", + source="path", + ) + + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stream = response.parse() + stream.close() + + @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses") + @parametrize + def test_streaming_response_stream(self, client: Kernel) -> None: + with client.browsers.logs.with_streaming_response.stream( + id="id", + source="path", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stream = response.parse() + stream.close() + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses") + @parametrize + def test_path_params_stream(self, client: Kernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.browsers.logs.with_raw_response.stream( + id="", + source="path", + ) + + +class TestAsyncLogs: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses") + @parametrize + async def test_method_stream(self, async_client: AsyncKernel) -> None: + log_stream = await async_client.browsers.logs.stream( + id="id", + source="path", + ) + await log_stream.response.aclose() + + @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses") + @parametrize + async def test_method_stream_with_all_params(self, async_client: AsyncKernel) -> None: + log_stream = await async_client.browsers.logs.stream( + id="id", + source="path", + follow=True, + path="path", + supervisor_process="supervisor_process", + ) + await log_stream.response.aclose() + + @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses") + @parametrize + async def test_raw_response_stream(self, async_client: AsyncKernel) -> None: + response = await async_client.browsers.logs.with_raw_response.stream( + id="id", + source="path", + ) + + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stream = await response.parse() + await stream.close() + + @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses") + @parametrize + async def test_streaming_response_stream(self, async_client: AsyncKernel) -> None: + async with async_client.browsers.logs.with_streaming_response.stream( + id="id", + source="path", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stream = await response.parse() + await stream.close() + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses") + @parametrize + async def test_path_params_stream(self, async_client: AsyncKernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.browsers.logs.with_raw_response.stream( + id="", + source="path", + ) diff --git a/tests/api_resources/browsers/test_process.py b/tests/api_resources/browsers/test_process.py new file mode 100644 index 00000000..69977621 --- /dev/null +++ b/tests/api_resources/browsers/test_process.py @@ -0,0 +1,708 @@ +# 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 kernel import Kernel, AsyncKernel +from tests.utils import assert_matches_type +from kernel.types.browsers import ( + ProcessExecResponse, + ProcessKillResponse, + ProcessSpawnResponse, + ProcessStdinResponse, + ProcessStatusResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestProcess: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_exec(self, client: Kernel) -> None: + process = client.browsers.process.exec( + id="id", + command="command", + ) + assert_matches_type(ProcessExecResponse, process, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_exec_with_all_params(self, client: Kernel) -> None: + process = client.browsers.process.exec( + id="id", + command="command", + args=["string"], + as_root=True, + as_user="as_user", + cwd="/J!", + env={"foo": "string"}, + timeout_sec=0, + ) + assert_matches_type(ProcessExecResponse, process, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_exec(self, client: Kernel) -> None: + response = client.browsers.process.with_raw_response.exec( + id="id", + command="command", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + process = response.parse() + assert_matches_type(ProcessExecResponse, process, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_exec(self, client: Kernel) -> None: + with client.browsers.process.with_streaming_response.exec( + id="id", + command="command", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + process = response.parse() + assert_matches_type(ProcessExecResponse, process, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_exec(self, client: Kernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.browsers.process.with_raw_response.exec( + id="", + command="command", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_kill(self, client: Kernel) -> None: + process = client.browsers.process.kill( + process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + id="id", + signal="TERM", + ) + assert_matches_type(ProcessKillResponse, process, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_kill(self, client: Kernel) -> None: + response = client.browsers.process.with_raw_response.kill( + process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + id="id", + signal="TERM", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + process = response.parse() + assert_matches_type(ProcessKillResponse, process, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_kill(self, client: Kernel) -> None: + with client.browsers.process.with_streaming_response.kill( + process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + id="id", + signal="TERM", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + process = response.parse() + assert_matches_type(ProcessKillResponse, process, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_kill(self, client: Kernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.browsers.process.with_raw_response.kill( + process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + id="", + signal="TERM", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `process_id` but received ''"): + client.browsers.process.with_raw_response.kill( + process_id="", + id="id", + signal="TERM", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_spawn(self, client: Kernel) -> None: + process = client.browsers.process.spawn( + id="id", + command="command", + ) + assert_matches_type(ProcessSpawnResponse, process, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_spawn_with_all_params(self, client: Kernel) -> None: + process = client.browsers.process.spawn( + id="id", + command="command", + args=["string"], + as_root=True, + as_user="as_user", + cwd="/J!", + env={"foo": "string"}, + timeout_sec=0, + ) + assert_matches_type(ProcessSpawnResponse, process, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_spawn(self, client: Kernel) -> None: + response = client.browsers.process.with_raw_response.spawn( + id="id", + command="command", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + process = response.parse() + assert_matches_type(ProcessSpawnResponse, process, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_spawn(self, client: Kernel) -> None: + with client.browsers.process.with_streaming_response.spawn( + id="id", + command="command", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + process = response.parse() + assert_matches_type(ProcessSpawnResponse, process, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_spawn(self, client: Kernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.browsers.process.with_raw_response.spawn( + id="", + command="command", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_status(self, client: Kernel) -> None: + process = client.browsers.process.status( + process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + id="id", + ) + assert_matches_type(ProcessStatusResponse, process, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_status(self, client: Kernel) -> None: + response = client.browsers.process.with_raw_response.status( + process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + id="id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + process = response.parse() + assert_matches_type(ProcessStatusResponse, process, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_status(self, client: Kernel) -> None: + with client.browsers.process.with_streaming_response.status( + process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + id="id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + process = response.parse() + assert_matches_type(ProcessStatusResponse, process, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_status(self, client: Kernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.browsers.process.with_raw_response.status( + process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `process_id` but received ''"): + client.browsers.process.with_raw_response.status( + process_id="", + id="id", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_stdin(self, client: Kernel) -> None: + process = client.browsers.process.stdin( + process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + id="id", + data_b64="data_b64", + ) + assert_matches_type(ProcessStdinResponse, process, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_stdin(self, client: Kernel) -> None: + response = client.browsers.process.with_raw_response.stdin( + process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + id="id", + data_b64="data_b64", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + process = response.parse() + assert_matches_type(ProcessStdinResponse, process, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_stdin(self, client: Kernel) -> None: + with client.browsers.process.with_streaming_response.stdin( + process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + id="id", + data_b64="data_b64", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + process = response.parse() + assert_matches_type(ProcessStdinResponse, process, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_stdin(self, client: Kernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.browsers.process.with_raw_response.stdin( + process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + id="", + data_b64="data_b64", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `process_id` but received ''"): + client.browsers.process.with_raw_response.stdin( + process_id="", + id="id", + data_b64="data_b64", + ) + + @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses") + @parametrize + def test_method_stdout_stream(self, client: Kernel) -> None: + process_stream = client.browsers.process.stdout_stream( + process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + id="id", + ) + process_stream.response.close() + + @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses") + @parametrize + def test_raw_response_stdout_stream(self, client: Kernel) -> None: + response = client.browsers.process.with_raw_response.stdout_stream( + process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + id="id", + ) + + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stream = response.parse() + stream.close() + + @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses") + @parametrize + def test_streaming_response_stdout_stream(self, client: Kernel) -> None: + with client.browsers.process.with_streaming_response.stdout_stream( + process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + id="id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stream = response.parse() + stream.close() + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses") + @parametrize + def test_path_params_stdout_stream(self, client: Kernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.browsers.process.with_raw_response.stdout_stream( + process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `process_id` but received ''"): + client.browsers.process.with_raw_response.stdout_stream( + process_id="", + id="id", + ) + + +class TestAsyncProcess: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_exec(self, async_client: AsyncKernel) -> None: + process = await async_client.browsers.process.exec( + id="id", + command="command", + ) + assert_matches_type(ProcessExecResponse, process, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_exec_with_all_params(self, async_client: AsyncKernel) -> None: + process = await async_client.browsers.process.exec( + id="id", + command="command", + args=["string"], + as_root=True, + as_user="as_user", + cwd="/J!", + env={"foo": "string"}, + timeout_sec=0, + ) + assert_matches_type(ProcessExecResponse, process, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_exec(self, async_client: AsyncKernel) -> None: + response = await async_client.browsers.process.with_raw_response.exec( + id="id", + command="command", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + process = await response.parse() + assert_matches_type(ProcessExecResponse, process, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_exec(self, async_client: AsyncKernel) -> None: + async with async_client.browsers.process.with_streaming_response.exec( + id="id", + command="command", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + process = await response.parse() + assert_matches_type(ProcessExecResponse, process, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_exec(self, async_client: AsyncKernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.browsers.process.with_raw_response.exec( + id="", + command="command", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_kill(self, async_client: AsyncKernel) -> None: + process = await async_client.browsers.process.kill( + process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + id="id", + signal="TERM", + ) + assert_matches_type(ProcessKillResponse, process, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_kill(self, async_client: AsyncKernel) -> None: + response = await async_client.browsers.process.with_raw_response.kill( + process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + id="id", + signal="TERM", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + process = await response.parse() + assert_matches_type(ProcessKillResponse, process, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_kill(self, async_client: AsyncKernel) -> None: + async with async_client.browsers.process.with_streaming_response.kill( + process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + id="id", + signal="TERM", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + process = await response.parse() + assert_matches_type(ProcessKillResponse, process, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_kill(self, async_client: AsyncKernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.browsers.process.with_raw_response.kill( + process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + id="", + signal="TERM", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `process_id` but received ''"): + await async_client.browsers.process.with_raw_response.kill( + process_id="", + id="id", + signal="TERM", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_spawn(self, async_client: AsyncKernel) -> None: + process = await async_client.browsers.process.spawn( + id="id", + command="command", + ) + assert_matches_type(ProcessSpawnResponse, process, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_spawn_with_all_params(self, async_client: AsyncKernel) -> None: + process = await async_client.browsers.process.spawn( + id="id", + command="command", + args=["string"], + as_root=True, + as_user="as_user", + cwd="/J!", + env={"foo": "string"}, + timeout_sec=0, + ) + assert_matches_type(ProcessSpawnResponse, process, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_spawn(self, async_client: AsyncKernel) -> None: + response = await async_client.browsers.process.with_raw_response.spawn( + id="id", + command="command", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + process = await response.parse() + assert_matches_type(ProcessSpawnResponse, process, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_spawn(self, async_client: AsyncKernel) -> None: + async with async_client.browsers.process.with_streaming_response.spawn( + id="id", + command="command", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + process = await response.parse() + assert_matches_type(ProcessSpawnResponse, process, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_spawn(self, async_client: AsyncKernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.browsers.process.with_raw_response.spawn( + id="", + command="command", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_status(self, async_client: AsyncKernel) -> None: + process = await async_client.browsers.process.status( + process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + id="id", + ) + assert_matches_type(ProcessStatusResponse, process, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_status(self, async_client: AsyncKernel) -> None: + response = await async_client.browsers.process.with_raw_response.status( + process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + id="id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + process = await response.parse() + assert_matches_type(ProcessStatusResponse, process, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_status(self, async_client: AsyncKernel) -> None: + async with async_client.browsers.process.with_streaming_response.status( + process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + id="id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + process = await response.parse() + assert_matches_type(ProcessStatusResponse, process, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_status(self, async_client: AsyncKernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.browsers.process.with_raw_response.status( + process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `process_id` but received ''"): + await async_client.browsers.process.with_raw_response.status( + process_id="", + id="id", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_stdin(self, async_client: AsyncKernel) -> None: + process = await async_client.browsers.process.stdin( + process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + id="id", + data_b64="data_b64", + ) + assert_matches_type(ProcessStdinResponse, process, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_stdin(self, async_client: AsyncKernel) -> None: + response = await async_client.browsers.process.with_raw_response.stdin( + process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + id="id", + data_b64="data_b64", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + process = await response.parse() + assert_matches_type(ProcessStdinResponse, process, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_stdin(self, async_client: AsyncKernel) -> None: + async with async_client.browsers.process.with_streaming_response.stdin( + process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + id="id", + data_b64="data_b64", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + process = await response.parse() + assert_matches_type(ProcessStdinResponse, process, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_stdin(self, async_client: AsyncKernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.browsers.process.with_raw_response.stdin( + process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + id="", + data_b64="data_b64", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `process_id` but received ''"): + await async_client.browsers.process.with_raw_response.stdin( + process_id="", + id="id", + data_b64="data_b64", + ) + + @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses") + @parametrize + async def test_method_stdout_stream(self, async_client: AsyncKernel) -> None: + process_stream = await async_client.browsers.process.stdout_stream( + process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + id="id", + ) + await process_stream.response.aclose() + + @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses") + @parametrize + async def test_raw_response_stdout_stream(self, async_client: AsyncKernel) -> None: + response = await async_client.browsers.process.with_raw_response.stdout_stream( + process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + id="id", + ) + + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stream = await response.parse() + await stream.close() + + @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses") + @parametrize + async def test_streaming_response_stdout_stream(self, async_client: AsyncKernel) -> None: + async with async_client.browsers.process.with_streaming_response.stdout_stream( + process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + id="id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stream = await response.parse() + await stream.close() + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses") + @parametrize + async def test_path_params_stdout_stream(self, async_client: AsyncKernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.browsers.process.with_raw_response.stdout_stream( + process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `process_id` but received ''"): + await async_client.browsers.process.with_raw_response.stdout_stream( + process_id="", + id="id", + ) From 18fd3a673fab82f414e60ca816b135d320a5eb76 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 27 Aug 2025 17:39:30 +0000 Subject: [PATCH 6/6] release: 0.10.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 20 ++++++++++++++++++++ pyproject.toml | 2 +- src/kernel/_version.py | 2 +- 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 05988747..091cfb12 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.9.1" + ".": "0.10.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index d78a949e..508c4207 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## 0.10.0 (2025-08-27) + +Full Changelog: [v0.9.1...v0.10.0](https://github.com/onkernel/kernel-python-sdk/compare/v0.9.1...v0.10.0) + +### Features + +* **api:** new process, fs, and log endpoints ([48a39b4](https://github.com/onkernel/kernel-python-sdk/commit/48a39b4cc1ab32b4375ad4e33e5f9e4349502072)) + + +### Bug Fixes + +* avoid newer type syntax ([9ec7c40](https://github.com/onkernel/kernel-python-sdk/commit/9ec7c40b34b264709b904f36e309624bd1161413)) + + +### Chores + +* **internal:** change ci workflow machines ([3a2969d](https://github.com/onkernel/kernel-python-sdk/commit/3a2969d035b9e0bb4fa39dc27de2db6d4edad6dd)) +* **internal:** update pyright exclude list ([39439aa](https://github.com/onkernel/kernel-python-sdk/commit/39439aaad72c92aa9f4bb74ac055b929c93b6060)) +* update github action ([fff64d0](https://github.com/onkernel/kernel-python-sdk/commit/fff64d001d2c759967f08e7a1932e1fb7d84b126)) + ## 0.9.1 (2025-08-15) Full Changelog: [v0.9.0...v0.9.1](https://github.com/onkernel/kernel-python-sdk/compare/v0.9.0...v0.9.1) diff --git a/pyproject.toml b/pyproject.toml index 584c3677..3cc0fb34 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "kernel" -version = "0.9.1" +version = "0.10.0" description = "The official Python library for the kernel API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/kernel/_version.py b/src/kernel/_version.py index de748b73..db4afd4b 100644 --- a/src/kernel/_version.py +++ b/src/kernel/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "kernel" -__version__ = "0.9.1" # x-release-please-version +__version__ = "0.10.0" # x-release-please-version