From 6dfd8826b84ed191d72ac114df986c398fa83c5c Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 8 Apr 2026 03:30:26 +0000
Subject: [PATCH 1/5] fix(client): preserve hardcoded query params when merging
with user params
---
src/kernel/_base_client.py | 4 ++++
tests/test_client.py | 48 ++++++++++++++++++++++++++++++++++++++
2 files changed, 52 insertions(+)
diff --git a/src/kernel/_base_client.py b/src/kernel/_base_client.py
index a3d47ea..2599dc4 100644
--- a/src/kernel/_base_client.py
+++ b/src/kernel/_base_client.py
@@ -540,6 +540,10 @@ def _build_request(
files = cast(HttpxRequestFiles, ForceMultipartDict())
prepared_url = self._prepare_url(options.url)
+ # preserve hard-coded query params from the url
+ if params and prepared_url.query:
+ params = {**dict(prepared_url.params.items()), **params}
+ prepared_url = prepared_url.copy_with(raw_path=prepared_url.raw_path.split(b"?", 1)[0])
if "_" in prepared_url.host:
# work around https://github.com/encode/httpx/discussions/2880
kwargs["extensions"] = {"sni_hostname": prepared_url.host.replace("_", "-")}
diff --git a/tests/test_client.py b/tests/test_client.py
index d2eca05..c3a1186 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -427,6 +427,30 @@ def test_default_query_option(self) -> None:
client.close()
+ def test_hardcoded_query_params_in_url(self, client: Kernel) -> None:
+ request = client._build_request(FinalRequestOptions(method="get", url="/foo?beta=true"))
+ url = httpx.URL(request.url)
+ assert dict(url.params) == {"beta": "true"}
+
+ request = client._build_request(
+ FinalRequestOptions(
+ method="get",
+ url="/foo?beta=true",
+ params={"limit": "10", "page": "abc"},
+ )
+ )
+ url = httpx.URL(request.url)
+ assert dict(url.params) == {"beta": "true", "limit": "10", "page": "abc"}
+
+ request = client._build_request(
+ FinalRequestOptions(
+ method="get",
+ url="/files/a%2Fb?beta=true",
+ params={"limit": "10"},
+ )
+ )
+ assert request.url.raw_path == b"/files/a%2Fb?beta=true&limit=10"
+
def test_request_extra_json(self, client: Kernel) -> None:
request = client._build_request(
FinalRequestOptions(
@@ -1330,6 +1354,30 @@ async def test_default_query_option(self) -> None:
await client.close()
+ async def test_hardcoded_query_params_in_url(self, async_client: AsyncKernel) -> None:
+ request = async_client._build_request(FinalRequestOptions(method="get", url="/foo?beta=true"))
+ url = httpx.URL(request.url)
+ assert dict(url.params) == {"beta": "true"}
+
+ request = async_client._build_request(
+ FinalRequestOptions(
+ method="get",
+ url="/foo?beta=true",
+ params={"limit": "10", "page": "abc"},
+ )
+ )
+ url = httpx.URL(request.url)
+ assert dict(url.params) == {"beta": "true", "limit": "10", "page": "abc"}
+
+ request = async_client._build_request(
+ FinalRequestOptions(
+ method="get",
+ url="/files/a%2Fb?beta=true",
+ params={"limit": "10"},
+ )
+ )
+ assert request.url.raw_path == b"/files/a%2Fb?beta=true&limit=10"
+
def test_request_extra_json(self, client: Kernel) -> None:
request = client._build_request(
FinalRequestOptions(
From 335d9e04998dd9e581f47d8869dd7f07a8f2db74 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 8 Apr 2026 13:12:06 +0000
Subject: [PATCH 2/5] feat: [kernel-1116] add base_url field to browser session
response
---
.stats.yml | 4 ++--
src/kernel/types/browser_create_response.py | 3 +++
src/kernel/types/browser_list_response.py | 3 +++
src/kernel/types/browser_pool_acquire_response.py | 3 +++
src/kernel/types/browser_retrieve_response.py | 3 +++
src/kernel/types/browser_update_response.py | 3 +++
src/kernel/types/invocation_list_browsers_response.py | 3 +++
7 files changed, 20 insertions(+), 2 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 7f48513..b81754a 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 104
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-ac10847d991ef8ed89124b5550922cb5726af2b4a4c3396ee6ff82938302fc25.yml
-openapi_spec_hash: 0d902563108fe2461708c05336eab40a
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-aee09720882ec1d78f845fee6ceecb0466c264629e4edecd3230406dd06d7983.yml
+openapi_spec_hash: 438da0d38d169897595f301d82fa7e2c
config_hash: 16e4457a0bb26e98a335a1c2a572290a
diff --git a/src/kernel/types/browser_create_response.py b/src/kernel/types/browser_create_response.py
index d59a3d0..9356bb0 100644
--- a/src/kernel/types/browser_create_response.py
+++ b/src/kernel/types/browser_create_response.py
@@ -35,6 +35,9 @@ class BrowserCreateResponse(BaseModel):
webdriver_ws_url: str
"""Websocket URL for WebDriver BiDi connections to the browser session"""
+ base_url: Optional[str] = None
+ """Metro-API HTTP base URL for this browser session."""
+
browser_live_view_url: Optional[str] = None
"""Remote URL for live viewing the browser session.
diff --git a/src/kernel/types/browser_list_response.py b/src/kernel/types/browser_list_response.py
index 708caa9..f3a88f2 100644
--- a/src/kernel/types/browser_list_response.py
+++ b/src/kernel/types/browser_list_response.py
@@ -35,6 +35,9 @@ class BrowserListResponse(BaseModel):
webdriver_ws_url: str
"""Websocket URL for WebDriver BiDi connections to the browser session"""
+ base_url: Optional[str] = None
+ """Metro-API HTTP base URL for this browser session."""
+
browser_live_view_url: Optional[str] = None
"""Remote URL for live viewing the browser session.
diff --git a/src/kernel/types/browser_pool_acquire_response.py b/src/kernel/types/browser_pool_acquire_response.py
index 5ab52b5..064c405 100644
--- a/src/kernel/types/browser_pool_acquire_response.py
+++ b/src/kernel/types/browser_pool_acquire_response.py
@@ -35,6 +35,9 @@ class BrowserPoolAcquireResponse(BaseModel):
webdriver_ws_url: str
"""Websocket URL for WebDriver BiDi connections to the browser session"""
+ base_url: Optional[str] = None
+ """Metro-API HTTP base URL for this browser session."""
+
browser_live_view_url: Optional[str] = None
"""Remote URL for live viewing the browser session.
diff --git a/src/kernel/types/browser_retrieve_response.py b/src/kernel/types/browser_retrieve_response.py
index 221eab5..5b5a891 100644
--- a/src/kernel/types/browser_retrieve_response.py
+++ b/src/kernel/types/browser_retrieve_response.py
@@ -35,6 +35,9 @@ class BrowserRetrieveResponse(BaseModel):
webdriver_ws_url: str
"""Websocket URL for WebDriver BiDi connections to the browser session"""
+ base_url: Optional[str] = None
+ """Metro-API HTTP base URL for this browser session."""
+
browser_live_view_url: Optional[str] = None
"""Remote URL for live viewing the browser session.
diff --git a/src/kernel/types/browser_update_response.py b/src/kernel/types/browser_update_response.py
index c8a85c3..188895a 100644
--- a/src/kernel/types/browser_update_response.py
+++ b/src/kernel/types/browser_update_response.py
@@ -35,6 +35,9 @@ class BrowserUpdateResponse(BaseModel):
webdriver_ws_url: str
"""Websocket URL for WebDriver BiDi connections to the browser session"""
+ base_url: Optional[str] = None
+ """Metro-API HTTP base URL for this browser session."""
+
browser_live_view_url: Optional[str] = None
"""Remote URL for live viewing the browser session.
diff --git a/src/kernel/types/invocation_list_browsers_response.py b/src/kernel/types/invocation_list_browsers_response.py
index a0fed9a..23eda77 100644
--- a/src/kernel/types/invocation_list_browsers_response.py
+++ b/src/kernel/types/invocation_list_browsers_response.py
@@ -35,6 +35,9 @@ class Browser(BaseModel):
webdriver_ws_url: str
"""Websocket URL for WebDriver BiDi connections to the browser session"""
+ base_url: Optional[str] = None
+ """Metro-API HTTP base URL for this browser session."""
+
browser_live_view_url: Optional[str] = None
"""Remote URL for live viewing the browser session.
From 48ce3f06ed11e124b643776f8afbe72882e0c62c Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 8 Apr 2026 20:48:21 +0000
Subject: [PATCH 3/5] codegen metadata
---
.stats.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index b81754a..d184bbd 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 104
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-aee09720882ec1d78f845fee6ceecb0466c264629e4edecd3230406dd06d7983.yml
-openapi_spec_hash: 438da0d38d169897595f301d82fa7e2c
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-49a1a92e00d1eb87e91e8527275cb0705fce2edea30e70fea745f134dd451fbd.yml
+openapi_spec_hash: 3aa6ab6939790f538332054162fbdedc
config_hash: 16e4457a0bb26e98a335a1c2a572290a
From ca22fd9dc4b5f4cd70e15ecb1ddd3607d8a99df8 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 10 Apr 2026 02:14:32 +0000
Subject: [PATCH 4/5] chore: retrigger Stainless codegen for projects resource
---
.stats.yml | 4 +-
api.md | 29 +
src/kernel/_client.py | 44 ++
src/kernel/resources/__init__.py | 14 +
src/kernel/resources/projects/__init__.py | 33 +
src/kernel/resources/projects/limits.py | 309 +++++++++
src/kernel/resources/projects/projects.py | 586 ++++++++++++++++++
src/kernel/types/__init__.py | 4 +
src/kernel/types/project.py | 25 +
src/kernel/types/project_create_params.py | 12 +
src/kernel/types/project_list_params.py | 15 +
src/kernel/types/project_update_params.py | 15 +
src/kernel/types/projects/__init__.py | 6 +
.../types/projects/limit_update_params.py | 34 +
src/kernel/types/projects/project_limits.py | 33 +
tests/api_resources/projects/__init__.py | 1 +
tests/api_resources/projects/test_limits.py | 216 +++++++
tests/api_resources/test_projects.py | 439 +++++++++++++
18 files changed, 1817 insertions(+), 2 deletions(-)
create mode 100644 src/kernel/resources/projects/__init__.py
create mode 100644 src/kernel/resources/projects/limits.py
create mode 100644 src/kernel/resources/projects/projects.py
create mode 100644 src/kernel/types/project.py
create mode 100644 src/kernel/types/project_create_params.py
create mode 100644 src/kernel/types/project_list_params.py
create mode 100644 src/kernel/types/project_update_params.py
create mode 100644 src/kernel/types/projects/__init__.py
create mode 100644 src/kernel/types/projects/limit_update_params.py
create mode 100644 src/kernel/types/projects/project_limits.py
create mode 100644 tests/api_resources/projects/__init__.py
create mode 100644 tests/api_resources/projects/test_limits.py
create mode 100644 tests/api_resources/test_projects.py
diff --git a/.stats.yml b/.stats.yml
index d184bbd..db7b03c 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 104
+configured_endpoints: 111
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-49a1a92e00d1eb87e91e8527275cb0705fce2edea30e70fea745f134dd451fbd.yml
openapi_spec_hash: 3aa6ab6939790f538332054162fbdedc
-config_hash: 16e4457a0bb26e98a335a1c2a572290a
+config_hash: 9818dd634f87b677410eefd013d7a179
diff --git a/api.md b/api.md
index 5e43066..96c90c4 100644
--- a/api.md
+++ b/api.md
@@ -341,6 +341,35 @@ Methods:
- client.credentials.delete(id_or_name) -> None
- client.credentials.totp_code(id_or_name) -> CredentialTotpCodeResponse
+# Projects
+
+Types:
+
+```python
+from kernel.types import CreateProjectRequest, Project, UpdateProjectRequest
+```
+
+Methods:
+
+- client.projects.create(\*\*params) -> Project
+- client.projects.retrieve(id) -> Project
+- client.projects.update(id, \*\*params) -> Project
+- client.projects.list(\*\*params) -> SyncOffsetPagination[Project]
+- client.projects.delete(id) -> None
+
+## Limits
+
+Types:
+
+```python
+from kernel.types.projects import ProjectLimits, UpdateProjectLimitsRequest
+```
+
+Methods:
+
+- client.projects.limits.retrieve(id) -> ProjectLimits
+- client.projects.limits.update(id, \*\*params) -> ProjectLimits
+
# CredentialProviders
Types:
diff --git a/src/kernel/_client.py b/src/kernel/_client.py
index 84fc48d..75fe4b6 100644
--- a/src/kernel/_client.py
+++ b/src/kernel/_client.py
@@ -37,6 +37,7 @@
proxies,
browsers,
profiles,
+ projects,
extensions,
credentials,
deployments,
@@ -54,6 +55,7 @@
from .resources.invocations import InvocationsResource, AsyncInvocationsResource
from .resources.browser_pools import BrowserPoolsResource, AsyncBrowserPoolsResource
from .resources.browsers.browsers import BrowsersResource, AsyncBrowsersResource
+ from .resources.projects.projects import ProjectsResource, AsyncProjectsResource
from .resources.credential_providers import CredentialProvidersResource, AsyncCredentialProvidersResource
__all__ = [
@@ -222,6 +224,13 @@ def credentials(self) -> CredentialsResource:
return CredentialsResource(self)
+ @cached_property
+ def projects(self) -> ProjectsResource:
+ """Create and manage projects for resource isolation within an organization."""
+ from .resources.projects import ProjectsResource
+
+ return ProjectsResource(self)
+
@cached_property
def credential_providers(self) -> CredentialProvidersResource:
"""Configure external credential providers like 1Password."""
@@ -492,6 +501,13 @@ def credentials(self) -> AsyncCredentialsResource:
return AsyncCredentialsResource(self)
+ @cached_property
+ def projects(self) -> AsyncProjectsResource:
+ """Create and manage projects for resource isolation within an organization."""
+ from .resources.projects import AsyncProjectsResource
+
+ return AsyncProjectsResource(self)
+
@cached_property
def credential_providers(self) -> AsyncCredentialProvidersResource:
"""Configure external credential providers like 1Password."""
@@ -689,6 +705,13 @@ def credentials(self) -> credentials.CredentialsResourceWithRawResponse:
return CredentialsResourceWithRawResponse(self._client.credentials)
+ @cached_property
+ def projects(self) -> projects.ProjectsResourceWithRawResponse:
+ """Create and manage projects for resource isolation within an organization."""
+ from .resources.projects import ProjectsResourceWithRawResponse
+
+ return ProjectsResourceWithRawResponse(self._client.projects)
+
@cached_property
def credential_providers(self) -> credential_providers.CredentialProvidersResourceWithRawResponse:
"""Configure external credential providers like 1Password."""
@@ -772,6 +795,13 @@ def credentials(self) -> credentials.AsyncCredentialsResourceWithRawResponse:
return AsyncCredentialsResourceWithRawResponse(self._client.credentials)
+ @cached_property
+ def projects(self) -> projects.AsyncProjectsResourceWithRawResponse:
+ """Create and manage projects for resource isolation within an organization."""
+ from .resources.projects import AsyncProjectsResourceWithRawResponse
+
+ return AsyncProjectsResourceWithRawResponse(self._client.projects)
+
@cached_property
def credential_providers(self) -> credential_providers.AsyncCredentialProvidersResourceWithRawResponse:
"""Configure external credential providers like 1Password."""
@@ -855,6 +885,13 @@ def credentials(self) -> credentials.CredentialsResourceWithStreamingResponse:
return CredentialsResourceWithStreamingResponse(self._client.credentials)
+ @cached_property
+ def projects(self) -> projects.ProjectsResourceWithStreamingResponse:
+ """Create and manage projects for resource isolation within an organization."""
+ from .resources.projects import ProjectsResourceWithStreamingResponse
+
+ return ProjectsResourceWithStreamingResponse(self._client.projects)
+
@cached_property
def credential_providers(self) -> credential_providers.CredentialProvidersResourceWithStreamingResponse:
"""Configure external credential providers like 1Password."""
@@ -938,6 +975,13 @@ def credentials(self) -> credentials.AsyncCredentialsResourceWithStreamingRespon
return AsyncCredentialsResourceWithStreamingResponse(self._client.credentials)
+ @cached_property
+ def projects(self) -> projects.AsyncProjectsResourceWithStreamingResponse:
+ """Create and manage projects for resource isolation within an organization."""
+ from .resources.projects import AsyncProjectsResourceWithStreamingResponse
+
+ return AsyncProjectsResourceWithStreamingResponse(self._client.projects)
+
@cached_property
def credential_providers(self) -> credential_providers.AsyncCredentialProvidersResourceWithStreamingResponse:
"""Configure external credential providers like 1Password."""
diff --git a/src/kernel/resources/__init__.py b/src/kernel/resources/__init__.py
index 4896e79..f078a03 100644
--- a/src/kernel/resources/__init__.py
+++ b/src/kernel/resources/__init__.py
@@ -40,6 +40,14 @@
ProfilesResourceWithStreamingResponse,
AsyncProfilesResourceWithStreamingResponse,
)
+from .projects import (
+ ProjectsResource,
+ AsyncProjectsResource,
+ ProjectsResourceWithRawResponse,
+ AsyncProjectsResourceWithRawResponse,
+ ProjectsResourceWithStreamingResponse,
+ AsyncProjectsResourceWithStreamingResponse,
+)
from .extensions import (
ExtensionsResource,
AsyncExtensionsResource,
@@ -150,6 +158,12 @@
"AsyncCredentialsResourceWithRawResponse",
"CredentialsResourceWithStreamingResponse",
"AsyncCredentialsResourceWithStreamingResponse",
+ "ProjectsResource",
+ "AsyncProjectsResource",
+ "ProjectsResourceWithRawResponse",
+ "AsyncProjectsResourceWithRawResponse",
+ "ProjectsResourceWithStreamingResponse",
+ "AsyncProjectsResourceWithStreamingResponse",
"CredentialProvidersResource",
"AsyncCredentialProvidersResource",
"CredentialProvidersResourceWithRawResponse",
diff --git a/src/kernel/resources/projects/__init__.py b/src/kernel/resources/projects/__init__.py
new file mode 100644
index 0000000..4126389
--- /dev/null
+++ b/src/kernel/resources/projects/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .limits import (
+ LimitsResource,
+ AsyncLimitsResource,
+ LimitsResourceWithRawResponse,
+ AsyncLimitsResourceWithRawResponse,
+ LimitsResourceWithStreamingResponse,
+ AsyncLimitsResourceWithStreamingResponse,
+)
+from .projects import (
+ ProjectsResource,
+ AsyncProjectsResource,
+ ProjectsResourceWithRawResponse,
+ AsyncProjectsResourceWithRawResponse,
+ ProjectsResourceWithStreamingResponse,
+ AsyncProjectsResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "LimitsResource",
+ "AsyncLimitsResource",
+ "LimitsResourceWithRawResponse",
+ "AsyncLimitsResourceWithRawResponse",
+ "LimitsResourceWithStreamingResponse",
+ "AsyncLimitsResourceWithStreamingResponse",
+ "ProjectsResource",
+ "AsyncProjectsResource",
+ "ProjectsResourceWithRawResponse",
+ "AsyncProjectsResourceWithRawResponse",
+ "ProjectsResourceWithStreamingResponse",
+ "AsyncProjectsResourceWithStreamingResponse",
+]
diff --git a/src/kernel/resources/projects/limits.py b/src/kernel/resources/projects/limits.py
new file mode 100644
index 0000000..eeff593
--- /dev/null
+++ b/src/kernel/resources/projects/limits.py
@@ -0,0 +1,309 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+
+import httpx
+
+from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from ..._utils import path_template, maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.projects import limit_update_params
+from ...types.projects.project_limits import ProjectLimits
+
+__all__ = ["LimitsResource", "AsyncLimitsResource"]
+
+
+class LimitsResource(SyncAPIResource):
+ """Create and manage projects for resource isolation within an organization."""
+
+ @cached_property
+ def with_raw_response(self) -> LimitsResourceWithRawResponse:
+ """
+ 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/kernel/kernel-python-sdk#accessing-raw-response-data-eg-headers
+ """
+ return LimitsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> LimitsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/kernel/kernel-python-sdk#with_streaming_response
+ """
+ return LimitsResourceWithStreamingResponse(self)
+
+ def retrieve(
+ self,
+ id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ProjectLimits:
+ """Get the resource limit overrides for a project.
+
+ Null values mean no
+ project-level cap (org limit applies).
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return self._get(
+ path_template("/projects/{id}/limits", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ProjectLimits,
+ )
+
+ def update(
+ self,
+ id: str,
+ *,
+ max_concurrent_invocations: Optional[int] | Omit = omit,
+ max_concurrent_sessions: Optional[int] | Omit = omit,
+ max_persistent_sessions: Optional[int] | Omit = omit,
+ max_pooled_sessions: Optional[int] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ProjectLimits:
+ """Update resource limit overrides for a project.
+
+ Only fields present in the
+ request are modified. Set a field to 0 to remove that limit cap; omit a field to
+ leave it unchanged.
+
+ Args:
+ max_concurrent_invocations: Maximum concurrent app invocations for this project. Set to 0 to remove the cap;
+ omit to leave unchanged.
+
+ max_concurrent_sessions: Maximum concurrent browser sessions for this project. Set to 0 to remove the
+ cap; omit to leave unchanged.
+
+ max_persistent_sessions: Maximum persistent browser sessions for this project. Set to 0 to remove the
+ cap; omit to leave unchanged.
+
+ max_pooled_sessions: Maximum pooled sessions capacity for this project. Set to 0 to remove the cap;
+ omit to leave unchanged.
+
+ 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._patch(
+ path_template("/projects/{id}/limits", id=id),
+ body=maybe_transform(
+ {
+ "max_concurrent_invocations": max_concurrent_invocations,
+ "max_concurrent_sessions": max_concurrent_sessions,
+ "max_persistent_sessions": max_persistent_sessions,
+ "max_pooled_sessions": max_pooled_sessions,
+ },
+ limit_update_params.LimitUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ProjectLimits,
+ )
+
+
+class AsyncLimitsResource(AsyncAPIResource):
+ """Create and manage projects for resource isolation within an organization."""
+
+ @cached_property
+ def with_raw_response(self) -> AsyncLimitsResourceWithRawResponse:
+ """
+ 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/kernel/kernel-python-sdk#accessing-raw-response-data-eg-headers
+ """
+ return AsyncLimitsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncLimitsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/kernel/kernel-python-sdk#with_streaming_response
+ """
+ return AsyncLimitsResourceWithStreamingResponse(self)
+
+ async def retrieve(
+ self,
+ id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ProjectLimits:
+ """Get the resource limit overrides for a project.
+
+ Null values mean no
+ project-level cap (org limit applies).
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return await self._get(
+ path_template("/projects/{id}/limits", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ProjectLimits,
+ )
+
+ async def update(
+ self,
+ id: str,
+ *,
+ max_concurrent_invocations: Optional[int] | Omit = omit,
+ max_concurrent_sessions: Optional[int] | Omit = omit,
+ max_persistent_sessions: Optional[int] | Omit = omit,
+ max_pooled_sessions: Optional[int] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ProjectLimits:
+ """Update resource limit overrides for a project.
+
+ Only fields present in the
+ request are modified. Set a field to 0 to remove that limit cap; omit a field to
+ leave it unchanged.
+
+ Args:
+ max_concurrent_invocations: Maximum concurrent app invocations for this project. Set to 0 to remove the cap;
+ omit to leave unchanged.
+
+ max_concurrent_sessions: Maximum concurrent browser sessions for this project. Set to 0 to remove the
+ cap; omit to leave unchanged.
+
+ max_persistent_sessions: Maximum persistent browser sessions for this project. Set to 0 to remove the
+ cap; omit to leave unchanged.
+
+ max_pooled_sessions: Maximum pooled sessions capacity for this project. Set to 0 to remove the cap;
+ omit to leave unchanged.
+
+ 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._patch(
+ path_template("/projects/{id}/limits", id=id),
+ body=await async_maybe_transform(
+ {
+ "max_concurrent_invocations": max_concurrent_invocations,
+ "max_concurrent_sessions": max_concurrent_sessions,
+ "max_persistent_sessions": max_persistent_sessions,
+ "max_pooled_sessions": max_pooled_sessions,
+ },
+ limit_update_params.LimitUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ProjectLimits,
+ )
+
+
+class LimitsResourceWithRawResponse:
+ def __init__(self, limits: LimitsResource) -> None:
+ self._limits = limits
+
+ self.retrieve = to_raw_response_wrapper(
+ limits.retrieve,
+ )
+ self.update = to_raw_response_wrapper(
+ limits.update,
+ )
+
+
+class AsyncLimitsResourceWithRawResponse:
+ def __init__(self, limits: AsyncLimitsResource) -> None:
+ self._limits = limits
+
+ self.retrieve = async_to_raw_response_wrapper(
+ limits.retrieve,
+ )
+ self.update = async_to_raw_response_wrapper(
+ limits.update,
+ )
+
+
+class LimitsResourceWithStreamingResponse:
+ def __init__(self, limits: LimitsResource) -> None:
+ self._limits = limits
+
+ self.retrieve = to_streamed_response_wrapper(
+ limits.retrieve,
+ )
+ self.update = to_streamed_response_wrapper(
+ limits.update,
+ )
+
+
+class AsyncLimitsResourceWithStreamingResponse:
+ def __init__(self, limits: AsyncLimitsResource) -> None:
+ self._limits = limits
+
+ self.retrieve = async_to_streamed_response_wrapper(
+ limits.retrieve,
+ )
+ self.update = async_to_streamed_response_wrapper(
+ limits.update,
+ )
diff --git a/src/kernel/resources/projects/projects.py b/src/kernel/resources/projects/projects.py
new file mode 100644
index 0000000..b40dc02
--- /dev/null
+++ b/src/kernel/resources/projects/projects.py
@@ -0,0 +1,586 @@
+# 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 .limits import (
+ LimitsResource,
+ AsyncLimitsResource,
+ LimitsResourceWithRawResponse,
+ AsyncLimitsResourceWithRawResponse,
+ LimitsResourceWithStreamingResponse,
+ AsyncLimitsResourceWithStreamingResponse,
+)
+from ...types import project_list_params, project_create_params, project_update_params
+from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
+from ..._utils import path_template, maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ...pagination import SyncOffsetPagination, AsyncOffsetPagination
+from ..._base_client import AsyncPaginator, make_request_options
+from ...types.project import Project
+
+__all__ = ["ProjectsResource", "AsyncProjectsResource"]
+
+
+class ProjectsResource(SyncAPIResource):
+ """Create and manage projects for resource isolation within an organization."""
+
+ @cached_property
+ def limits(self) -> LimitsResource:
+ """Create and manage projects for resource isolation within an organization."""
+ return LimitsResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> ProjectsResourceWithRawResponse:
+ """
+ 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/kernel/kernel-python-sdk#accessing-raw-response-data-eg-headers
+ """
+ return ProjectsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> ProjectsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/kernel/kernel-python-sdk#with_streaming_response
+ """
+ return ProjectsResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ *,
+ name: 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,
+ ) -> Project:
+ """Create a new project within the authenticated organization.
+
+ Requires a paid plan
+ and the projects feature flag.
+
+ Args:
+ name: Project name (1-255 characters)
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._post(
+ "/projects",
+ body=maybe_transform({"name": name}, project_create_params.ProjectCreateParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Project,
+ )
+
+ def retrieve(
+ self,
+ id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Project:
+ """
+ Get a project by ID.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return self._get(
+ path_template("/projects/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Project,
+ )
+
+ def update(
+ self,
+ id: str,
+ *,
+ name: str | Omit = omit,
+ status: Literal["active", "archived"] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Project:
+ """
+ Update a project's name or status.
+
+ Args:
+ name: New project name
+
+ status: New project status
+
+ 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._patch(
+ path_template("/projects/{id}", id=id),
+ body=maybe_transform(
+ {
+ "name": name,
+ "status": status,
+ },
+ project_update_params.ProjectUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Project,
+ )
+
+ def list(
+ self,
+ *,
+ limit: int | Omit = omit,
+ offset: int | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SyncOffsetPagination[Project]:
+ """
+ List projects for the authenticated organization.
+
+ Args:
+ limit: Maximum number of results to return
+
+ offset: Number of results to skip
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get_api_list(
+ "/projects",
+ page=SyncOffsetPagination[Project],
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "limit": limit,
+ "offset": offset,
+ },
+ project_list_params.ProjectListParams,
+ ),
+ ),
+ model=Project,
+ )
+
+ def delete(
+ self,
+ id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """Soft-delete a project.
+
+ The project must be empty (no active resources).
+
+ 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 {})}
+ return self._delete(
+ path_template("/projects/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+
+class AsyncProjectsResource(AsyncAPIResource):
+ """Create and manage projects for resource isolation within an organization."""
+
+ @cached_property
+ def limits(self) -> AsyncLimitsResource:
+ """Create and manage projects for resource isolation within an organization."""
+ return AsyncLimitsResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncProjectsResourceWithRawResponse:
+ """
+ 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/kernel/kernel-python-sdk#accessing-raw-response-data-eg-headers
+ """
+ return AsyncProjectsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncProjectsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/kernel/kernel-python-sdk#with_streaming_response
+ """
+ return AsyncProjectsResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ *,
+ name: 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,
+ ) -> Project:
+ """Create a new project within the authenticated organization.
+
+ Requires a paid plan
+ and the projects feature flag.
+
+ Args:
+ name: Project name (1-255 characters)
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._post(
+ "/projects",
+ body=await async_maybe_transform({"name": name}, project_create_params.ProjectCreateParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Project,
+ )
+
+ async def retrieve(
+ self,
+ id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Project:
+ """
+ Get a project by ID.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return await self._get(
+ path_template("/projects/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Project,
+ )
+
+ async def update(
+ self,
+ id: str,
+ *,
+ name: str | Omit = omit,
+ status: Literal["active", "archived"] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Project:
+ """
+ Update a project's name or status.
+
+ Args:
+ name: New project name
+
+ status: New project status
+
+ 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._patch(
+ path_template("/projects/{id}", id=id),
+ body=await async_maybe_transform(
+ {
+ "name": name,
+ "status": status,
+ },
+ project_update_params.ProjectUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Project,
+ )
+
+ def list(
+ self,
+ *,
+ limit: int | Omit = omit,
+ offset: int | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> AsyncPaginator[Project, AsyncOffsetPagination[Project]]:
+ """
+ List projects for the authenticated organization.
+
+ Args:
+ limit: Maximum number of results to return
+
+ offset: Number of results to skip
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get_api_list(
+ "/projects",
+ page=AsyncOffsetPagination[Project],
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "limit": limit,
+ "offset": offset,
+ },
+ project_list_params.ProjectListParams,
+ ),
+ ),
+ model=Project,
+ )
+
+ async def delete(
+ self,
+ id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """Soft-delete a project.
+
+ The project must be empty (no active resources).
+
+ 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 {})}
+ return await self._delete(
+ path_template("/projects/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+
+class ProjectsResourceWithRawResponse:
+ def __init__(self, projects: ProjectsResource) -> None:
+ self._projects = projects
+
+ self.create = to_raw_response_wrapper(
+ projects.create,
+ )
+ self.retrieve = to_raw_response_wrapper(
+ projects.retrieve,
+ )
+ self.update = to_raw_response_wrapper(
+ projects.update,
+ )
+ self.list = to_raw_response_wrapper(
+ projects.list,
+ )
+ self.delete = to_raw_response_wrapper(
+ projects.delete,
+ )
+
+ @cached_property
+ def limits(self) -> LimitsResourceWithRawResponse:
+ """Create and manage projects for resource isolation within an organization."""
+ return LimitsResourceWithRawResponse(self._projects.limits)
+
+
+class AsyncProjectsResourceWithRawResponse:
+ def __init__(self, projects: AsyncProjectsResource) -> None:
+ self._projects = projects
+
+ self.create = async_to_raw_response_wrapper(
+ projects.create,
+ )
+ self.retrieve = async_to_raw_response_wrapper(
+ projects.retrieve,
+ )
+ self.update = async_to_raw_response_wrapper(
+ projects.update,
+ )
+ self.list = async_to_raw_response_wrapper(
+ projects.list,
+ )
+ self.delete = async_to_raw_response_wrapper(
+ projects.delete,
+ )
+
+ @cached_property
+ def limits(self) -> AsyncLimitsResourceWithRawResponse:
+ """Create and manage projects for resource isolation within an organization."""
+ return AsyncLimitsResourceWithRawResponse(self._projects.limits)
+
+
+class ProjectsResourceWithStreamingResponse:
+ def __init__(self, projects: ProjectsResource) -> None:
+ self._projects = projects
+
+ self.create = to_streamed_response_wrapper(
+ projects.create,
+ )
+ self.retrieve = to_streamed_response_wrapper(
+ projects.retrieve,
+ )
+ self.update = to_streamed_response_wrapper(
+ projects.update,
+ )
+ self.list = to_streamed_response_wrapper(
+ projects.list,
+ )
+ self.delete = to_streamed_response_wrapper(
+ projects.delete,
+ )
+
+ @cached_property
+ def limits(self) -> LimitsResourceWithStreamingResponse:
+ """Create and manage projects for resource isolation within an organization."""
+ return LimitsResourceWithStreamingResponse(self._projects.limits)
+
+
+class AsyncProjectsResourceWithStreamingResponse:
+ def __init__(self, projects: AsyncProjectsResource) -> None:
+ self._projects = projects
+
+ self.create = async_to_streamed_response_wrapper(
+ projects.create,
+ )
+ self.retrieve = async_to_streamed_response_wrapper(
+ projects.retrieve,
+ )
+ self.update = async_to_streamed_response_wrapper(
+ projects.update,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ projects.list,
+ )
+ self.delete = async_to_streamed_response_wrapper(
+ projects.delete,
+ )
+
+ @cached_property
+ def limits(self) -> AsyncLimitsResourceWithStreamingResponse:
+ """Create and manage projects for resource isolation within an organization."""
+ return AsyncLimitsResourceWithStreamingResponse(self._projects.limits)
diff --git a/src/kernel/types/__init__.py b/src/kernel/types/__init__.py
index 0e740fb..91e68d6 100644
--- a/src/kernel/types/__init__.py
+++ b/src/kernel/types/__init__.py
@@ -14,6 +14,7 @@
BrowserExtension as BrowserExtension,
)
from .profile import Profile as Profile
+from .project import Project as Project
from .credential import Credential as Credential
from .browser_pool import BrowserPool as BrowserPool
from .browser_usage import BrowserUsage as BrowserUsage
@@ -25,6 +26,7 @@
from .browser_persistence import BrowserPersistence as BrowserPersistence
from .credential_provider import CredentialProvider as CredentialProvider
from .profile_list_params import ProfileListParams as ProfileListParams
+from .project_list_params import ProjectListParams as ProjectListParams
from .proxy_create_params import ProxyCreateParams as ProxyCreateParams
from .proxy_list_response import ProxyListResponse as ProxyListResponse
from .proxy_check_response import ProxyCheckResponse as ProxyCheckResponse
@@ -33,6 +35,8 @@
from .browser_list_response import BrowserListResponse as BrowserListResponse
from .browser_update_params import BrowserUpdateParams as BrowserUpdateParams
from .profile_create_params import ProfileCreateParams as ProfileCreateParams
+from .project_create_params import ProjectCreateParams as ProjectCreateParams
+from .project_update_params import ProjectUpdateParams as ProjectUpdateParams
from .proxy_create_response import ProxyCreateResponse as ProxyCreateResponse
from .credential_list_params import CredentialListParams as CredentialListParams
from .deployment_list_params import DeploymentListParams as DeploymentListParams
diff --git a/src/kernel/types/project.py b/src/kernel/types/project.py
new file mode 100644
index 0000000..db2a377
--- /dev/null
+++ b/src/kernel/types/project.py
@@ -0,0 +1,25 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from datetime import datetime
+from typing_extensions import Literal
+
+from .._models import BaseModel
+
+__all__ = ["Project"]
+
+
+class Project(BaseModel):
+ id: str
+ """Unique project identifier"""
+
+ created_at: datetime
+ """When the project was created"""
+
+ name: str
+ """Project name"""
+
+ status: Literal["active", "archived"]
+ """Project status"""
+
+ updated_at: datetime
+ """When the project was last updated"""
diff --git a/src/kernel/types/project_create_params.py b/src/kernel/types/project_create_params.py
new file mode 100644
index 0000000..99a5986
--- /dev/null
+++ b/src/kernel/types/project_create_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__ = ["ProjectCreateParams"]
+
+
+class ProjectCreateParams(TypedDict, total=False):
+ name: Required[str]
+ """Project name (1-255 characters)"""
diff --git a/src/kernel/types/project_list_params.py b/src/kernel/types/project_list_params.py
new file mode 100644
index 0000000..ea10f07
--- /dev/null
+++ b/src/kernel/types/project_list_params.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["ProjectListParams"]
+
+
+class ProjectListParams(TypedDict, total=False):
+ limit: int
+ """Maximum number of results to return"""
+
+ offset: int
+ """Number of results to skip"""
diff --git a/src/kernel/types/project_update_params.py b/src/kernel/types/project_update_params.py
new file mode 100644
index 0000000..ea7de90
--- /dev/null
+++ b/src/kernel/types/project_update_params.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, TypedDict
+
+__all__ = ["ProjectUpdateParams"]
+
+
+class ProjectUpdateParams(TypedDict, total=False):
+ name: str
+ """New project name"""
+
+ status: Literal["active", "archived"]
+ """New project status"""
diff --git a/src/kernel/types/projects/__init__.py b/src/kernel/types/projects/__init__.py
new file mode 100644
index 0000000..acf030c
--- /dev/null
+++ b/src/kernel/types/projects/__init__.py
@@ -0,0 +1,6 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .project_limits import ProjectLimits as ProjectLimits
+from .limit_update_params import LimitUpdateParams as LimitUpdateParams
diff --git a/src/kernel/types/projects/limit_update_params.py b/src/kernel/types/projects/limit_update_params.py
new file mode 100644
index 0000000..6f0ec8a
--- /dev/null
+++ b/src/kernel/types/projects/limit_update_params.py
@@ -0,0 +1,34 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import TypedDict
+
+__all__ = ["LimitUpdateParams"]
+
+
+class LimitUpdateParams(TypedDict, total=False):
+ max_concurrent_invocations: Optional[int]
+ """Maximum concurrent app invocations for this project.
+
+ Set to 0 to remove the cap; omit to leave unchanged.
+ """
+
+ max_concurrent_sessions: Optional[int]
+ """Maximum concurrent browser sessions for this project.
+
+ Set to 0 to remove the cap; omit to leave unchanged.
+ """
+
+ max_persistent_sessions: Optional[int]
+ """Maximum persistent browser sessions for this project.
+
+ Set to 0 to remove the cap; omit to leave unchanged.
+ """
+
+ max_pooled_sessions: Optional[int]
+ """Maximum pooled sessions capacity for this project.
+
+ Set to 0 to remove the cap; omit to leave unchanged.
+ """
diff --git a/src/kernel/types/projects/project_limits.py b/src/kernel/types/projects/project_limits.py
new file mode 100644
index 0000000..bf49b2a
--- /dev/null
+++ b/src/kernel/types/projects/project_limits.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ..._models import BaseModel
+
+__all__ = ["ProjectLimits"]
+
+
+class ProjectLimits(BaseModel):
+ max_concurrent_invocations: Optional[int] = None
+ """Maximum concurrent app invocations for this project.
+
+ Null means no project-level cap.
+ """
+
+ max_concurrent_sessions: Optional[int] = None
+ """Maximum concurrent browser sessions for this project.
+
+ Null means no project-level cap.
+ """
+
+ max_persistent_sessions: Optional[int] = None
+ """Maximum persistent browser sessions for this project.
+
+ Null means no project-level cap.
+ """
+
+ max_pooled_sessions: Optional[int] = None
+ """Maximum pooled sessions capacity for this project.
+
+ Null means no project-level cap.
+ """
diff --git a/tests/api_resources/projects/__init__.py b/tests/api_resources/projects/__init__.py
new file mode 100644
index 0000000..fd8019a
--- /dev/null
+++ b/tests/api_resources/projects/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/projects/test_limits.py b/tests/api_resources/projects/test_limits.py
new file mode 100644
index 0000000..9df6a0d
--- /dev/null
+++ b/tests/api_resources/projects/test_limits.py
@@ -0,0 +1,216 @@
+# 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.projects import ProjectLimits
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestLimits:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_retrieve(self, client: Kernel) -> None:
+ limit = client.projects.limits.retrieve(
+ "id",
+ )
+ assert_matches_type(ProjectLimits, limit, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_retrieve(self, client: Kernel) -> None:
+ response = client.projects.limits.with_raw_response.retrieve(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ limit = response.parse()
+ assert_matches_type(ProjectLimits, limit, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_retrieve(self, client: Kernel) -> None:
+ with client.projects.limits.with_streaming_response.retrieve(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ limit = response.parse()
+ assert_matches_type(ProjectLimits, limit, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_retrieve(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.projects.limits.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_update(self, client: Kernel) -> None:
+ limit = client.projects.limits.update(
+ id="id",
+ )
+ assert_matches_type(ProjectLimits, limit, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_update_with_all_params(self, client: Kernel) -> None:
+ limit = client.projects.limits.update(
+ id="id",
+ max_concurrent_invocations=0,
+ max_concurrent_sessions=0,
+ max_persistent_sessions=0,
+ max_pooled_sessions=0,
+ )
+ assert_matches_type(ProjectLimits, limit, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_update(self, client: Kernel) -> None:
+ response = client.projects.limits.with_raw_response.update(
+ id="id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ limit = response.parse()
+ assert_matches_type(ProjectLimits, limit, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_update(self, client: Kernel) -> None:
+ with client.projects.limits.with_streaming_response.update(
+ id="id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ limit = response.parse()
+ assert_matches_type(ProjectLimits, limit, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_update(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.projects.limits.with_raw_response.update(
+ id="",
+ )
+
+
+class TestAsyncLimits:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_retrieve(self, async_client: AsyncKernel) -> None:
+ limit = await async_client.projects.limits.retrieve(
+ "id",
+ )
+ assert_matches_type(ProjectLimits, limit, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_retrieve(self, async_client: AsyncKernel) -> None:
+ response = await async_client.projects.limits.with_raw_response.retrieve(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ limit = await response.parse()
+ assert_matches_type(ProjectLimits, limit, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_retrieve(self, async_client: AsyncKernel) -> None:
+ async with async_client.projects.limits.with_streaming_response.retrieve(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ limit = await response.parse()
+ assert_matches_type(ProjectLimits, limit, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_retrieve(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.projects.limits.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_update(self, async_client: AsyncKernel) -> None:
+ limit = await async_client.projects.limits.update(
+ id="id",
+ )
+ assert_matches_type(ProjectLimits, limit, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_update_with_all_params(self, async_client: AsyncKernel) -> None:
+ limit = await async_client.projects.limits.update(
+ id="id",
+ max_concurrent_invocations=0,
+ max_concurrent_sessions=0,
+ max_persistent_sessions=0,
+ max_pooled_sessions=0,
+ )
+ assert_matches_type(ProjectLimits, limit, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_update(self, async_client: AsyncKernel) -> None:
+ response = await async_client.projects.limits.with_raw_response.update(
+ id="id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ limit = await response.parse()
+ assert_matches_type(ProjectLimits, limit, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_update(self, async_client: AsyncKernel) -> None:
+ async with async_client.projects.limits.with_streaming_response.update(
+ id="id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ limit = await response.parse()
+ assert_matches_type(ProjectLimits, limit, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_update(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.projects.limits.with_raw_response.update(
+ id="",
+ )
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
new file mode 100644
index 0000000..488c191
--- /dev/null
+++ b/tests/api_resources/test_projects.py
@@ -0,0 +1,439 @@
+# 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 import Project
+from kernel.pagination import SyncOffsetPagination, AsyncOffsetPagination
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestProjects:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_create(self, client: Kernel) -> None:
+ project = client.projects.create(
+ name="staging",
+ )
+ assert_matches_type(Project, project, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_create(self, client: Kernel) -> None:
+ response = client.projects.with_raw_response.create(
+ name="staging",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ project = response.parse()
+ assert_matches_type(Project, project, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_create(self, client: Kernel) -> None:
+ with client.projects.with_streaming_response.create(
+ name="staging",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ project = response.parse()
+ assert_matches_type(Project, project, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_retrieve(self, client: Kernel) -> None:
+ project = client.projects.retrieve(
+ "id",
+ )
+ assert_matches_type(Project, project, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_retrieve(self, client: Kernel) -> None:
+ response = client.projects.with_raw_response.retrieve(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ project = response.parse()
+ assert_matches_type(Project, project, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_retrieve(self, client: Kernel) -> None:
+ with client.projects.with_streaming_response.retrieve(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ project = response.parse()
+ assert_matches_type(Project, project, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_retrieve(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.projects.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_update(self, client: Kernel) -> None:
+ project = client.projects.update(
+ id="id",
+ )
+ assert_matches_type(Project, project, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_update_with_all_params(self, client: Kernel) -> None:
+ project = client.projects.update(
+ id="id",
+ name="name",
+ status="active",
+ )
+ assert_matches_type(Project, project, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_update(self, client: Kernel) -> None:
+ response = client.projects.with_raw_response.update(
+ id="id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ project = response.parse()
+ assert_matches_type(Project, project, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_update(self, client: Kernel) -> None:
+ with client.projects.with_streaming_response.update(
+ id="id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ project = response.parse()
+ assert_matches_type(Project, project, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_update(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.projects.with_raw_response.update(
+ id="",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list(self, client: Kernel) -> None:
+ project = client.projects.list()
+ assert_matches_type(SyncOffsetPagination[Project], project, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list_with_all_params(self, client: Kernel) -> None:
+ project = client.projects.list(
+ limit=100,
+ offset=0,
+ )
+ assert_matches_type(SyncOffsetPagination[Project], project, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_list(self, client: Kernel) -> None:
+ response = client.projects.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ project = response.parse()
+ assert_matches_type(SyncOffsetPagination[Project], project, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_list(self, client: Kernel) -> None:
+ with client.projects.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ project = response.parse()
+ assert_matches_type(SyncOffsetPagination[Project], project, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_delete(self, client: Kernel) -> None:
+ project = client.projects.delete(
+ "id",
+ )
+ assert project is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_delete(self, client: Kernel) -> None:
+ response = client.projects.with_raw_response.delete(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ project = response.parse()
+ assert project is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_delete(self, client: Kernel) -> None:
+ with client.projects.with_streaming_response.delete(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ project = response.parse()
+ assert project is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_delete(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.projects.with_raw_response.delete(
+ "",
+ )
+
+
+class TestAsyncProjects:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_create(self, async_client: AsyncKernel) -> None:
+ project = await async_client.projects.create(
+ name="staging",
+ )
+ assert_matches_type(Project, project, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_create(self, async_client: AsyncKernel) -> None:
+ response = await async_client.projects.with_raw_response.create(
+ name="staging",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ project = await response.parse()
+ assert_matches_type(Project, project, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_create(self, async_client: AsyncKernel) -> None:
+ async with async_client.projects.with_streaming_response.create(
+ name="staging",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ project = await response.parse()
+ assert_matches_type(Project, project, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_retrieve(self, async_client: AsyncKernel) -> None:
+ project = await async_client.projects.retrieve(
+ "id",
+ )
+ assert_matches_type(Project, project, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_retrieve(self, async_client: AsyncKernel) -> None:
+ response = await async_client.projects.with_raw_response.retrieve(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ project = await response.parse()
+ assert_matches_type(Project, project, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_retrieve(self, async_client: AsyncKernel) -> None:
+ async with async_client.projects.with_streaming_response.retrieve(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ project = await response.parse()
+ assert_matches_type(Project, project, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_retrieve(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.projects.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_update(self, async_client: AsyncKernel) -> None:
+ project = await async_client.projects.update(
+ id="id",
+ )
+ assert_matches_type(Project, project, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_update_with_all_params(self, async_client: AsyncKernel) -> None:
+ project = await async_client.projects.update(
+ id="id",
+ name="name",
+ status="active",
+ )
+ assert_matches_type(Project, project, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_update(self, async_client: AsyncKernel) -> None:
+ response = await async_client.projects.with_raw_response.update(
+ id="id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ project = await response.parse()
+ assert_matches_type(Project, project, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_update(self, async_client: AsyncKernel) -> None:
+ async with async_client.projects.with_streaming_response.update(
+ id="id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ project = await response.parse()
+ assert_matches_type(Project, project, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_update(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.projects.with_raw_response.update(
+ id="",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list(self, async_client: AsyncKernel) -> None:
+ project = await async_client.projects.list()
+ assert_matches_type(AsyncOffsetPagination[Project], project, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncKernel) -> None:
+ project = await async_client.projects.list(
+ limit=100,
+ offset=0,
+ )
+ assert_matches_type(AsyncOffsetPagination[Project], project, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncKernel) -> None:
+ response = await async_client.projects.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ project = await response.parse()
+ assert_matches_type(AsyncOffsetPagination[Project], project, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncKernel) -> None:
+ async with async_client.projects.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ project = await response.parse()
+ assert_matches_type(AsyncOffsetPagination[Project], project, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_delete(self, async_client: AsyncKernel) -> None:
+ project = await async_client.projects.delete(
+ "id",
+ )
+ assert project is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_delete(self, async_client: AsyncKernel) -> None:
+ response = await async_client.projects.with_raw_response.delete(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ project = await response.parse()
+ assert project is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_delete(self, async_client: AsyncKernel) -> None:
+ async with async_client.projects.with_streaming_response.delete(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ project = await response.parse()
+ assert project is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_delete(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.projects.with_raw_response.delete(
+ "",
+ )
From c0d866527a7157ca1e23dbcba60c334fea471a18 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 10 Apr 2026 02:14:54 +0000
Subject: [PATCH 5/5] release: 0.48.0
---
.release-please-manifest.json | 2 +-
CHANGELOG.md | 18 ++++++++++++++++++
pyproject.toml | 2 +-
src/kernel/_version.py | 2 +-
4 files changed, 21 insertions(+), 3 deletions(-)
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 141e7cd..ff66120 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.47.0"
+ ".": "0.48.0"
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5e99ff9..3e4f398 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,23 @@
# Changelog
+## 0.48.0 (2026-04-10)
+
+Full Changelog: [v0.47.0...v0.48.0](https://github.com/kernel/kernel-python-sdk/compare/v0.47.0...v0.48.0)
+
+### Features
+
+* [kernel-1116] add base_url field to browser session response ([335d9e0](https://github.com/kernel/kernel-python-sdk/commit/335d9e04998dd9e581f47d8869dd7f07a8f2db74))
+
+
+### Bug Fixes
+
+* **client:** preserve hardcoded query params when merging with user params ([6dfd882](https://github.com/kernel/kernel-python-sdk/commit/6dfd8826b84ed191d72ac114df986c398fa83c5c))
+
+
+### Chores
+
+* retrigger Stainless codegen for projects resource ([ca22fd9](https://github.com/kernel/kernel-python-sdk/commit/ca22fd9dc4b5f4cd70e15ecb1ddd3607d8a99df8))
+
## 0.47.0 (2026-04-07)
Full Changelog: [v0.46.0...v0.47.0](https://github.com/kernel/kernel-python-sdk/compare/v0.46.0...v0.47.0)
diff --git a/pyproject.toml b/pyproject.toml
index c9fe0cd..b26c2b0 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "kernel"
-version = "0.47.0"
+version = "0.48.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 254e7e2..a9881de 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.47.0" # x-release-please-version
+__version__ = "0.48.0" # x-release-please-version