Skip to content

Commit 2b79e96

Browse files
feat: WIP browser extensions
1 parent 6fa28e3 commit 2b79e96

15 files changed

+1464
-8
lines changed

.stats.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
configured_endpoints: 51
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-8a6175a75caa75c3de5400edf97a34e526ac3f62c63955375437461581deb0c2.yml
3-
openapi_spec_hash: 1a880e4ce337a0e44630e6d87ef5162a
4-
config_hash: 49c2ff978aaa5ccb4ce324a72f116010
1+
configured_endpoints: 57
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-936db268b3dcae5d64bd5d590506d8134304ffcbf67389eb9b1555b3febfd4cb.yml
3+
openapi_spec_hash: 145485087adf1b28c052bacb4df68462
4+
config_hash: 5236f9b34e39dc1930e36a88c714abd4

api.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ Methods:
8282
- <code title="get /browsers">client.browsers.<a href="./src/kernel/resources/browsers/browsers.py">list</a>() -> <a href="./src/kernel/types/browser_list_response.py">BrowserListResponse</a></code>
8383
- <code title="delete /browsers">client.browsers.<a href="./src/kernel/resources/browsers/browsers.py">delete</a>(\*\*<a href="src/kernel/types/browser_delete_params.py">params</a>) -> None</code>
8484
- <code title="delete /browsers/{id}">client.browsers.<a href="./src/kernel/resources/browsers/browsers.py">delete_by_id</a>(id) -> None</code>
85+
- <code title="post /browsers/{id}/extensions">client.browsers.<a href="./src/kernel/resources/browsers/browsers.py">upload_extensions</a>(id, \*\*<a href="src/kernel/types/browser_upload_extensions_params.py">params</a>) -> None</code>
8586

8687
## Replays
8788

@@ -195,3 +196,19 @@ Methods:
195196
- <code title="get /proxies/{id}">client.proxies.<a href="./src/kernel/resources/proxies.py">retrieve</a>(id) -> <a href="./src/kernel/types/proxy_retrieve_response.py">ProxyRetrieveResponse</a></code>
196197
- <code title="get /proxies">client.proxies.<a href="./src/kernel/resources/proxies.py">list</a>() -> <a href="./src/kernel/types/proxy_list_response.py">ProxyListResponse</a></code>
197198
- <code title="delete /proxies/{id}">client.proxies.<a href="./src/kernel/resources/proxies.py">delete</a>(id) -> None</code>
199+
200+
# Extensions
201+
202+
Types:
203+
204+
```python
205+
from kernel.types import ExtensionListResponse, ExtensionUploadResponse
206+
```
207+
208+
Methods:
209+
210+
- <code title="get /extensions">client.extensions.<a href="./src/kernel/resources/extensions.py">list</a>() -> <a href="./src/kernel/types/extension_list_response.py">ExtensionListResponse</a></code>
211+
- <code title="delete /extensions/{id_or_name}">client.extensions.<a href="./src/kernel/resources/extensions.py">delete</a>(id_or_name) -> None</code>
212+
- <code title="get /extensions/{id_or_name}">client.extensions.<a href="./src/kernel/resources/extensions.py">download</a>(id_or_name) -> BinaryAPIResponse</code>
213+
- <code title="get /extensions/from_chrome_store">client.extensions.<a href="./src/kernel/resources/extensions.py">download_from_chrome_store</a>(\*\*<a href="src/kernel/types/extension_download_from_chrome_store_params.py">params</a>) -> BinaryAPIResponse</code>
214+
- <code title="post /extensions">client.extensions.<a href="./src/kernel/resources/extensions.py">upload</a>(\*\*<a href="src/kernel/types/extension_upload_params.py">params</a>) -> <a href="./src/kernel/types/extension_upload_response.py">ExtensionUploadResponse</a></code>

src/kernel/_client.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
)
2222
from ._utils import is_given, get_async_library
2323
from ._version import __version__
24-
from .resources import apps, proxies, profiles, deployments, invocations
24+
from .resources import apps, proxies, profiles, extensions, deployments, invocations
2525
from ._streaming import Stream as Stream, AsyncStream as AsyncStream
2626
from ._exceptions import KernelError, APIStatusError
2727
from ._base_client import (
@@ -56,6 +56,7 @@ class Kernel(SyncAPIClient):
5656
browsers: browsers.BrowsersResource
5757
profiles: profiles.ProfilesResource
5858
proxies: proxies.ProxiesResource
59+
extensions: extensions.ExtensionsResource
5960
with_raw_response: KernelWithRawResponse
6061
with_streaming_response: KernelWithStreamedResponse
6162

@@ -143,6 +144,7 @@ def __init__(
143144
self.browsers = browsers.BrowsersResource(self)
144145
self.profiles = profiles.ProfilesResource(self)
145146
self.proxies = proxies.ProxiesResource(self)
147+
self.extensions = extensions.ExtensionsResource(self)
146148
self.with_raw_response = KernelWithRawResponse(self)
147149
self.with_streaming_response = KernelWithStreamedResponse(self)
148150

@@ -260,6 +262,7 @@ class AsyncKernel(AsyncAPIClient):
260262
browsers: browsers.AsyncBrowsersResource
261263
profiles: profiles.AsyncProfilesResource
262264
proxies: proxies.AsyncProxiesResource
265+
extensions: extensions.AsyncExtensionsResource
263266
with_raw_response: AsyncKernelWithRawResponse
264267
with_streaming_response: AsyncKernelWithStreamedResponse
265268

@@ -347,6 +350,7 @@ def __init__(
347350
self.browsers = browsers.AsyncBrowsersResource(self)
348351
self.profiles = profiles.AsyncProfilesResource(self)
349352
self.proxies = proxies.AsyncProxiesResource(self)
353+
self.extensions = extensions.AsyncExtensionsResource(self)
350354
self.with_raw_response = AsyncKernelWithRawResponse(self)
351355
self.with_streaming_response = AsyncKernelWithStreamedResponse(self)
352356

@@ -465,6 +469,7 @@ def __init__(self, client: Kernel) -> None:
465469
self.browsers = browsers.BrowsersResourceWithRawResponse(client.browsers)
466470
self.profiles = profiles.ProfilesResourceWithRawResponse(client.profiles)
467471
self.proxies = proxies.ProxiesResourceWithRawResponse(client.proxies)
472+
self.extensions = extensions.ExtensionsResourceWithRawResponse(client.extensions)
468473

469474

470475
class AsyncKernelWithRawResponse:
@@ -475,6 +480,7 @@ def __init__(self, client: AsyncKernel) -> None:
475480
self.browsers = browsers.AsyncBrowsersResourceWithRawResponse(client.browsers)
476481
self.profiles = profiles.AsyncProfilesResourceWithRawResponse(client.profiles)
477482
self.proxies = proxies.AsyncProxiesResourceWithRawResponse(client.proxies)
483+
self.extensions = extensions.AsyncExtensionsResourceWithRawResponse(client.extensions)
478484

479485

480486
class KernelWithStreamedResponse:
@@ -485,6 +491,7 @@ def __init__(self, client: Kernel) -> None:
485491
self.browsers = browsers.BrowsersResourceWithStreamingResponse(client.browsers)
486492
self.profiles = profiles.ProfilesResourceWithStreamingResponse(client.profiles)
487493
self.proxies = proxies.ProxiesResourceWithStreamingResponse(client.proxies)
494+
self.extensions = extensions.ExtensionsResourceWithStreamingResponse(client.extensions)
488495

489496

490497
class AsyncKernelWithStreamedResponse:
@@ -495,6 +502,7 @@ def __init__(self, client: AsyncKernel) -> None:
495502
self.browsers = browsers.AsyncBrowsersResourceWithStreamingResponse(client.browsers)
496503
self.profiles = profiles.AsyncProfilesResourceWithStreamingResponse(client.profiles)
497504
self.proxies = proxies.AsyncProxiesResourceWithStreamingResponse(client.proxies)
505+
self.extensions = extensions.AsyncExtensionsResourceWithStreamingResponse(client.extensions)
498506

499507

500508
Client = Kernel

src/kernel/resources/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@
3232
ProfilesResourceWithStreamingResponse,
3333
AsyncProfilesResourceWithStreamingResponse,
3434
)
35+
from .extensions import (
36+
ExtensionsResource,
37+
AsyncExtensionsResource,
38+
ExtensionsResourceWithRawResponse,
39+
AsyncExtensionsResourceWithRawResponse,
40+
ExtensionsResourceWithStreamingResponse,
41+
AsyncExtensionsResourceWithStreamingResponse,
42+
)
3543
from .deployments import (
3644
DeploymentsResource,
3745
AsyncDeploymentsResource,
@@ -86,4 +94,10 @@
8694
"AsyncProxiesResourceWithRawResponse",
8795
"ProxiesResourceWithStreamingResponse",
8896
"AsyncProxiesResourceWithStreamingResponse",
97+
"ExtensionsResource",
98+
"AsyncExtensionsResource",
99+
"ExtensionsResourceWithRawResponse",
100+
"AsyncExtensionsResourceWithRawResponse",
101+
"ExtensionsResourceWithStreamingResponse",
102+
"AsyncExtensionsResourceWithStreamingResponse",
89103
]

src/kernel/resources/browsers/browsers.py

Lines changed: 116 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from __future__ import annotations
44

5+
from typing import Mapping, Iterable, cast
6+
57
import httpx
68

79
from .logs import (
@@ -20,7 +22,7 @@
2022
FsResourceWithStreamingResponse,
2123
AsyncFsResourceWithStreamingResponse,
2224
)
23-
from ...types import browser_create_params, browser_delete_params
25+
from ...types import browser_create_params, browser_delete_params, browser_upload_extensions_params
2426
from .process import (
2527
ProcessResource,
2628
AsyncProcessResource,
@@ -38,7 +40,7 @@
3840
AsyncReplaysResourceWithStreamingResponse,
3941
)
4042
from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
41-
from ..._utils import maybe_transform, async_maybe_transform
43+
from ..._utils import extract_files, maybe_transform, deepcopy_minimal, async_maybe_transform
4244
from ..._compat import cached_property
4345
from ..._resource import SyncAPIResource, AsyncAPIResource
4446
from ..._response import (
@@ -95,6 +97,7 @@ def with_streaming_response(self) -> BrowsersResourceWithStreamingResponse:
9597
def create(
9698
self,
9799
*,
100+
extensions: Iterable[browser_create_params.Extension] | Omit = omit,
98101
headless: bool | Omit = omit,
99102
invocation_id: str | Omit = omit,
100103
persistence: BrowserPersistenceParam | Omit = omit,
@@ -113,6 +116,8 @@ def create(
113116
Create a new browser session from within an action.
114117
115118
Args:
119+
extensions: List of browser extensions to load into the session. Provide each by id or name.
120+
116121
headless: If true, launches the browser using a headless image (no VNC/GUI). Defaults to
117122
false.
118123
@@ -149,6 +154,7 @@ def create(
149154
"/browsers",
150155
body=maybe_transform(
151156
{
157+
"extensions": extensions,
152158
"headless": headless,
153159
"invocation_id": invocation_id,
154160
"persistence": persistence,
@@ -289,6 +295,52 @@ def delete_by_id(
289295
cast_to=NoneType,
290296
)
291297

298+
def upload_extensions(
299+
self,
300+
id: str,
301+
*,
302+
extensions: Iterable[browser_upload_extensions_params.Extension],
303+
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
304+
# The extra values given here take precedence over values defined on the client or passed to this method.
305+
extra_headers: Headers | None = None,
306+
extra_query: Query | None = None,
307+
extra_body: Body | None = None,
308+
timeout: float | httpx.Timeout | None | NotGiven = not_given,
309+
) -> None:
310+
"""
311+
Loads one or more unpacked extensions and restarts Chromium on the browser
312+
instance.
313+
314+
Args:
315+
extensions: List of extensions to upload and activate
316+
317+
extra_headers: Send extra headers
318+
319+
extra_query: Add additional query parameters to the request
320+
321+
extra_body: Add additional JSON properties to the request
322+
323+
timeout: Override the client-level default timeout for this request, in seconds
324+
"""
325+
if not id:
326+
raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
327+
extra_headers = {"Accept": "*/*", **(extra_headers or {})}
328+
body = deepcopy_minimal({"extensions": extensions})
329+
files = extract_files(cast(Mapping[str, object], body), paths=[["extensions", "<array>", "zip_file"]])
330+
# It should be noted that the actual Content-Type header that will be
331+
# sent to the server will contain a `boundary` parameter, e.g.
332+
# multipart/form-data; boundary=---abc--
333+
extra_headers["Content-Type"] = "multipart/form-data"
334+
return self._post(
335+
f"/browsers/{id}/extensions",
336+
body=maybe_transform(body, browser_upload_extensions_params.BrowserUploadExtensionsParams),
337+
files=files,
338+
options=make_request_options(
339+
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
340+
),
341+
cast_to=NoneType,
342+
)
343+
292344

293345
class AsyncBrowsersResource(AsyncAPIResource):
294346
@cached_property
@@ -329,6 +381,7 @@ def with_streaming_response(self) -> AsyncBrowsersResourceWithStreamingResponse:
329381
async def create(
330382
self,
331383
*,
384+
extensions: Iterable[browser_create_params.Extension] | Omit = omit,
332385
headless: bool | Omit = omit,
333386
invocation_id: str | Omit = omit,
334387
persistence: BrowserPersistenceParam | Omit = omit,
@@ -347,6 +400,8 @@ async def create(
347400
Create a new browser session from within an action.
348401
349402
Args:
403+
extensions: List of browser extensions to load into the session. Provide each by id or name.
404+
350405
headless: If true, launches the browser using a headless image (no VNC/GUI). Defaults to
351406
false.
352407
@@ -383,6 +438,7 @@ async def create(
383438
"/browsers",
384439
body=await async_maybe_transform(
385440
{
441+
"extensions": extensions,
386442
"headless": headless,
387443
"invocation_id": invocation_id,
388444
"persistence": persistence,
@@ -525,6 +581,52 @@ async def delete_by_id(
525581
cast_to=NoneType,
526582
)
527583

584+
async def upload_extensions(
585+
self,
586+
id: str,
587+
*,
588+
extensions: Iterable[browser_upload_extensions_params.Extension],
589+
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
590+
# The extra values given here take precedence over values defined on the client or passed to this method.
591+
extra_headers: Headers | None = None,
592+
extra_query: Query | None = None,
593+
extra_body: Body | None = None,
594+
timeout: float | httpx.Timeout | None | NotGiven = not_given,
595+
) -> None:
596+
"""
597+
Loads one or more unpacked extensions and restarts Chromium on the browser
598+
instance.
599+
600+
Args:
601+
extensions: List of extensions to upload and activate
602+
603+
extra_headers: Send extra headers
604+
605+
extra_query: Add additional query parameters to the request
606+
607+
extra_body: Add additional JSON properties to the request
608+
609+
timeout: Override the client-level default timeout for this request, in seconds
610+
"""
611+
if not id:
612+
raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
613+
extra_headers = {"Accept": "*/*", **(extra_headers or {})}
614+
body = deepcopy_minimal({"extensions": extensions})
615+
files = extract_files(cast(Mapping[str, object], body), paths=[["extensions", "<array>", "zip_file"]])
616+
# It should be noted that the actual Content-Type header that will be
617+
# sent to the server will contain a `boundary` parameter, e.g.
618+
# multipart/form-data; boundary=---abc--
619+
extra_headers["Content-Type"] = "multipart/form-data"
620+
return await self._post(
621+
f"/browsers/{id}/extensions",
622+
body=await async_maybe_transform(body, browser_upload_extensions_params.BrowserUploadExtensionsParams),
623+
files=files,
624+
options=make_request_options(
625+
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
626+
),
627+
cast_to=NoneType,
628+
)
629+
528630

529631
class BrowsersResourceWithRawResponse:
530632
def __init__(self, browsers: BrowsersResource) -> None:
@@ -545,6 +647,9 @@ def __init__(self, browsers: BrowsersResource) -> None:
545647
self.delete_by_id = to_raw_response_wrapper(
546648
browsers.delete_by_id,
547649
)
650+
self.upload_extensions = to_raw_response_wrapper(
651+
browsers.upload_extensions,
652+
)
548653

549654
@cached_property
550655
def replays(self) -> ReplaysResourceWithRawResponse:
@@ -582,6 +687,9 @@ def __init__(self, browsers: AsyncBrowsersResource) -> None:
582687
self.delete_by_id = async_to_raw_response_wrapper(
583688
browsers.delete_by_id,
584689
)
690+
self.upload_extensions = async_to_raw_response_wrapper(
691+
browsers.upload_extensions,
692+
)
585693

586694
@cached_property
587695
def replays(self) -> AsyncReplaysResourceWithRawResponse:
@@ -619,6 +727,9 @@ def __init__(self, browsers: BrowsersResource) -> None:
619727
self.delete_by_id = to_streamed_response_wrapper(
620728
browsers.delete_by_id,
621729
)
730+
self.upload_extensions = to_streamed_response_wrapper(
731+
browsers.upload_extensions,
732+
)
622733

623734
@cached_property
624735
def replays(self) -> ReplaysResourceWithStreamingResponse:
@@ -656,6 +767,9 @@ def __init__(self, browsers: AsyncBrowsersResource) -> None:
656767
self.delete_by_id = async_to_streamed_response_wrapper(
657768
browsers.delete_by_id,
658769
)
770+
self.upload_extensions = async_to_streamed_response_wrapper(
771+
browsers.upload_extensions,
772+
)
659773

660774
@cached_property
661775
def replays(self) -> AsyncReplaysResourceWithStreamingResponse:

0 commit comments

Comments
 (0)