From 457e6c14bd10df055044c2b48704b64d90ca8aff Mon Sep 17 00:00:00 2001 From: "kernel-internal[bot]" <260533166+kernel-internal[bot]@users.noreply.github.com> Date: Fri, 16 Oct 2026 20:06:51 +0000 Subject: [PATCH 1/5] feat: chore(stlc): seal custom-code tracking files Stainless-Generated-From: f9646cbea73c1147aefb46dce6e811b9683125e0 --- api.md | 8 +- src/kernel/resources/proxies.py | 86 ++++++++++--------- .../types/vaults/card_vault_item_spec.py | 7 +- .../vaults/card_vault_item_spec_param.py | 7 +- tests/api_resources/test_proxies.py | 76 ++++++++-------- 5 files changed, 88 insertions(+), 96 deletions(-) diff --git a/api.md b/api.md index 33b17d60..882ac3a8 100644 --- a/api.md +++ b/api.md @@ -484,11 +484,11 @@ from kernel.types import ( Methods: - client.proxies.create(\*\*params) -> ProxyCreateResponse -- client.proxies.retrieve(id) -> ProxyRetrieveResponse -- client.proxies.update(id, \*\*params) -> ProxyUpdateResponse +- client.proxies.retrieve(id_or_name) -> ProxyRetrieveResponse +- client.proxies.update(id_or_name, \*\*params) -> ProxyUpdateResponse - client.proxies.list(\*\*params) -> SyncOffsetPagination[ProxyListResponse] -- client.proxies.delete(id) -> None -- client.proxies.check(id, \*\*params) -> ProxyCheckResponse +- client.proxies.delete(id_or_name) -> None +- client.proxies.check(id_or_name, \*\*params) -> ProxyCheckResponse # Extensions diff --git a/src/kernel/resources/proxies.py b/src/kernel/resources/proxies.py index 240d18d1..4b0d3458 100644 --- a/src/kernel/resources/proxies.py +++ b/src/kernel/resources/proxies.py @@ -105,7 +105,7 @@ def create( def retrieve( self, - id: str, + id_or_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. @@ -115,7 +115,10 @@ def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ProxyRetrieveResponse: """ - Retrieve a proxy in the resolved project by ID. + Retrieve a proxy in the resolved project by ID or exact name using the database + collation. IDs take precedence when a value matches both an ID and a name. Proxy + names are not unique; an ambiguous name returns a 400 response with code + ambiguous_proxy_name. Args: extra_headers: Send extra headers @@ -126,10 +129,10 @@ def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + if not id_or_name: + raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}") return self._get( - path_template("/proxies/{id}", id=id), + path_template("/proxies/{id_or_name}", id_or_name=id_or_name), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -138,7 +141,7 @@ def retrieve( def update( self, - id: str, + id_or_name: str, *, name: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -150,10 +153,8 @@ def update( ) -> ProxyUpdateResponse: """Update a proxy's name. - Proxy names are not unique and are not ID-or-name - addressable on this endpoint; duplicate names are allowed. Name-based - session-create lookups can remain ambiguous until callers resolve proxies by ID - or the API adds a stronger uniqueness contract. + Proxy names are not unique and duplicate names are + allowed. Args: name: New proxy name. Proxy names are trimmed and length-checked only; duplicates are @@ -167,10 +168,10 @@ def update( timeout: Override the client-level default timeout for this request, in seconds """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + if not id_or_name: + raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}") return self._patch( - path_template("/proxies/{id}", id=id), + path_template("/proxies/{id_or_name}", id_or_name=id_or_name), body=maybe_transform({"name": name}, proxy_update_params.ProxyUpdateParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout @@ -238,7 +239,7 @@ def list( def delete( self, - id: str, + id_or_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. @@ -263,11 +264,11 @@ def delete( timeout: Override the client-level default timeout for this request, in seconds """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + if not id_or_name: + raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} return self._delete( - path_template("/proxies/{id}", id=id), + path_template("/proxies/{id_or_name}", id_or_name=id_or_name), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -276,7 +277,7 @@ def delete( def check( self, - id: str, + id_or_name: str, *, url: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -317,10 +318,10 @@ def check( timeout: Override the client-level default timeout for this request, in seconds """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + if not id_or_name: + raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}") return self._post( - path_template("/proxies/{id}/check", id=id), + path_template("/proxies/{id_or_name}/check", id_or_name=id_or_name), body=maybe_transform({"url": url}, proxy_check_params.ProxyCheckParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout @@ -406,7 +407,7 @@ async def create( async def retrieve( self, - id: str, + id_or_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. @@ -416,7 +417,10 @@ async def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ProxyRetrieveResponse: """ - Retrieve a proxy in the resolved project by ID. + Retrieve a proxy in the resolved project by ID or exact name using the database + collation. IDs take precedence when a value matches both an ID and a name. Proxy + names are not unique; an ambiguous name returns a 400 response with code + ambiguous_proxy_name. Args: extra_headers: Send extra headers @@ -427,10 +431,10 @@ async def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + if not id_or_name: + raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}") return await self._get( - path_template("/proxies/{id}", id=id), + path_template("/proxies/{id_or_name}", id_or_name=id_or_name), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -439,7 +443,7 @@ async def retrieve( async def update( self, - id: str, + id_or_name: str, *, name: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -451,10 +455,8 @@ async def update( ) -> ProxyUpdateResponse: """Update a proxy's name. - Proxy names are not unique and are not ID-or-name - addressable on this endpoint; duplicate names are allowed. Name-based - session-create lookups can remain ambiguous until callers resolve proxies by ID - or the API adds a stronger uniqueness contract. + Proxy names are not unique and duplicate names are + allowed. Args: name: New proxy name. Proxy names are trimmed and length-checked only; duplicates are @@ -468,10 +470,10 @@ async def update( timeout: Override the client-level default timeout for this request, in seconds """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + if not id_or_name: + raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}") return await self._patch( - path_template("/proxies/{id}", id=id), + path_template("/proxies/{id_or_name}", id_or_name=id_or_name), body=await async_maybe_transform({"name": name}, proxy_update_params.ProxyUpdateParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout @@ -539,7 +541,7 @@ def list( async def delete( self, - id: str, + id_or_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. @@ -564,11 +566,11 @@ async def delete( timeout: Override the client-level default timeout for this request, in seconds """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + if not id_or_name: + raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} return await self._delete( - path_template("/proxies/{id}", id=id), + path_template("/proxies/{id_or_name}", id_or_name=id_or_name), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -577,7 +579,7 @@ async def delete( async def check( self, - id: str, + id_or_name: str, *, url: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -618,10 +620,10 @@ async def check( timeout: Override the client-level default timeout for this request, in seconds """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + if not id_or_name: + raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}") return await self._post( - path_template("/proxies/{id}/check", id=id), + path_template("/proxies/{id_or_name}/check", id_or_name=id_or_name), body=await async_maybe_transform({"url": url}, proxy_check_params.ProxyCheckParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout diff --git a/src/kernel/types/vaults/card_vault_item_spec.py b/src/kernel/types/vaults/card_vault_item_spec.py index f6b471f8..749bf1d4 100644 --- a/src/kernel/types/vaults/card_vault_item_spec.py +++ b/src/kernel/types/vaults/card_vault_item_spec.py @@ -63,12 +63,7 @@ class LinkCardVaultItemSpec(BaseModel): """ amount: int - """Integer amount in minor currency units. - - Link Pay Token execution permits at most 500000; virtual-card execution permits - at most 50000. Authorization rejects a larger amount when the checkout requires - a virtual card. - """ + """Integer amount in minor currency units, at most 50000.""" context: str diff --git a/src/kernel/types/vaults/card_vault_item_spec_param.py b/src/kernel/types/vaults/card_vault_item_spec_param.py index 6e3ad521..c888247f 100644 --- a/src/kernel/types/vaults/card_vault_item_spec_param.py +++ b/src/kernel/types/vaults/card_vault_item_spec_param.py @@ -63,12 +63,7 @@ class LinkCardVaultItemSpec(TypedDict, total=False): """ amount: Required[int] - """Integer amount in minor currency units. - - Link Pay Token execution permits at most 500000; virtual-card execution permits - at most 50000. Authorization rejects a larger amount when the checkout requires - a virtual card. - """ + """Integer amount in minor currency units, at most 50000.""" context: Required[str] diff --git a/tests/api_resources/test_proxies.py b/tests/api_resources/test_proxies.py index 7fc6412c..a8737804 100644 --- a/tests/api_resources/test_proxies.py +++ b/tests/api_resources/test_proxies.py @@ -74,7 +74,7 @@ def test_streaming_response_create(self, client: Kernel) -> None: @parametrize def test_method_retrieve(self, client: Kernel) -> None: proxy = client.proxies.retrieve( - "id", + "id_or_name", ) assert_matches_type(ProxyRetrieveResponse, proxy, path=["response"]) @@ -82,7 +82,7 @@ def test_method_retrieve(self, client: Kernel) -> None: @parametrize def test_raw_response_retrieve(self, client: Kernel) -> None: response = client.proxies.with_raw_response.retrieve( - "id", + "id_or_name", ) assert response.is_closed is True @@ -94,7 +94,7 @@ def test_raw_response_retrieve(self, client: Kernel) -> None: @parametrize def test_streaming_response_retrieve(self, client: Kernel) -> None: with client.proxies.with_streaming_response.retrieve( - "id", + "id_or_name", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -107,7 +107,7 @@ def test_streaming_response_retrieve(self, client: Kernel) -> None: @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 ''"): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"): client.proxies.with_raw_response.retrieve( "", ) @@ -116,7 +116,7 @@ def test_path_params_retrieve(self, client: Kernel) -> None: @parametrize def test_method_update(self, client: Kernel) -> None: proxy = client.proxies.update( - id="id", + id_or_name="id_or_name", name="my-renamed-proxy", ) assert_matches_type(ProxyUpdateResponse, proxy, path=["response"]) @@ -125,7 +125,7 @@ def test_method_update(self, client: Kernel) -> None: @parametrize def test_raw_response_update(self, client: Kernel) -> None: response = client.proxies.with_raw_response.update( - id="id", + id_or_name="id_or_name", name="my-renamed-proxy", ) @@ -138,7 +138,7 @@ def test_raw_response_update(self, client: Kernel) -> None: @parametrize def test_streaming_response_update(self, client: Kernel) -> None: with client.proxies.with_streaming_response.update( - id="id", + id_or_name="id_or_name", name="my-renamed-proxy", ) as response: assert not response.is_closed @@ -152,9 +152,9 @@ def test_streaming_response_update(self, client: Kernel) -> None: @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 ''"): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"): client.proxies.with_raw_response.update( - id="", + id_or_name="", name="my-renamed-proxy", ) @@ -201,7 +201,7 @@ def test_streaming_response_list(self, client: Kernel) -> None: @parametrize def test_method_delete(self, client: Kernel) -> None: proxy = client.proxies.delete( - "id", + "id_or_name", ) assert proxy is None @@ -209,7 +209,7 @@ def test_method_delete(self, client: Kernel) -> None: @parametrize def test_raw_response_delete(self, client: Kernel) -> None: response = client.proxies.with_raw_response.delete( - "id", + "id_or_name", ) assert response.is_closed is True @@ -221,7 +221,7 @@ def test_raw_response_delete(self, client: Kernel) -> None: @parametrize def test_streaming_response_delete(self, client: Kernel) -> None: with client.proxies.with_streaming_response.delete( - "id", + "id_or_name", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -234,7 +234,7 @@ def test_streaming_response_delete(self, client: Kernel) -> None: @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 ''"): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"): client.proxies.with_raw_response.delete( "", ) @@ -243,7 +243,7 @@ def test_path_params_delete(self, client: Kernel) -> None: @parametrize def test_method_check(self, client: Kernel) -> None: proxy = client.proxies.check( - id="id", + id_or_name="id_or_name", ) assert_matches_type(ProxyCheckResponse, proxy, path=["response"]) @@ -251,7 +251,7 @@ def test_method_check(self, client: Kernel) -> None: @parametrize def test_method_check_with_all_params(self, client: Kernel) -> None: proxy = client.proxies.check( - id="id", + id_or_name="id_or_name", url="https://example.com", ) assert_matches_type(ProxyCheckResponse, proxy, path=["response"]) @@ -260,7 +260,7 @@ def test_method_check_with_all_params(self, client: Kernel) -> None: @parametrize def test_raw_response_check(self, client: Kernel) -> None: response = client.proxies.with_raw_response.check( - id="id", + id_or_name="id_or_name", ) assert response.is_closed is True @@ -272,7 +272,7 @@ def test_raw_response_check(self, client: Kernel) -> None: @parametrize def test_streaming_response_check(self, client: Kernel) -> None: with client.proxies.with_streaming_response.check( - id="id", + id_or_name="id_or_name", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -285,9 +285,9 @@ def test_streaming_response_check(self, client: Kernel) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_path_params_check(self, client: Kernel) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"): client.proxies.with_raw_response.check( - id="", + id_or_name="", ) @@ -346,7 +346,7 @@ async def test_streaming_response_create(self, async_client: AsyncKernel) -> Non @parametrize async def test_method_retrieve(self, async_client: AsyncKernel) -> None: proxy = await async_client.proxies.retrieve( - "id", + "id_or_name", ) assert_matches_type(ProxyRetrieveResponse, proxy, path=["response"]) @@ -354,7 +354,7 @@ async def test_method_retrieve(self, async_client: AsyncKernel) -> None: @parametrize async def test_raw_response_retrieve(self, async_client: AsyncKernel) -> None: response = await async_client.proxies.with_raw_response.retrieve( - "id", + "id_or_name", ) assert response.is_closed is True @@ -366,7 +366,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncKernel) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncKernel) -> None: async with async_client.proxies.with_streaming_response.retrieve( - "id", + "id_or_name", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -379,7 +379,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncKernel) -> N @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 ''"): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"): await async_client.proxies.with_raw_response.retrieve( "", ) @@ -388,7 +388,7 @@ async def test_path_params_retrieve(self, async_client: AsyncKernel) -> None: @parametrize async def test_method_update(self, async_client: AsyncKernel) -> None: proxy = await async_client.proxies.update( - id="id", + id_or_name="id_or_name", name="my-renamed-proxy", ) assert_matches_type(ProxyUpdateResponse, proxy, path=["response"]) @@ -397,7 +397,7 @@ async def test_method_update(self, async_client: AsyncKernel) -> None: @parametrize async def test_raw_response_update(self, async_client: AsyncKernel) -> None: response = await async_client.proxies.with_raw_response.update( - id="id", + id_or_name="id_or_name", name="my-renamed-proxy", ) @@ -410,7 +410,7 @@ async def test_raw_response_update(self, async_client: AsyncKernel) -> None: @parametrize async def test_streaming_response_update(self, async_client: AsyncKernel) -> None: async with async_client.proxies.with_streaming_response.update( - id="id", + id_or_name="id_or_name", name="my-renamed-proxy", ) as response: assert not response.is_closed @@ -424,9 +424,9 @@ async def test_streaming_response_update(self, async_client: AsyncKernel) -> Non @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 ''"): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"): await async_client.proxies.with_raw_response.update( - id="", + id_or_name="", name="my-renamed-proxy", ) @@ -473,7 +473,7 @@ async def test_streaming_response_list(self, async_client: AsyncKernel) -> None: @parametrize async def test_method_delete(self, async_client: AsyncKernel) -> None: proxy = await async_client.proxies.delete( - "id", + "id_or_name", ) assert proxy is None @@ -481,7 +481,7 @@ async def test_method_delete(self, async_client: AsyncKernel) -> None: @parametrize async def test_raw_response_delete(self, async_client: AsyncKernel) -> None: response = await async_client.proxies.with_raw_response.delete( - "id", + "id_or_name", ) assert response.is_closed is True @@ -493,7 +493,7 @@ async def test_raw_response_delete(self, async_client: AsyncKernel) -> None: @parametrize async def test_streaming_response_delete(self, async_client: AsyncKernel) -> None: async with async_client.proxies.with_streaming_response.delete( - "id", + "id_or_name", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -506,7 +506,7 @@ async def test_streaming_response_delete(self, async_client: AsyncKernel) -> Non @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 ''"): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"): await async_client.proxies.with_raw_response.delete( "", ) @@ -515,7 +515,7 @@ async def test_path_params_delete(self, async_client: AsyncKernel) -> None: @parametrize async def test_method_check(self, async_client: AsyncKernel) -> None: proxy = await async_client.proxies.check( - id="id", + id_or_name="id_or_name", ) assert_matches_type(ProxyCheckResponse, proxy, path=["response"]) @@ -523,7 +523,7 @@ async def test_method_check(self, async_client: AsyncKernel) -> None: @parametrize async def test_method_check_with_all_params(self, async_client: AsyncKernel) -> None: proxy = await async_client.proxies.check( - id="id", + id_or_name="id_or_name", url="https://example.com", ) assert_matches_type(ProxyCheckResponse, proxy, path=["response"]) @@ -532,7 +532,7 @@ async def test_method_check_with_all_params(self, async_client: AsyncKernel) -> @parametrize async def test_raw_response_check(self, async_client: AsyncKernel) -> None: response = await async_client.proxies.with_raw_response.check( - id="id", + id_or_name="id_or_name", ) assert response.is_closed is True @@ -544,7 +544,7 @@ async def test_raw_response_check(self, async_client: AsyncKernel) -> None: @parametrize async def test_streaming_response_check(self, async_client: AsyncKernel) -> None: async with async_client.proxies.with_streaming_response.check( - id="id", + id_or_name="id_or_name", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -557,7 +557,7 @@ async def test_streaming_response_check(self, async_client: AsyncKernel) -> None @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_path_params_check(self, async_client: AsyncKernel) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"): await async_client.proxies.with_raw_response.check( - id="", + id_or_name="", ) From c1a6765c789f5ed7585a55780f83435cc8c5d4ee Mon Sep 17 00:00:00 2001 From: "kernel-internal[bot]" <260533166+kernel-internal[bot]@users.noreply.github.com> Date: Mon, 19 Oct 2026 16:51:08 +0000 Subject: [PATCH 2/5] feat: Support existing-user AgentCard wallet imports Stainless-Generated-From: 207cf9bd5926ddf327a2b6db9df5a97300e62a59 --- src/kernel/resources/vaults/items.py | 24 +++++++++++++++---- src/kernel/types/vaults/item_upsert_params.py | 14 ++++++++--- src/kernel/types/vaults/vault_item.py | 12 ++++++++-- .../vaults/vault_item_operation_response.py | 12 ++++++++-- .../types/vaults/wallet_vault_item_spec.py | 2 +- 5 files changed, 52 insertions(+), 12 deletions(-) diff --git a/src/kernel/resources/vaults/items.py b/src/kernel/resources/vaults/items.py index 209c9e86..9829d498 100644 --- a/src/kernel/resources/vaults/items.py +++ b/src/kernel/resources/vaults/items.py @@ -1236,8 +1236,16 @@ def upsert( select a customer-owned configuration. Mode (sandbox vs live) is determined by the selected credential; there is no per-item test flag. Without user_id, creation returns a hosted enrollment action and Kernel polls until the user - connects. user_id may only reference a user already enrolled by a wallet in this - organization under the same configuration. + connects. With a customer-owned provider_config, user_id imports an existing + connection after verifying access with the selected credentials in their sandbox + or live mode; no hosted enrollment is needed, even if the connected user has no + stored cards. With Kernel-managed credentials, user_id may only reference a user + already enrolled by a wallet in this organization under the same configuration. + The user and configuration bindings cannot be changed after creation. Import + validation is read-only and runs before creation: a failed validation leaves no + wallet or reserved key, so the user_id can be corrected and retried. Concurrent + imports of the same key may both validate, but create at most one wallet and + connected event. extra_headers: Send extra headers @@ -2609,8 +2617,16 @@ async def upsert( select a customer-owned configuration. Mode (sandbox vs live) is determined by the selected credential; there is no per-item test flag. Without user_id, creation returns a hosted enrollment action and Kernel polls until the user - connects. user_id may only reference a user already enrolled by a wallet in this - organization under the same configuration. + connects. With a customer-owned provider_config, user_id imports an existing + connection after verifying access with the selected credentials in their sandbox + or live mode; no hosted enrollment is needed, even if the connected user has no + stored cards. With Kernel-managed credentials, user_id may only reference a user + already enrolled by a wallet in this organization under the same configuration. + The user and configuration bindings cannot be changed after creation. Import + validation is read-only and runs before creation: a failed validation leaves no + wallet or reserved key, so the user_id can be corrected and retried. Concurrent + imports of the same key may both validate, but create at most one wallet and + connected event. extra_headers: Send extra headers diff --git a/src/kernel/types/vaults/item_upsert_params.py b/src/kernel/types/vaults/item_upsert_params.py index 3fa6c509..04bd3f11 100644 --- a/src/kernel/types/vaults/item_upsert_params.py +++ b/src/kernel/types/vaults/item_upsert_params.py @@ -40,8 +40,16 @@ class WalletVaultItemRequest(TypedDict, total=False): customer-owned configuration. Mode (sandbox vs live) is determined by the selected credential; there is no per-item test flag. Without user_id, creation returns a hosted enrollment action and Kernel polls until the user connects. - user_id may only reference a user already enrolled by a wallet in this - organization under the same configuration. + With a customer-owned provider_config, user_id imports an existing connection + after verifying access with the selected credentials in their sandbox or live + mode; no hosted enrollment is needed, even if the connected user has no stored + cards. With Kernel-managed credentials, user_id may only reference a user + already enrolled by a wallet in this organization under the same configuration. + The user and configuration bindings cannot be changed after creation. Import + validation is read-only and runs before creation: a failed validation leaves no + wallet or reserved key, so the user_id can be corrected and retried. Concurrent + imports of the same key may both validate, but create at most one wallet and + connected event. """ type: Required[Literal["wallet"]] @@ -162,7 +170,7 @@ class WalletVaultItemRequestSpecAgentCardWalletVaultItemSpecProviderConfig(Typed class WalletVaultItemRequestSpecAgentCardWalletVaultItemSpec(TypedDict, total=False): """AgentCard wallet. - Omit provider_config to use Kernel-managed credentials, or select a customer-owned configuration. Mode (sandbox vs live) is determined by the selected credential; there is no per-item test flag. Without user_id, creation returns a hosted enrollment action and Kernel polls until the user connects. user_id may only reference a user already enrolled by a wallet in this organization under the same configuration. + Omit provider_config to use Kernel-managed credentials, or select a customer-owned configuration. Mode (sandbox vs live) is determined by the selected credential; there is no per-item test flag. Without user_id, creation returns a hosted enrollment action and Kernel polls until the user connects. With a customer-owned provider_config, user_id imports an existing connection after verifying access with the selected credentials in their sandbox or live mode; no hosted enrollment is needed, even if the connected user has no stored cards. With Kernel-managed credentials, user_id may only reference a user already enrolled by a wallet in this organization under the same configuration. The user and configuration bindings cannot be changed after creation. Import validation is read-only and runs before creation: a failed validation leaves no wallet or reserved key, so the user_id can be corrected and retried. Concurrent imports of the same key may both validate, but create at most one wallet and connected event. """ provider: Required[Literal["agentcard"]] diff --git a/src/kernel/types/vaults/vault_item.py b/src/kernel/types/vaults/vault_item.py index 1b72d59c..45d2f021 100644 --- a/src/kernel/types/vaults/vault_item.py +++ b/src/kernel/types/vaults/vault_item.py @@ -86,8 +86,16 @@ class WalletVaultItem(BaseModel): customer-owned configuration. Mode (sandbox vs live) is determined by the selected credential; there is no per-item test flag. Without user_id, creation returns a hosted enrollment action and Kernel polls until the user connects. - user_id may only reference a user already enrolled by a wallet in this - organization under the same configuration. + With a customer-owned provider_config, user_id imports an existing connection + after verifying access with the selected credentials in their sandbox or live + mode; no hosted enrollment is needed, even if the connected user has no stored + cards. With Kernel-managed credentials, user_id may only reference a user + already enrolled by a wallet in this organization under the same configuration. + The user and configuration bindings cannot be changed after creation. Import + validation is read-only and runs before creation: a failed validation leaves no + wallet or reserved key, so the user_id can be corrected and retried. Concurrent + imports of the same key may both validate, but create at most one wallet and + connected event. """ state: WalletVaultItemState diff --git a/src/kernel/types/vaults/vault_item_operation_response.py b/src/kernel/types/vaults/vault_item_operation_response.py index 4faf7487..a7a31733 100644 --- a/src/kernel/types/vaults/vault_item_operation_response.py +++ b/src/kernel/types/vaults/vault_item_operation_response.py @@ -88,8 +88,16 @@ class WalletVaultItem(BaseModel): customer-owned configuration. Mode (sandbox vs live) is determined by the selected credential; there is no per-item test flag. Without user_id, creation returns a hosted enrollment action and Kernel polls until the user connects. - user_id may only reference a user already enrolled by a wallet in this - organization under the same configuration. + With a customer-owned provider_config, user_id imports an existing connection + after verifying access with the selected credentials in their sandbox or live + mode; no hosted enrollment is needed, even if the connected user has no stored + cards. With Kernel-managed credentials, user_id may only reference a user + already enrolled by a wallet in this organization under the same configuration. + The user and configuration bindings cannot be changed after creation. Import + validation is read-only and runs before creation: a failed validation leaves no + wallet or reserved key, so the user_id can be corrected and retried. Concurrent + imports of the same key may both validate, but create at most one wallet and + connected event. """ state: WalletVaultItemState diff --git a/src/kernel/types/vaults/wallet_vault_item_spec.py b/src/kernel/types/vaults/wallet_vault_item_spec.py index 288fd7e3..6c4d9950 100644 --- a/src/kernel/types/vaults/wallet_vault_item_spec.py +++ b/src/kernel/types/vaults/wallet_vault_item_spec.py @@ -81,7 +81,7 @@ class AgentCardWalletVaultItemSpecProviderConfig(BaseModel): class AgentCardWalletVaultItemSpec(BaseModel): """AgentCard wallet. - Omit provider_config to use Kernel-managed credentials, or select a customer-owned configuration. Mode (sandbox vs live) is determined by the selected credential; there is no per-item test flag. Without user_id, creation returns a hosted enrollment action and Kernel polls until the user connects. user_id may only reference a user already enrolled by a wallet in this organization under the same configuration. + Omit provider_config to use Kernel-managed credentials, or select a customer-owned configuration. Mode (sandbox vs live) is determined by the selected credential; there is no per-item test flag. Without user_id, creation returns a hosted enrollment action and Kernel polls until the user connects. With a customer-owned provider_config, user_id imports an existing connection after verifying access with the selected credentials in their sandbox or live mode; no hosted enrollment is needed, even if the connected user has no stored cards. With Kernel-managed credentials, user_id may only reference a user already enrolled by a wallet in this organization under the same configuration. The user and configuration bindings cannot be changed after creation. Import validation is read-only and runs before creation: a failed validation leaves no wallet or reserved key, so the user_id can be corrected and retried. Concurrent imports of the same key may both validate, but create at most one wallet and connected event. """ provider: Literal["agentcard"] From a9ab0d3955c3535458f99f117fae744e34c66930 Mon Sep 17 00:00:00 2001 From: "kernel-internal[bot]" <260533166+kernel-internal[bot]@users.noreply.github.com> Date: Mon, 19 Oct 2026 17:21:53 +0000 Subject: [PATCH 3/5] feat: Fence browser allowlist updates with revision checks Stainless-Generated-From: 98e3c1efbf1a574ca1fd596e616e5a0dd857b703 --- api.md | 1 + src/kernel/types/__init__.py | 2 ++ src/kernel/types/browser_allowlist_state.py | 21 +++++++++++++++++++ .../types/browser_allowlist_state_param.py | 21 +++++++++++++++++++ src/kernel/types/browser_network_config.py | 9 ++++++++ .../types/browser_network_update_param.py | 19 +++++++++++++++++ tests/api_resources/test_browsers.py | 12 +++++++++-- 7 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 src/kernel/types/browser_allowlist_state.py create mode 100644 src/kernel/types/browser_allowlist_state_param.py diff --git a/api.md b/api.md index 882ac3a8..9822c59d 100644 --- a/api.md +++ b/api.md @@ -117,6 +117,7 @@ Types: ```python from kernel.types import ( + BrowserAllowlistState, BrowserMemory, BrowserMemoryRequest, BrowserNetworkConfig, diff --git a/src/kernel/types/__init__.py b/src/kernel/types/__init__.py index a3eac5b8..656272ec 100644 --- a/src/kernel/types/__init__.py +++ b/src/kernel/types/__init__.py @@ -98,6 +98,7 @@ from .invocation_state_event import InvocationStateEvent as InvocationStateEvent from .recommendation_summary import RecommendationSummary as RecommendationSummary from .api_key_retrieve_params import APIKeyRetrieveParams as APIKeyRetrieveParams +from .browser_allowlist_state import BrowserAllowlistState as BrowserAllowlistState from .browser_create_response import BrowserCreateResponse as BrowserCreateResponse from .browser_retrieve_params import BrowserRetrieveParams as BrowserRetrieveParams from .browser_update_response import BrowserUpdateResponse as BrowserUpdateResponse @@ -138,6 +139,7 @@ from .deployment_retrieve_response import DeploymentRetrieveResponse as DeploymentRetrieveResponse from .invocation_retrieve_response import InvocationRetrieveResponse as InvocationRetrieveResponse from .audit_log_export_chunk_params import AuditLogExportChunkParams as AuditLogExportChunkParams +from .browser_allowlist_state_param import BrowserAllowlistStateParam as BrowserAllowlistStateParam from .browser_pool_acquire_response import BrowserPoolAcquireResponse as BrowserPoolAcquireResponse from .config_registry_lookup_params import ConfigRegistryLookupParams as ConfigRegistryLookupParams from .credential_totp_code_response import CredentialTotpCodeResponse as CredentialTotpCodeResponse diff --git a/src/kernel/types/browser_allowlist_state.py b/src/kernel/types/browser_allowlist_state.py new file mode 100644 index 00000000..21ee5cfb --- /dev/null +++ b/src/kernel/types/browser_allowlist_state.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["BrowserAllowlistState"] + + +class BrowserAllowlistState(BaseModel): + revision: str + """Opaque revision to pass as network.expected_allowlist_revision on an update.""" + + base_revision: Optional[str] = None + """ + Revision against which that mutation was accepted; reuse it when retrying the + mutation. + """ + + mutation_id: Optional[str] = None + """ID of the last conditional allowlist update saved on this browser.""" diff --git a/src/kernel/types/browser_allowlist_state_param.py b/src/kernel/types/browser_allowlist_state_param.py new file mode 100644 index 00000000..72e0a3ef --- /dev/null +++ b/src/kernel/types/browser_allowlist_state_param.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["BrowserAllowlistStateParam"] + + +class BrowserAllowlistStateParam(TypedDict, total=False): + revision: Required[str] + """Opaque revision to pass as network.expected_allowlist_revision on an update.""" + + base_revision: str + """ + Revision against which that mutation was accepted; reuse it when retrying the + mutation. + """ + + mutation_id: str + """ID of the last conditional allowlist update saved on this browser.""" diff --git a/src/kernel/types/browser_network_config.py b/src/kernel/types/browser_network_config.py index cd205e65..d326c6bf 100644 --- a/src/kernel/types/browser_network_config.py +++ b/src/kernel/types/browser_network_config.py @@ -3,6 +3,7 @@ from typing import List, Optional from .._models import BaseModel +from .browser_allowlist_state import BrowserAllowlistState __all__ = ["BrowserNetworkConfig", "ProxyRoute", "ProxyRouteProxy"] @@ -58,6 +59,14 @@ class BrowserNetworkConfig(BaseModel): replaced on release rather than given an allowlist again. Requires proxy v3. """ + allowlist_state: Optional[BrowserAllowlistState] = None + """Concurrency metadata for a browser's saved allowlist configuration. + + Browser pools omit it. This remains present after removal so an uncertain + removal can be retried. It does not prove proxy or VM application completed. Do + not include it in creation or pool updates. + """ + private_hosts: Optional[List[str]] = None """ Destinations the browser reaches directly through the session's own network diff --git a/src/kernel/types/browser_network_update_param.py b/src/kernel/types/browser_network_update_param.py index 31158d46..f329552d 100644 --- a/src/kernel/types/browser_network_update_param.py +++ b/src/kernel/types/browser_network_update_param.py @@ -34,3 +34,22 @@ class BrowserNetworkUpdateParam(TypedDict, total=False): safely restored. If the request fails, retry it: the new list may already apply to some requests. """ + + expected_allowlist_revision: str + """The network.allowlist_state.revision from the browser's last saved snapshot. + + Supply together with mutation_id to opt into conditional allowlist updates. The + revision is checked atomically with the configuration write. A stale request + returns 409 stale_network without changing the allowlist. A GET alone does not + fence an earlier unfinished PATCH. + """ + + mutation_id: str + """ + A unique ID for this allowlist update, supplied together with + expected_allowlist_revision. Reuse both values and the identical allowed_hosts + payload when retrying an uncertain update. If it is still the current mutation, + the retry finishes proxy and VM application without creating another revision. A + superseded mutation returns 409 stale_network. These fields guard only + network.allowed_hosts, not other browser update fields. + """ diff --git a/tests/api_resources/test_browsers.py b/tests/api_resources/test_browsers.py index d12c67b2..c1475b95 100644 --- a/tests/api_resources/test_browsers.py +++ b/tests/api_resources/test_browsers.py @@ -211,7 +211,11 @@ def test_method_update_with_all_params(self, client: Kernel) -> None: id_or_name="htzv5orfit78e1m2biiifpbv", disable_default_proxy=True, name="checkout-flow-1", - network={"allowed_hosts": ["example.com", "*.example.com"]}, + network={ + "allowed_hosts": ["example.com", "*.example.com"], + "expected_allowlist_revision": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "mutation_id": "mutation_id", + }, profile={ "id": "id", "name": "name", @@ -758,7 +762,11 @@ async def test_method_update_with_all_params(self, async_client: AsyncKernel) -> id_or_name="htzv5orfit78e1m2biiifpbv", disable_default_proxy=True, name="checkout-flow-1", - network={"allowed_hosts": ["example.com", "*.example.com"]}, + network={ + "allowed_hosts": ["example.com", "*.example.com"], + "expected_allowlist_revision": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "mutation_id": "mutation_id", + }, profile={ "id": "id", "name": "name", From 48fd52646ea0dafcecd637d2cae6c71a19318f7a Mon Sep 17 00:00:00 2001 From: "kernel-internal[bot]" <260533166+kernel-internal[bot]@users.noreply.github.com> Date: Mon, 19 Oct 2026 19:09:28 +0000 Subject: [PATCH 4/5] feat: Add browser location session policy Stainless-Generated-From: a55a0720ed50df5df1001ae2613977041468284b --- README.md | 4 +-- api.md | 4 +++ src/kernel/resources/browsers/browsers.py | 32 +++++++++++++++++++ src/kernel/types/__init__.py | 6 ++++ src/kernel/types/browser_create_params.py | 8 +++++ src/kernel/types/browser_create_response.py | 4 +++ src/kernel/types/browser_list_response.py | 4 +++ src/kernel/types/browser_location.py | 28 ++++++++++++++++ .../types/browser_location_request_param.py | 21 ++++++++++++ src/kernel/types/browser_location_source.py | 7 ++++ .../browser_location_update_request_param.py | 26 +++++++++++++++ .../types/browser_pool_acquire_response.py | 4 +++ src/kernel/types/browser_retrieve_response.py | 4 +++ src/kernel/types/browser_update_params.py | 12 +++++++ src/kernel/types/browser_update_response.py | 4 +++ .../invocation_list_browsers_response.py | 4 +++ tests/api_resources/test_browsers.py | 16 ++++++++++ 17 files changed, 186 insertions(+), 2 deletions(-) create mode 100644 src/kernel/types/browser_location.py create mode 100644 src/kernel/types/browser_location_request_param.py create mode 100644 src/kernel/types/browser_location_source.py create mode 100644 src/kernel/types/browser_location_update_request_param.py diff --git a/README.md b/README.md index 35a3bc41..a8c9fc1c 100644 --- a/README.md +++ b/README.md @@ -203,9 +203,9 @@ from kernel import Kernel client = Kernel() browser = client.browsers.create( - network={}, + location={}, ) -print(browser.network) +print(browser.location) ``` ## File uploads diff --git a/api.md b/api.md index 9822c59d..5463b586 100644 --- a/api.md +++ b/api.md @@ -118,6 +118,10 @@ Types: ```python from kernel.types import ( BrowserAllowlistState, + BrowserLocation, + BrowserLocationRequest, + BrowserLocationSource, + BrowserLocationUpdateRequest, BrowserMemory, BrowserMemoryRequest, BrowserNetworkConfig, diff --git a/src/kernel/resources/browsers/browsers.py b/src/kernel/resources/browsers/browsers.py index 0333bfdd..885c7709 100644 --- a/src/kernel/resources/browsers/browsers.py +++ b/src/kernel/resources/browsers/browsers.py @@ -116,8 +116,10 @@ from ...types.browser_network_config_param import BrowserNetworkConfigParam from ...types.browser_network_update_param import BrowserNetworkUpdateParam from ...types.shared_params.browser_profile import BrowserProfile +from ...types.browser_location_request_param import BrowserLocationRequestParam from ...types.shared_params.browser_viewport import BrowserViewport from ...types.shared_params.browser_extension import BrowserExtension +from ...types.browser_location_update_request_param import BrowserLocationUpdateRequestParam __all__ = ["BrowsersResource", "AsyncBrowsersResource"] @@ -195,6 +197,7 @@ def create( headless: bool | Omit = omit, invocation_id: str | Omit = omit, kiosk_mode: bool | Omit = omit, + location: BrowserLocationRequestParam | Omit = omit, memory: BrowserMemoryRequest | Omit = omit, name: str | Omit = omit, network: BrowserNetworkConfigParam | Omit = omit, @@ -239,6 +242,9 @@ def create( kiosk_mode: If true, launches the browser in kiosk mode to hide address bar and tabs in live view. + location: Optional session-only timezone and locale overrides. Each omitted field uses the + default for the browser's region. start_url opens once the location applies. + memory: Memory for a non-GPU browser. Headless: 1GiB (default), 2GiB, or 4GiB. Headful: 8GiB (default) or 16GiB. @@ -333,6 +339,7 @@ def create( "headless": headless, "invocation_id": invocation_id, "kiosk_mode": kiosk_mode, + "location": location, "memory": memory, "name": name, "network": network, @@ -404,6 +411,7 @@ def update( id_or_name: str, *, disable_default_proxy: bool | Omit = omit, + location: BrowserLocationUpdateRequestParam | Omit = omit, name: Optional[str] | Omit = omit, network: BrowserNetworkUpdateParam | Omit = omit, profile: BrowserProfile | Omit = omit, @@ -427,6 +435,14 @@ def update( disable_default_proxy: If true, stealth browsers connect directly instead of using the default stealth proxy. Deprecated in favor of proxy.mode. + location: Session-only timezone and locale overrides. Omit the object or a field to leave + it unchanged; set a string to override; set null to clear that field and use the + browser region's default. A browser that cannot change location refuses the + request before anything changes. The location applies after the request's other + changes, and start_url opens once it has applied. If applying it fails, the + other changes and the saved override remain and start_url does not open; retry + the request to apply both. + name: Human-readable name for the browser session. Omit to leave unchanged, set to an empty string to clear the name. When set, must be unique among active sessions within the project. @@ -478,6 +494,7 @@ def update( body=maybe_transform( { "disable_default_proxy": disable_default_proxy, + "location": location, "name": name, "network": network, "profile": profile, @@ -912,6 +929,7 @@ async def create( headless: bool | Omit = omit, invocation_id: str | Omit = omit, kiosk_mode: bool | Omit = omit, + location: BrowserLocationRequestParam | Omit = omit, memory: BrowserMemoryRequest | Omit = omit, name: str | Omit = omit, network: BrowserNetworkConfigParam | Omit = omit, @@ -956,6 +974,9 @@ async def create( kiosk_mode: If true, launches the browser in kiosk mode to hide address bar and tabs in live view. + location: Optional session-only timezone and locale overrides. Each omitted field uses the + default for the browser's region. start_url opens once the location applies. + memory: Memory for a non-GPU browser. Headless: 1GiB (default), 2GiB, or 4GiB. Headful: 8GiB (default) or 16GiB. @@ -1050,6 +1071,7 @@ async def create( "headless": headless, "invocation_id": invocation_id, "kiosk_mode": kiosk_mode, + "location": location, "memory": memory, "name": name, "network": network, @@ -1121,6 +1143,7 @@ async def update( id_or_name: str, *, disable_default_proxy: bool | Omit = omit, + location: BrowserLocationUpdateRequestParam | Omit = omit, name: Optional[str] | Omit = omit, network: BrowserNetworkUpdateParam | Omit = omit, profile: BrowserProfile | Omit = omit, @@ -1144,6 +1167,14 @@ async def update( disable_default_proxy: If true, stealth browsers connect directly instead of using the default stealth proxy. Deprecated in favor of proxy.mode. + location: Session-only timezone and locale overrides. Omit the object or a field to leave + it unchanged; set a string to override; set null to clear that field and use the + browser region's default. A browser that cannot change location refuses the + request before anything changes. The location applies after the request's other + changes, and start_url opens once it has applied. If applying it fails, the + other changes and the saved override remain and start_url does not open; retry + the request to apply both. + name: Human-readable name for the browser session. Omit to leave unchanged, set to an empty string to clear the name. When set, must be unique among active sessions within the project. @@ -1195,6 +1226,7 @@ async def update( body=await async_maybe_transform( { "disable_default_proxy": disable_default_proxy, + "location": location, "name": name, "network": network, "profile": profile, diff --git a/src/kernel/types/__init__.py b/src/kernel/types/__init__.py index 656272ec..a7735052 100644 --- a/src/kernel/types/__init__.py +++ b/src/kernel/types/__init__.py @@ -44,6 +44,7 @@ from .lookup_response import LookupResponse as LookupResponse from .vault_reference import VaultReference as VaultReference from .analysis_summary import AnalysisSummary as AnalysisSummary +from .browser_location import BrowserLocation as BrowserLocation from .browser_pool_ref import BrowserPoolRef as BrowserPoolRef from .app_list_response import AppListResponse as AppListResponse from .no_recommendation import NoRecommendation as NoRecommendation @@ -100,6 +101,7 @@ from .api_key_retrieve_params import APIKeyRetrieveParams as APIKeyRetrieveParams from .browser_allowlist_state import BrowserAllowlistState as BrowserAllowlistState from .browser_create_response import BrowserCreateResponse as BrowserCreateResponse +from .browser_location_source import BrowserLocationSource as BrowserLocationSource from .browser_retrieve_params import BrowserRetrieveParams as BrowserRetrieveParams from .browser_update_response import BrowserUpdateResponse as BrowserUpdateResponse from .extension_list_response import ExtensionListResponse as ExtensionListResponse @@ -144,6 +146,7 @@ from .config_registry_lookup_params import ConfigRegistryLookupParams as ConfigRegistryLookupParams from .credential_totp_code_response import CredentialTotpCodeResponse as CredentialTotpCodeResponse from .browser_load_extensions_params import BrowserLoadExtensionsParams as BrowserLoadExtensionsParams +from .browser_location_request_param import BrowserLocationRequestParam as BrowserLocationRequestParam from .config_registry_resolve_params import ConfigRegistryResolveParams as ConfigRegistryResolveParams from .credential_provider_list_params import CredentialProviderListParams as CredentialProviderListParams from .credential_provider_test_result import CredentialProviderTestResult as CredentialProviderTestResult @@ -153,6 +156,9 @@ from .vault_provider_config_list_params import VaultProviderConfigListParams as VaultProviderConfigListParams from .vault_provider_config_create_params import VaultProviderConfigCreateParams as VaultProviderConfigCreateParams from .vault_provider_config_update_params import VaultProviderConfigUpdateParams as VaultProviderConfigUpdateParams +from .browser_location_update_request_param import ( + BrowserLocationUpdateRequestParam as BrowserLocationUpdateRequestParam, +) from .credential_provider_list_items_response import ( CredentialProviderListItemsResponse as CredentialProviderListItemsResponse, ) diff --git a/src/kernel/types/browser_create_params.py b/src/kernel/types/browser_create_params.py index d773f8cc..8e319668 100644 --- a/src/kernel/types/browser_create_params.py +++ b/src/kernel/types/browser_create_params.py @@ -12,6 +12,7 @@ from .browser_proxy_config_param import BrowserProxyConfigParam from .browser_network_config_param import BrowserNetworkConfigParam from .shared_params.browser_profile import BrowserProfile +from .browser_location_request_param import BrowserLocationRequestParam from .shared_params.browser_viewport import BrowserViewport from .shared_params.browser_extension import BrowserExtension from .browsers.browser_telemetry_categories_config_param import BrowserTelemetryCategoriesConfigParam @@ -62,6 +63,13 @@ class BrowserCreateParams(TypedDict, total=False): view. """ + location: BrowserLocationRequestParam + """Optional session-only timezone and locale overrides. + + Each omitted field uses the default for the browser's region. start_url opens + once the location applies. + """ + memory: BrowserMemoryRequest """Memory for a non-GPU browser. diff --git a/src/kernel/types/browser_create_response.py b/src/kernel/types/browser_create_response.py index 25e82277..53ae17de 100644 --- a/src/kernel/types/browser_create_response.py +++ b/src/kernel/types/browser_create_response.py @@ -11,6 +11,7 @@ from .browser_usage import BrowserUsage from .browser_memory import BrowserMemory from .vault_reference import VaultReference +from .browser_location import BrowserLocation from .browser_pool_ref import BrowserPoolRef from .browser_video_memory import BrowserVideoMemory from .browser_network_config import BrowserNetworkConfig @@ -76,6 +77,9 @@ class BrowserCreateResponse(BaseModel): kiosk_mode: Optional[bool] = None """Whether the browser session is running in kiosk mode.""" + location: Optional[BrowserLocation] = None + """Effective browser timezone and locale with per-field provenance.""" + name: Optional[str] = None """Human-readable name of the browser session, if one was set at creation.""" diff --git a/src/kernel/types/browser_list_response.py b/src/kernel/types/browser_list_response.py index 6527b9c2..fa6c01ba 100644 --- a/src/kernel/types/browser_list_response.py +++ b/src/kernel/types/browser_list_response.py @@ -11,6 +11,7 @@ from .browser_usage import BrowserUsage from .browser_memory import BrowserMemory from .vault_reference import VaultReference +from .browser_location import BrowserLocation from .browser_pool_ref import BrowserPoolRef from .browser_video_memory import BrowserVideoMemory from .browser_network_config import BrowserNetworkConfig @@ -76,6 +77,9 @@ class BrowserListResponse(BaseModel): kiosk_mode: Optional[bool] = None """Whether the browser session is running in kiosk mode.""" + location: Optional[BrowserLocation] = None + """Effective browser timezone and locale with per-field provenance.""" + name: Optional[str] = None """Human-readable name of the browser session, if one was set at creation.""" diff --git a/src/kernel/types/browser_location.py b/src/kernel/types/browser_location.py new file mode 100644 index 00000000..1ae33af7 --- /dev/null +++ b/src/kernel/types/browser_location.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel +from .browser_location_source import BrowserLocationSource + +__all__ = ["BrowserLocation"] + + +class BrowserLocation(BaseModel): + locale: str + """Effective canonical BCP 47 formatting locale selected for the browser session.""" + + locale_source: BrowserLocationSource + """Where one effective browser location field came from. + + `override` is set explicitly on the session; `metro` is the default for the + browser's region. + """ + + timezone: str + """Effective IANA timezone selected for the browser session.""" + + timezone_source: BrowserLocationSource + """Where one effective browser location field came from. + + `override` is set explicitly on the session; `metro` is the default for the + browser's region. + """ diff --git a/src/kernel/types/browser_location_request_param.py b/src/kernel/types/browser_location_request_param.py new file mode 100644 index 00000000..55b73014 --- /dev/null +++ b/src/kernel/types/browser_location_request_param.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["BrowserLocationRequestParam"] + + +class BrowserLocationRequestParam(TypedDict, total=False): + locale: str + """Session-only supported canonical BCP 47 locale override. + + Omit to use the default for the browser's region. + """ + + timezone: str + """Session-only IANA timezone override. + + Omit to use the default for the browser's region. + """ diff --git a/src/kernel/types/browser_location_source.py b/src/kernel/types/browser_location_source.py new file mode 100644 index 00000000..b067e5ef --- /dev/null +++ b/src/kernel/types/browser_location_source.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["BrowserLocationSource"] + +BrowserLocationSource: TypeAlias = Literal["override", "metro"] diff --git a/src/kernel/types/browser_location_update_request_param.py b/src/kernel/types/browser_location_update_request_param.py new file mode 100644 index 00000000..a0742845 --- /dev/null +++ b/src/kernel/types/browser_location_update_request_param.py @@ -0,0 +1,26 @@ +# 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__ = ["BrowserLocationUpdateRequestParam"] + + +class BrowserLocationUpdateRequestParam(TypedDict, total=False): + """ + Omit a field to leave it unchanged, set a string override, or set null to clear the override and use the default for the browser's region. + """ + + locale: Optional[str] + """Session-only supported canonical BCP 47 locale override. + + Null clears the override and uses the browser region's default. + """ + + timezone: Optional[str] + """Session-only IANA timezone override. + + Null clears the override and uses the browser region's default. + """ diff --git a/src/kernel/types/browser_pool_acquire_response.py b/src/kernel/types/browser_pool_acquire_response.py index 8791bb7b..cceb9892 100644 --- a/src/kernel/types/browser_pool_acquire_response.py +++ b/src/kernel/types/browser_pool_acquire_response.py @@ -12,6 +12,7 @@ from .browser_usage import BrowserUsage from .browser_memory import BrowserMemory from .vault_reference import VaultReference +from .browser_location import BrowserLocation from .browser_pool_ref import BrowserPoolRef from .browser_video_memory import BrowserVideoMemory from .browser_network_config import BrowserNetworkConfig @@ -77,6 +78,9 @@ class BrowserPoolAcquireResponse(NoContentAsNone, BaseModel): kiosk_mode: Optional[bool] = None """Whether the browser session is running in kiosk mode.""" + location: Optional[BrowserLocation] = None + """Effective browser timezone and locale with per-field provenance.""" + name: Optional[str] = None """Human-readable name of the browser session, if one was set at creation.""" diff --git a/src/kernel/types/browser_retrieve_response.py b/src/kernel/types/browser_retrieve_response.py index 735c9ba4..c537589f 100644 --- a/src/kernel/types/browser_retrieve_response.py +++ b/src/kernel/types/browser_retrieve_response.py @@ -11,6 +11,7 @@ from .browser_usage import BrowserUsage from .browser_memory import BrowserMemory from .vault_reference import VaultReference +from .browser_location import BrowserLocation from .browser_pool_ref import BrowserPoolRef from .browser_video_memory import BrowserVideoMemory from .browser_network_config import BrowserNetworkConfig @@ -76,6 +77,9 @@ class BrowserRetrieveResponse(BaseModel): kiosk_mode: Optional[bool] = None """Whether the browser session is running in kiosk mode.""" + location: Optional[BrowserLocation] = None + """Effective browser timezone and locale with per-field provenance.""" + name: Optional[str] = None """Human-readable name of the browser session, if one was set at creation.""" diff --git a/src/kernel/types/browser_update_params.py b/src/kernel/types/browser_update_params.py index 475010bd..3be13135 100644 --- a/src/kernel/types/browser_update_params.py +++ b/src/kernel/types/browser_update_params.py @@ -10,6 +10,7 @@ from .browser_network_update_param import BrowserNetworkUpdateParam from .shared_params.browser_profile import BrowserProfile from .shared_params.browser_viewport import BrowserViewport +from .browser_location_update_request_param import BrowserLocationUpdateRequestParam from .browsers.browser_telemetry_categories_config_param import BrowserTelemetryCategoriesConfigParam __all__ = [ @@ -30,6 +31,17 @@ class BrowserUpdateParams(TypedDict, total=False): proxy. Deprecated in favor of proxy.mode. """ + location: BrowserLocationUpdateRequestParam + """Session-only timezone and locale overrides. + + Omit the object or a field to leave it unchanged; set a string to override; set + null to clear that field and use the browser region's default. A browser that + cannot change location refuses the request before anything changes. The location + applies after the request's other changes, and start_url opens once it has + applied. If applying it fails, the other changes and the saved override remain + and start_url does not open; retry the request to apply both. + """ + name: Optional[str] """Human-readable name for the browser session. diff --git a/src/kernel/types/browser_update_response.py b/src/kernel/types/browser_update_response.py index b53d2ac3..5b9f8ae3 100644 --- a/src/kernel/types/browser_update_response.py +++ b/src/kernel/types/browser_update_response.py @@ -11,6 +11,7 @@ from .browser_usage import BrowserUsage from .browser_memory import BrowserMemory from .vault_reference import VaultReference +from .browser_location import BrowserLocation from .browser_pool_ref import BrowserPoolRef from .browser_video_memory import BrowserVideoMemory from .browser_network_config import BrowserNetworkConfig @@ -76,6 +77,9 @@ class BrowserUpdateResponse(BaseModel): kiosk_mode: Optional[bool] = None """Whether the browser session is running in kiosk mode.""" + location: Optional[BrowserLocation] = None + """Effective browser timezone and locale with per-field provenance.""" + name: Optional[str] = None """Human-readable name of the browser session, if one was set at creation.""" diff --git a/src/kernel/types/invocation_list_browsers_response.py b/src/kernel/types/invocation_list_browsers_response.py index dc99f0dc..2df9877c 100644 --- a/src/kernel/types/invocation_list_browsers_response.py +++ b/src/kernel/types/invocation_list_browsers_response.py @@ -11,6 +11,7 @@ from .browser_usage import BrowserUsage from .browser_memory import BrowserMemory from .vault_reference import VaultReference +from .browser_location import BrowserLocation from .browser_pool_ref import BrowserPoolRef from .browser_video_memory import BrowserVideoMemory from .browser_network_config import BrowserNetworkConfig @@ -76,6 +77,9 @@ class Browser(BaseModel): kiosk_mode: Optional[bool] = None """Whether the browser session is running in kiosk mode.""" + location: Optional[BrowserLocation] = None + """Effective browser timezone and locale with per-field provenance.""" + name: Optional[str] = None """Human-readable name of the browser session, if one was set at creation.""" diff --git a/tests/api_resources/test_browsers.py b/tests/api_resources/test_browsers.py index c1475b95..6c5e9696 100644 --- a/tests/api_resources/test_browsers.py +++ b/tests/api_resources/test_browsers.py @@ -46,6 +46,10 @@ def test_method_create_with_all_params(self, client: Kernel) -> None: headless=False, invocation_id="rr33xuugxj9h0bkf1rdt2bet", kiosk_mode=True, + location={ + "locale": "en-US", + "timezone": "America/Los_Angeles", + }, memory="8GiB", name="checkout-flow-1", network={ @@ -210,6 +214,10 @@ def test_method_update_with_all_params(self, client: Kernel) -> None: browser = client.browsers.update( id_or_name="htzv5orfit78e1m2biiifpbv", disable_default_proxy=True, + location={ + "locale": "en-US", + "timezone": "America/Los_Angeles", + }, name="checkout-flow-1", network={ "allowed_hosts": ["example.com", "*.example.com"], @@ -597,6 +605,10 @@ async def test_method_create_with_all_params(self, async_client: AsyncKernel) -> headless=False, invocation_id="rr33xuugxj9h0bkf1rdt2bet", kiosk_mode=True, + location={ + "locale": "en-US", + "timezone": "America/Los_Angeles", + }, memory="8GiB", name="checkout-flow-1", network={ @@ -761,6 +773,10 @@ async def test_method_update_with_all_params(self, async_client: AsyncKernel) -> browser = await async_client.browsers.update( id_or_name="htzv5orfit78e1m2biiifpbv", disable_default_proxy=True, + location={ + "locale": "en-US", + "timezone": "America/Los_Angeles", + }, name="checkout-flow-1", network={ "allowed_hosts": ["example.com", "*.example.com"], From a32f2ad7ea01c1fb7cbeeed8909a96c8100dd6af Mon Sep 17 00:00:00 2001 From: "kernel-internal[bot]" <260533166+kernel-internal[bot]@users.noreply.github.com> Date: Mon, 19 Oct 2026 19:15:00 +0000 Subject: [PATCH 5/5] release: 0.128.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 10 ++++++++++ pyproject.toml | 2 +- src/kernel/_version.py | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 47775e91..f118d043 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.127.0" + ".": "0.128.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index dcd39316..4193ab9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## [0.128.0](https://github.com/kernel/kernel-python-sdk/compare/v0.127.0...v0.128.0) (2026-10-19) + + +### Features + +* Add browser location session policy ([48fd526](https://github.com/kernel/kernel-python-sdk/commit/48fd52646ea0dafcecd637d2cae6c71a19318f7a)) +* chore(stlc): seal custom-code tracking files ([457e6c1](https://github.com/kernel/kernel-python-sdk/commit/457e6c14bd10df055044c2b48704b64d90ca8aff)) +* Fence browser allowlist updates with revision checks ([a9ab0d3](https://github.com/kernel/kernel-python-sdk/commit/a9ab0d3955c3535458f99f117fae744e34c66930)) +* Support existing-user AgentCard wallet imports ([c1a6765](https://github.com/kernel/kernel-python-sdk/commit/c1a6765c789f5ed7585a55780f83435cc8c5d4ee)) + ## [0.127.0](https://github.com/kernel/kernel-python-sdk/compare/v0.126.0...v0.127.0) (2026-10-16) diff --git a/pyproject.toml b/pyproject.toml index d2de2e3b..eca42025 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "kernel" -version = "0.127.0" +version = "0.128.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 d351e0a2..ba7941bc 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.127.0" # x-release-please-version +__version__ = "0.128.0" # x-release-please-version